//- -----------------------------------------------------------------------------------------------------------------------
// AskSin++
// 2016-10-31 papa Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//- -----------------------------------------------------------------------------------------------------------------------

#ifndef __CHANNEL_H__
#define __CHANNEL_H__

#include "Peer.h"
#include "Storage.h"

namespace as {

template <class HalType,class List0Type> class Device;
class ActionSetMsg;
class ActionCommandMsg;
class RemoteEventMsg;
class SensorEventMsg;

template<class HalType,class List1Type,class List3Type,class List4Type,int PeerCount,class List0Type=List0, class List2Type=EmptyList>
class Channel {
  Device<HalType,List0Type>*   dev;
  bool      change : 1; // the status is changed, we may need to send a status
  bool      inhi   : 1;
  uint8_t   num   ; // channels per device
  uint16_t  addr  ; // start address in eeprom

public:
  typedef List1Type List1;
  typedef List2Type List2;
  typedef List3Type List3;
  typedef List4Type List4;
  typedef Device<HalType,List0Type> DeviceType;

  public:
  Channel () : dev(0), change(false), inhi(false), num(0), addr(0) {}

  DeviceType& device () { return *dev; }

  const DeviceType& device () const { return *dev; }

  uint8_t number () const { return num; }

  uint16_t address () const { return addr; }

  uint8_t peers () const { return PeerCount; }

  bool changed () const { return change; }

  void changed (bool c) { change = c; }

  void inhibit (bool value) { inhi = value; }

  bool inhibit () const { return inhi; }

  bool aesActive () const { return getList1().aesActive(); }

  void setup(Device<HalType,List0Type>* dev,uint8_t number,uint16_t addr) {
    this->dev = dev;
    this->num = number;
    this->addr = addr;
  }

  uint16_t size () const {
    uint16_t size = sizeof(Peer);
    size += List3::size() + List4::size();
    size *= PeerCount;
    size += List1::size();
    return size;
  }

  bool has (const Peer& p) const {
    for( uint8_t i=0; i<peers(); ++i ) {
      if( p == peerat(i) ) {
        return true;
      }
    }
    return false;
  }

  uint8_t peerfor (const HMID& hmid) const {
    for( uint8_t i=0; i<peers(); ++i ) {
      if( hmid == peerat(i) ) {
        return i;
      }
    }
    return 0xff;
  }

  Peer peerat (uint8_t idx) const {
    Peer result;
    uint16_t paddr = peerAddress(idx);
    if( paddr != 0 ) {
      storage().getData(paddr,&result);
    }
    return result;
  }


  bool peer (const Peer& p) {
    deletepeer(p);
    uint8_t pidx = findpeer();
    if( pidx != 0xff ) {
      storage().setData(peerAddress(pidx),p);
      if( hasList3() == true ) {
        getList3(pidx).single();
      }
      return true;
    }
    return false;
  }

  bool peer (const Peer& p1,const Peer& p2) const {
    deletepeer(p1);
    deletepeer(p2);
    uint8_t pidx1 = findpeer();
    if( pidx1 != 0xff ) {
      storage().setData(peerAddress(pidx1),p1);
      uint8_t pidx2 = findpeer();
      if( pidx2 != 0xff ) {
        storage().setData(peerAddress(pidx2),p2);
        if( hasList3() == true ) {
          if( p1.odd() == true ) {
            getList3(pidx1).odd();
            getList3(pidx2).even();
          }
          else {
            getList3(pidx2).odd();
            getList3(pidx1).even();
          }
        }
        return true;
      }
      else {
        // free already stored data
        deletepeer(p1);
      }
    }
    return false;
  }

  bool deletepeer (uint8_t idx) const {
    return storage().setData(peerAddress(idx),Peer());
  }

  uint8_t findpeer () const {
    for( int i=0; i<peers(); ++i ) {
      if( peerat(i).valid()==false ) {
        return i;
      }
    }
    return 0xff;
  }

  bool deletepeer (const Peer& p) const {
    for( uint8_t i=0; i<peers(); ++i ) {
      if( peerat(i) == p ) {
        deletepeer(i);
      }
    }
    return true;
  }

  void firstinit () {
    storage().clearData(address(),size());
    List1Type cl1 = getList1();
    cl1.defaults();
    if( hasList2() ) {
      List2Type cl2 = getList2();
      cl2.defaults();
    }
  }

  List1Type getList1 () const {
    // we start always with list1
    return List1Type(address());
  }

  List2Type getList2 () const {
    return List2Type(address()+List1::size());
  }

  List3Type getList3 (const Peer& p) const {
    uint16_t liststart = 0x00;
    if( hasList3() == true ) {
      for( uint8_t i=0; i<peers(); ++i ) {
        if( peerat(i) == p ) {
          liststart = peerAddress(i) + sizeof(Peer);
          break;
        }
      }
    }
    return List3Type(liststart);
  }

  List4Type getList4 (const Peer& p) const {
    uint16_t liststart = 0x00;
    if( hasList4() == true ) {
      for( uint8_t i=0; i<peers(); ++i ) {
        if( peerat(i) == p ) {
          liststart = peerAddress(i) + sizeof(Peer) + List3::size();
          break;
        }
      }
    }
    return List4Type(liststart);
  }

  List3Type getList3 (uint8_t pidx) const {
    uint16_t liststart = 0x00;
    if( hasList3() == true && pidx < peers() ) {
      liststart = peerAddress(pidx) + sizeof(Peer);
    }
    return List3Type(liststart);
  }

  List4Type getList4 (uint8_t pidx) const {
    uint16_t liststart = 0x00;
    if( hasList4() == true && pidx < peers() ) {
      liststart = peerAddress(pidx) + sizeof(Peer) + List3::size();
    }
    return List4Type(liststart);
  }

  static bool hasList2 () {
    return List2Type::size() > 0;
  }

  static bool hasList3 () {
    return List3Type::size() > 0;
  }

  static bool hasList4 () {
    return List4Type::size() > 0;
  }

  void stop () {}

  bool process (__attribute__((unused)) const ActionSetMsg& msg) {
    return false;
  }

  bool process (__attribute__((unused)) const ActionCommandMsg& msg) {
    return false;
  }
 
  bool process (__attribute__((unused)) const RemoteEventMsg& msg) {
    return false;
  }

  bool process (__attribute__((unused)) const SensorEventMsg& msg) {
    return false;
  }

  void patchStatus (__attribute__((unused)) Message& msg) {}

  void configChanged () {}

  protected:
  uint16_t peerAddress (uint8_t pidx) const {
    if( pidx < PeerCount ) {
      uint16_t offset = sizeof(Peer);
      offset += List3::size() + List4::size();
      offset *= pidx;
      offset += List1::size() + List2::size();
      return addr + offset;
    }
    return 0x00;
  }
};

template <class HalType,class List1Type,class List3Type,int PeerCount,class List0Type, class StateMachine, class List2Type=EmptyList>
class ActorChannel : public Channel<HalType,List1Type, List3Type,EmptyList,PeerCount,List0Type, List2Type >, public StateMachine {
public:
  typedef Channel<HalType,List1Type, List3Type,EmptyList,PeerCount,List0Type, List2Type> BaseChannel;
  uint8_t lastmsgcnt;

public:
  ActorChannel () : BaseChannel(), lastmsgcnt(0xff) {}
  ~ActorChannel() {}

  bool changed () const { return StateMachine::changed(); }
  void changed (bool c) { StateMachine::changed(c); }

  void setup(Device<HalType,List0Type>* dev,uint8_t number,uint16_t addr) {
    BaseChannel::setup(dev,number,addr);
    StateMachine::setup(this->getList1());
  }

  uint8_t status () const {
    return StateMachine::status();
  }

  uint8_t flags () const {
    uint8_t flags = StateMachine::flags();
    if( this->device().battery().low() == true ) {
      flags |= 0x80;
    }
    return flags;
  }

  void status (uint8_t stat, uint16_t delay) {
    StateMachine::status(stat,delay);
  }


  void stop () {
    StateMachine::stop();
  }

  bool process (__attribute__((unused)) const ActionCommandMsg& msg) {
    return true;
  }

  bool process (const ActionSetMsg& msg) {
    StateMachine::set( msg.value(), msg.ramp(), msg.delay() );
    return true;
  }

  bool process (const RemoteEventMsg& msg) {
    if( BaseChannel::hasList3() ) {
      bool lg = msg.isLong();
      Peer p(msg.peer());
      uint8_t cnt = msg.counter();
      List3Type l3 = BaseChannel::getList3(p);
      if( l3.valid() == true ) {
        // l3.dump();
        typename List3Type::PeerList pl = lg ? l3.lg() : l3.sh();
        // pl.dump();
        if( lg == false || cnt != lastmsgcnt || pl.multiExec() == true ) {
          lastmsgcnt = cnt;
          StateMachine::remote(pl,cnt);
        }
        return true;
      }
    }
    return false;
  }

  bool process (const SensorEventMsg& msg) {
    if( BaseChannel::hasList3() ) {
      bool lg = msg.isLong();
      Peer p(msg.peer());
      uint8_t cnt = msg.counter();
      uint8_t value = msg.value();
      List3Type l3 = BaseChannel::getList3(p);
      if( l3.valid() == true ) {
        // l3.dump();
        typename List3Type::PeerList pl = lg ? l3.lg() : l3.sh();
        // pl.dump();
        StateMachine::sensor(pl,cnt,value);
        return true;
      }
    }
    return false;
  }
};


template <class HalType,class List0Type=List0>
class VirtBaseChannel {
public:
  VirtBaseChannel () {}
  virtual ~VirtBaseChannel () {}

  virtual void setup(Device<HalType,List0Type>* dev,uint8_t number,uint16_t addr) = 0;
  virtual uint16_t size () const = 0;
  virtual uint8_t number () const = 0;
  virtual uint16_t address () const = 0;
  virtual uint8_t peers () const = 0;
  virtual bool changed () const = 0;
  virtual void changed (bool c) = 0;
  virtual void inhibit (bool value) = 0;
  virtual bool inhibit () const = 0;
  virtual bool aesActive () const = 0;
  virtual bool has (const Peer& p) const = 0;
  virtual Peer peerat (uint8_t idx) const = 0;
  virtual bool peer (const Peer& p) = 0;
  virtual bool peer (const Peer& p1,const Peer& p2) = 0;
  virtual bool deletepeer (const Peer& p) = 0;
  virtual void firstinit () = 0;

#ifndef SENSOR_ONLY
  virtual void stop () = 0;
  virtual bool process (const ActionSetMsg& msg) = 0;
  virtual bool process (const ActionCommandMsg& msg) = 0;
  virtual bool process (const RemoteEventMsg& msg) = 0;
  virtual bool process (const SensorEventMsg& msg) = 0;
#endif
  virtual uint8_t status () = 0;
  virtual uint8_t flags () = 0;

  virtual void patchStatus (Message& msg) = 0;
  virtual void configChanged () = 0;

  virtual GenericList getList1 () const = 0;
  virtual GenericList getList2 () const = 0;
  virtual GenericList getList3 (const Peer& p) const = 0;
  virtual GenericList getList4 (const Peer& p) const = 0;
  virtual bool hasList2 () const = 0;
  virtual bool hasList3 () const = 0;
  virtual bool hasList4 () const = 0;

};

template <class HalType,class ChannelType,class List0Type=List0>
class VirtChannel : public VirtBaseChannel<HalType,List0Type> {
  ChannelType ch;
public:
  VirtChannel () {}
  virtual ~VirtChannel () {}

  operator ChannelType& () { return ch; }

  virtual void setup(Device<HalType,List0Type>* dev,uint8_t number,uint16_t addr) { ch.setup(dev,number,addr); }
  virtual uint16_t size () const { return ch.size(); }
  virtual uint8_t number () const { return ch.number(); }
  virtual uint16_t address () const { return ch.address(); }
  virtual uint8_t peers () const { return ch.peers(); }
  virtual bool changed () const { return ch.changed(); }
  virtual void changed (bool c) { ch.changed(c); }
  virtual void inhibit (bool value) { ch.inhibit(value); }
  virtual bool inhibit () const { return ch.inhibit(); }
  virtual bool aesActive () const { return ch.aesActive(); }
  virtual bool has (const Peer& p) const { return ch.has(p); };
  virtual Peer peerat (uint8_t idx) const { return ch.peerat(idx); }
  virtual bool peer (const Peer& p) { return ch.peer(p); }
  virtual bool peer (const Peer& p1,const Peer& p2) { return ch.peer(p1,p2); }
  virtual bool deletepeer (const Peer& p) { return ch.deletepeer(p); }
  virtual void firstinit () { ch.firstinit(); }

#ifndef SENSOR_ONLY
  virtual void stop () { ch.stop(); };
  virtual bool process (const ActionSetMsg& msg) { return ch.process(msg); }
  virtual bool process (const ActionCommandMsg& msg) { return ch.process(msg); }
  virtual bool process (const RemoteEventMsg& msg) { return ch.process(msg); }
  virtual bool process (const SensorEventMsg& msg) { return ch.process(msg); }
#endif
  virtual uint8_t status () { return ch.status(); }
  virtual uint8_t flags () { return ch.flags(); }

  virtual void patchStatus (Message& msg) { ch.patchStatus(msg); }
  virtual void configChanged () { ch.configChanged(); }

  virtual GenericList getList1 () const { return ch.getList1(); }
  virtual GenericList getList2 () const { return ch.getList2(); }
  virtual GenericList getList3 (const Peer& p) const { return ch.getList3(p); }
  virtual GenericList getList4 (const Peer& p) const { return ch.getList4(p); }
  virtual bool hasList2 () const { return ChannelType::hasList2(); }
  virtual bool hasList3 () const { return ChannelType::hasList3(); }
  virtual bool hasList4 () const { return ChannelType::hasList4(); }
};

#define channelISR(chan,pin,mode,type) class __##pin##ISRHandler { \
    public: \
    static void isr () { chan.handleISR(); } \
  }; \
  pinMode(pin,mode); \
  enableInterrupt(pin,__##pin##ISRHandler::isr,type);


}

#endif