#pragma once

#include "MyFRAM_I2C.h"

namespace MY_FRAMCONTROL
{ 
  consteval uint32_t sizeofObject (const auto &data) { return sizeof(data); }
  consteval uint32_t lengthStructure (const auto &data, const size_t N) { return sizeofObject(data)/N; }
  constinit uint8_t TWI_BUFFER_SIZE {BUFFER_LENGTH-2}; // 'BUFFER_LENGTH' in twi.h abzüglich 2 Bytes Speicherzellenadressierung
}

/*                I2C Addr
                  |                 wire0/wire1  
                  |                 |              Capacity in kBit       
                  |                 |              |                 Object for length determination etc.                 
                  |                 |              |                 |               initial address of object in FRAM
                  |                 |              |                 |               |                              */
template <uint8_t i2cAddr, TwoWire& line, uint16_t memorySize, auto &myObj, uint32_t initObjAddr>
class MyFramControl   // allgemeines Template
{
  static_assert((10<=BUFFER_LENGTH), "TWI buffer size smaller than 10 is possible but not practical, Wire src twi.h" );
  static_assert(((initObjAddr+sizeof(myObj)) < (1024UL*memorySize/8)), "Object to large or Addr to high" );
  private:  
    MyFRAM_I2C <i2cAddr, line, memorySize> fram;
   
    uint32_t calcMemberAddr (const uint32_t objStartIndex, const uint32_t memberOffset) {
      using namespace MY_FRAMCONTROL;
      return initObjAddr + (objStartIndex*lengthStructure(myObj,1)) + memberOffset;
    }
    
  public:
    MyFramControl() = default;
    
    uint8_t isConnected (void) { return fram.isConnected(); }
    
    uint8_t getError (void) { return fram.getError(); }
    
    uint32_t getCellAddr (void) { return fram.getCellAddr(); }
    
    void setCellAddr (const uint32_t var) { fram.setCellAddr(var); }
    
    void update (const uint32_t objStartIndex, const uint32_t memberOffset, auto &data) {
      const uint32_t memberAddr = calcMemberAddr(objStartIndex, memberOffset);
      fram.write(memberAddr, data);
    }
    
    void monitor (Stream &out, uint32_t fromAddr, uint32_t toAddr, const uint8_t width, const uint8_t base = DEC) {
      fram.monitor (out, fromAddr, toAddr, width, base);
    }
    
    void clear (const uint32_t fromAddr, const uint32_t toAddr) { 
      fram.fill (fromAddr, toAddr, 0xFF);  
    }
    
    void clear (const uint32_t fromAddr, const uint32_t toAddr, const uint8_t data) { 
      fram.fill (fromAddr, toAddr, data);  
    }
    
    void read (void) { fram.read(initObjAddr, myObj); }
    
    void write (void) { fram.write(initObjAddr, myObj); }
      
    void readTo (auto(&otherObj)[1]) { fram.read(initObjAddr, otherObj); }   
    
    void wasBinIch (void) { cout.println("allg. Template"); }
};

template <uint8_t i2cAddr, TwoWire& line, uint16_t memorySize, typename T, size_t N, T(&myObj)[N], uint32_t initObjAddr> 
class MyFramControl <i2cAddr, line, memorySize, myObj, initObjAddr>  // array Template Spezialisierung
{
  static_assert((10<=BUFFER_LENGTH), "TWI buffer size smaller than 10 is possible but not practical, Wire src twi.h" );
  static_assert(((initObjAddr+sizeof(myObj)) < (1024UL*memorySize/8)), "Object to large or Addr to high" );
  private:  
    MyFRAM_I2C <i2cAddr, line, memorySize> fram;
   
    uint32_t calcMemberAddr (const uint32_t objStartIndex, const uint32_t memberOffset) {
      using namespace MY_FRAMCONTROL;
      return initObjAddr + (objStartIndex*lengthStructure(myObj,N)) + memberOffset;
    }
    
  public:
    MyFramControl() = default;
    
    uint8_t isConnected (void) { return fram.isConnected(); }
    
    uint8_t getError (void) { return fram.getError(); }
    
    uint32_t getCellAddr (void) { return fram.getCellAddr(); }
    
    void setCellAddr (const uint32_t var) { fram.setCellAddr(var); }
    
    void update (const uint32_t objStartIndex, const uint32_t memberOffset, auto &data) {
      const uint32_t memberAddr = calcMemberAddr(objStartIndex, memberOffset);
      fram.write(memberAddr, data);
    }
    
    void monitor (Stream &out, uint32_t fromAddr, uint32_t toAddr, const uint8_t width, const uint8_t base = DEC) {
      fram.monitor (out, fromAddr, toAddr, width, base);
    }
    
    void clear (const uint32_t fromAddr, const uint32_t toAddr) { 
      fram.fill (fromAddr, toAddr, 0xFF);  
    }
    
    void clear (const uint32_t fromAddr, const uint32_t toAddr, const uint8_t data) { 
      fram.fill (fromAddr, toAddr, data);  
    }
    
    void read (void) { fram.read(initObjAddr, myObj); }
    
    void write (void) { fram.write(initObjAddr, myObj); }
      
    void readTo (T(&otherObj)[N]) { fram.read(initObjAddr, otherObj); } 
    
    void wasBinIch (void) { cout.println("array spez. Template"); }
};