/*
  EEPROMEx.cpp - Extended EEPROM library
  Copyright (c) 2012 Thijs Elenbaas.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/******************************************************************************
 * Includes
 ******************************************************************************/
#include "EEPROMex.h"

/******************************************************************************
 * Definitions
 ******************************************************************************/

 #define _EEPROMEX_VERSION 1 // software version of this library
 #define _EEPROMEX_DEBUG     // Enables logging of maximum of writes and out-of-memory
/******************************************************************************
 * Constructors
 ******************************************************************************/

// Boards with ATmega328, Duemilanove, Uno, Uno SMD, Lilypad - 1024 bytes (1 kilobyte)
// Boards with ATmega1280 or 2560, Arduino Mega series – 4096 bytes (4 kilobytes)
// Boards with ATmega168, Lilypad, old Nano, Diecimila  – 512 bytes
// By default we choose conservative settings
EEPROMClassEx::EEPROMClassEx()
  :  _allowedWrites(100)
{
}
 
/******************************************************************************
 * User API
 ******************************************************************************/

void EEPROMClassEx::setMemPool(int base, int memSize) {
	//Base can only be adjusted if no addresses have already been issued
	if (_nextAvailableaddress == _base) 
		_base = base;
		_nextAvailableaddress=_base;
	
	//Ceiling can only be adjusted if not below issued addresses
	if (memSize >= _nextAvailableaddress ) 
		_memSize = memSize;

	#ifdef _EEPROMEX_DEBUG    
	if (_nextAvailableaddress != _base) 
		Serial.println("Cannot change base, addresses have been issued");

	if (memSize < _nextAvailableaddress )  
		Serial.println("Cannot change ceiling, below issued addresses");
	#endif	
	
}

void EEPROMClassEx::setMaxAllowedWrites(int allowedWrites) {
#ifdef _EEPROMEX_DEBUG
	_allowedWrites = allowedWrites;
#endif			
}

int EEPROMClassEx::getAddress(int noOfBytes){
	int availableaddress   = _nextAvailableaddress;
	_nextAvailableaddress += noOfBytes;

#ifdef _EEPROMEX_DEBUG    
	if (_nextAvailableaddress > _memSize) {
		Serial.println("Attempt to write outside of EEPROM memory");
		return -availableaddress;
	} else {
		return availableaddress;
	}
#endif
	return availableaddress;		
}
 

bool EEPROMClassEx::isReady() {
	return eeprom_is_ready();
}

uint8_t EEPROMClassEx::read(int address)
{
	return readByte(address);
}

bool EEPROMClassEx::readBit(int address, byte bit) {
	  if (bit> 7) return false; 
	  if (!isReadOk(address+sizeof(uint8_t))) return false;
	  byte byteVal =  eeprom_read_byte((unsigned char *) address);      
	  byte bytePos = (1 << bit);
      return (byteVal & bytePos);
}

uint8_t EEPROMClassEx::readByte(int address)
{	
	if (!isReadOk(address+sizeof(uint8_t))) return 0;
	return eeprom_read_byte((unsigned char *) address);
}

uint16_t EEPROMClassEx::readInt(int address)
{
	if (!isReadOk(address+sizeof(uint16_t))) return 0;
	return eeprom_read_word((uint16_t *) address);
}

uint32_t EEPROMClassEx::readLong(int address)
{
	if (!isReadOk(address+sizeof(uint32_t))) return 0;
	return eeprom_read_dword((unsigned long *) address);
}

float EEPROMClassEx::readFloat(int address)
{
	if (!isReadOk(address+sizeof(float))) return 0;
	float _value;
	readBlock<float>(address, _value);
	return _value;
}

double EEPROMClassEx::readDouble(int address)
{
	if (!isReadOk(address+sizeof(double))) return 0;	
	double _value;
	readBlock<double>(address, _value);
	return _value;
}

bool EEPROMClassEx::write(int address, uint8_t value)
{
	return writeByte(address, value);
}

bool EEPROMClassEx::writeBit(int address, uint8_t bit, bool value) {
	updateBit(address, bit, value);
	return true;
}


bool EEPROMClassEx::writeByte(int address, uint8_t value)
{
	if (!isWriteOk(address+sizeof(uint8_t))) return false;
	eeprom_write_byte((unsigned char *) address, value);
	return true;
}

bool EEPROMClassEx::writeInt(int address, uint16_t value)
{
	if (!isWriteOk(address+sizeof(uint16_t))) return false;
	eeprom_write_word((uint16_t *) address, value);
	return true;
}

bool EEPROMClassEx::writeLong(int address, uint32_t value)
{
	if (!isWriteOk(address+sizeof(uint32_t))) return false;
	eeprom_write_dword((unsigned long *) address, value);
	return true;
}

bool EEPROMClassEx::writeFloat(int address, float value)
{
	return (writeBlock<float>(address, value)!=0);	
}

bool EEPROMClassEx::writeDouble(int address, double value)
{
	return (writeBlock<float>(address, value)!=0);	
}

bool EEPROMClassEx::update(int address, uint8_t value)
{
	return (updateByte(address, value));
}

bool EEPROMClassEx::updateBit(int address, uint8_t bit, bool value) 
{
	  if (bit> 7) return false; 
	  
	  byte byteValInput  = readByte(address);
	  byte byteValOutput = byteValInput;	  
	  // Set bit
	  if (value) {	    
		byteValOutput |= (1 << bit);  //Set bit to 1
	  } else {		
	    byteValOutput &= ~(1 << bit); //Set bit to 0
	  }
	  // Store if different from input
	  if (byteValOutput!=byteValInput) {
		writeByte(address, byteValOutput);	  
	  }
	  return true;
}

bool EEPROMClassEx::updateByte(int address, uint8_t value)
{
	return (updateBlock<uint8_t>(address, value)!=0);
}

bool EEPROMClassEx::updateInt(int address, uint16_t value)
{
	return (updateBlock<uint16_t>(address, value)!=0);
}

bool EEPROMClassEx::updateLong(int address, uint32_t value)
{
	return (updateBlock<uint32_t>(address, value)!=0);
}

bool EEPROMClassEx::updateFloat(int address, float value)
{
	return (updateBlock<float>(address, value)!=0);
}

bool EEPROMClassEx::updateDouble(int address, double value)
{
	return (writeBlock<double>(address, value)!=0);
}

bool EEPROMClassEx::isWriteOk(int address)
{
#ifdef _EEPROMEX_DEBUG    
	_writeCounts++;
	if (_allowedWrites == 0 || _writeCounts > _allowedWrites ) {
		Serial.println("Exceeded maximum number of writes");
		return false;
	}
	
	if (address > _memSize) {
		Serial.println("Attempt to write outside of EEPROM memory");
		return false;
	} else {
		return true;
	}
#endif		
	return true;
}

bool EEPROMClassEx::isReadOk(int address)
{
#ifdef _EEPROMEX_DEBUG    
	if (address > _memSize) {
		Serial.println("Attempt to write outside of EEPROM memory");
		return false;
	} else {
		return true;
	}
#endif
	return true;	
}

int EEPROMClassEx::_base= 0;
int EEPROMClassEx::_memSize= 512;
int EEPROMClassEx::_nextAvailableaddress= 0;
int EEPROMClassEx::_writeCounts =0;

EEPROMClassEx EEPROM;
