#ifndef ELEMENT_TUMOR_H
#define ELEMENT_TUMOR_H

#include "Element.h"
#include "EventWindow.h"
#include "ElementTable.h"
#include "itype.h"
#include "P3Atom.h"
#include "Logger.h"
//#include "Element_Aura.h"

namespace MFM {
  #define TUMOR_VERSION 1

  template <class CC>
  class Element_Tumor : public Element<CC>
  {
    typedef typename CC::ATOM_TYPE T;
    typedef typename CC::PARAM_CONFIG P;
    enum {
	   R = P::EVENT_WINDOW_RADIUS,
	   BITS = P::BITS_PER_ATOM,

	   RADIUS_POS = P3Atom<P>::P3_STATE_BITS_POS,
	   RADIUS_LEN = 8,

	   LAYER_POS =RADIUS_POS+RADIUS_LEN,
	   LAYER_LEN = 8,

	   X_LOC_POS = LAYER_POS+LAYER_LEN,
	   X_LOC_LEN = 8,

	   Y_LOC_POS = X_LOC_POS+X_LOC_LEN,
	   Y_LOC_LEN = 8,

	   IDLABEL_POS = Y_LOC_POS+Y_LOC_LEN,
	   IDLABEL_LEN = 8,

	   AURA_POS = IDLABEL_POS+IDLABEL_LEN,
	   AURA_LEN = 8,

	   TIMER_POS = AURA_POS + AURA_LEN,
	   TIMER_LEN = 8,

	   ABSORB_POS = TIMER_POS + TIMER_LEN,
	   ABSORB_LEN = 8
    };

    typedef BitField<BitVector<BITS>, VD::U32, RADIUS_LEN, RADIUS_POS> AFRadius;
    typedef BitField<BitVector<BITS>, VD::U32, LAYER_LEN, LAYER_POS> AFLayer;
    typedef BitField<BitVector<BITS>, VD::U32, X_LOC_LEN, X_LOC_POS> AFXLoc;
    typedef BitField<BitVector<BITS>, VD::U32, Y_LOC_LEN, Y_LOC_POS> AFYLoc;
    typedef BitField<BitVector<BITS>, VD::U32, IDLABEL_LEN, IDLABEL_POS> AFIdLabel;
    typedef BitField<BitVector<BITS>, VD::U32, AURA_LEN, AURA_POS> AFAura;
    typedef BitField<BitVector<BITS>, VD::U32, TIMER_LEN, TIMER_POS> AFTimer;
    typedef BitField<BitVector<BITS>, VD::U32, ABSORB_LEN, ABSORB_POS> AFAbsorb;

    /*******************************
     * Timer: contorl aura-emit
    ********************************/
    u8 GetTimer(const T& us) const
    {
      return AFTimer::Read(this->GetBits(us));
    }

    void SetTimer(T& us, const u8 timer) const
    {
      AFTimer::Write(this->GetBits(us), timer);
    }
    bool DecrementTimer(T& us) const
    {
      u32 timerVal = GetTimer(us);
      if(timerVal == 0)
      {
	timerVal = 1;
      }
      SetTimer(us, timerVal - 1);

      return timerVal == 1;
    }
    /*******************************
     * ID#: 0-200:good / 201-256 bad
    ********************************/
    u8 GetIDLabel(const T& us) const
    {
      return AFIdLabel::Read(this->GetBits(us));
    }

    void SetIDLabel(T& us, const u8 idd) const
    {
      AFIdLabel::Write(this->GetBits(us), idd);
    }
    /*******************************
     * radius: set cell volume
    ********************************/
    u8 GetRadius(const T& us) const
    {
      return AFRadius::Read(this->GetBits(us));
    }

    void SetRadius(T& us, const u8 radius) const
    {
      AFRadius::Write(this->GetBits(us), radius);
    }
    /*******************************
     * layer: how far from center
    ********************************/
    u8 GetLayer(const T& us) const
    {
      return AFLayer::Read(this->GetBits(us));
    }

    void SetLayer(T& us, const u8 layer) const
    {
      AFLayer::Write(this->GetBits(us), layer);
    }
    /*******************************
     * cordinates X and Y
    ********************************/
    s8 GetXLoc(const T& us) const
    {
      return (s8)AFXLoc::Read(this->GetBits(us));
    }

    void SetXLoc(T& us, const s8 xloc) const
    {
      AFXLoc::Write(this->GetBits(us), xloc);
    }

    s8 GetYLoc(const T& us) const
    {
      return AFYLoc::Read(this->GetBits(us));
    }

    void SetYLoc(T& us, const s8 timer) const
    {
      AFYLoc::Write(this->GetBits(us), timer);
    }

    void SetLoc(T& us, const SPoint& loc) const
    {
      SetXLoc(us, (s8)loc.GetX());
      SetYLoc(us, (s8)loc.GetY());
    }

    void FillLoc(SPoint& pt, const T& us) const
    {
      pt.Set(GetXLoc(us), GetYLoc(us));
    }

  private:
    ElementParameterS32<CC> m_auraDense;
    ElementParameterS32<CC> m_auraLife;

  public:
    static Element_Tumor THE_INSTANCE;
    static const u32 TYPE()
    {
      return THE_INSTANCE.GetType();
    }


    Element_Tumor()
      : Element<CC>(MFM_UUID_FOR("Provider", TUMOR_VERSION)),
	m_auraDense(this, "auraDense", "Maximum Aura Dense", "Maximum Aura Dense",
		  0, 8, 10),
	m_auraLife(this, "auraLife", "Maximum Aura Life", "Maximum Aura Life",
		  0, 7, 100)
    {
      Element<CC>::SetAtomicSymbol("Pv");
      Element<CC>::SetName("Provider");
    }

    virtual const T & GetDefaultAtom() const
    {
      static T defaultAtom(TYPE(),0,0,0);
      SetIDLabel(defaultAtom,0);
      SetLayer(defaultAtom,0);//Layer always starts from 0
      SetTimer(defaultAtom,20);
      return defaultAtom;
    }

    virtual u32 DefaultPhysicsColor() const
    {
      return 0xffff8300;
    }

    virtual u32 DefaultLowlightColor() const
    {
      return 0x00000000;
    }
    /*****************************
     *change color for each layer
     *****************************/
    virtual u32 LocalPhysicsColor(const T& atom, u32 selector) const
    {
      u32 myColor=0xffff8300;/*T[0]*/
      switch(GetLayer(atom)){
      case 0:
	//myColor = 0xffffffff;
	//break;/*G[0]*/
      case 1: //myColor = 0xffbbbbaa;break;
      case 2: //myColor = 0xff404040;break;
      case 3: //myColor = 0xff505050;break;
      case 4: //myColor = 0xff505050;break;
    /*case 5: myColor = 0xff202030;break;
      case 6: myColor = 0xff00ff00;break;
      case 7: myColor = 0xff007f00;break;
      case 8: myColor = 0xff00aa00;break;
      case 9: myColor = 0x7f807f80;break;*/
      default: break;
      }
      return myColor;
    }
    virtual u32 PercentMovable(const T& you,
                               const T& me, const SPoint& offset) const
    {
      return 0;
    }
    // Non-diffusable
    virtual u32 Diffusability(EventWindow<CC> & ew, SPoint nowAt, SPoint maybeAt) const
    {
      return nowAt.Equals(maybeAt)?Element<CC>::COMPLETE_DIFFUSABILITY:0;
    }

    virtual const char* GetDescription() const
    {
      return "Short for \"Provider\", These Atoms form a structure "
             "They will Emit Signals.";
    }
    virtual void AppendDescription(const T* atomPtr, OString64& desc) const
    {
      const T& atom = *atomPtr;

      u8 datum = GetIDLabel(atom);
      s8 x = GetXLoc(atom);
      s8 y = GetYLoc(atom);
      /*u8 tm = GetTimer(atom);
      u8 ly = GetLayer(atom);*/
      u8 ab = GetAbsorb(atom);
      if(datum)
      {
        //desc.Printf("id(%d)at(%d,%d)\n tm(%d)ly(%d)hit(%d)", datum,x,y,tm,ly,ab);
        desc.Printf("id(%d)at(%d,%d) hit(%d)", datum,x,y,ab);
      }
      else
      {
        desc.Printf("Provier: INVALID");
      }
    }
    /*******************************
     * Absorb: count absorb
    ********************************/
    u8 GetAbsorb(const T& us) const
    {
      return AFAbsorb::Read(this->GetBits(us));
    }

    void SetAbsorb(T& us, const u8 timer) const
    {
      AFAbsorb::Write(this->GetBits(us), timer);
    }
    /*********************************************
     * Can Grow  only the inner layer
     *********************************************/
    bool TestCanGrow(const T& us,Random & random) const
    {

      if(GetLayer(us)==GetRadius(us)){
	return false;
      }
      return true;
    }
    /*********************************************
     * Can Aura: only the outmost layer
     *********************************************/
    bool TestCanAura(const T& us) const
    {
      if(GetLayer(us)==GetRadius(us)){
	if (m_auraDense.GetValue()>0){
	  return true;
	}
      }
      return false;
    }
    /*********************************************
     * Divide New Cell: only the out most layer
     *********************************************/
    bool TestCanDivide(const T& us,Random & random) const
    {
      if((GetLayer(us)==0)){
	return false;//forTEST
      }
      return false;
    }
    /*********************************************
     * DecrementSelf: decrement timer
     *********************************************/
    void DecrementSelf(EventWindow<CC>& window) const{
      T us = window.GetCenterAtom();
      DecrementTimer(us);
      window.SetCenterAtom(us);
    }

    /*********************************************
     * SetupSelf: choose random IDnumber,radius,
     *********************************************/
    void SetUpSelf(EventWindow<CC>& window) const{
      T us = window.GetCenterAtom();
      Random & random = window.GetRandom();
      u8 randomID = (u8)random.Between(1,256);
      u8 randomRadius = (u8)random.Between(2,2);

      SetIDLabel(us,randomID);
      SetRadius(us,randomRadius);
      SetLayer(us,0);
      SetXLoc(us,0);
      SetYLoc(us,0);
      SetTimer(us,5);
      SetAbsorb(us,0);
      window.SetCenterAtom(us);
    }
    /*********************************************
     * Grow: myLoc/possibleLoc absolute X,Y to core
     *       possiblePt relative X,y to eventwindow
     *********************************************/
    virtual void Grow(EventWindow<CC>& window) const;
    /*********************************************
     * Emit Aura: Tumor emit aura after certain time
     *********************************************/
    virtual void EmitAura(EventWindow<CC>& window) const;

    virtual void Behavior(EventWindow<CC>& window) const
    {
      Random & random = window.GetRandom();
      const T& me = window.GetCenterAtom();
      /**********************
       *step0:setupMyself
       **********************/
      if (GetIDLabel(me)==0){
	SetUpSelf(window);
      }
      /**********************
       *step1:DecrementTimer
       **********************/
      DecrementSelf(window);

      const T& us = window.GetCenterAtom();
      //LOG.Debug("i am layer(%d) at(%d,%d) id(%d) radius(%d) maxage(%d) timer(%d)",GetLayer(us),GetXLoc(us),GetYLoc(us),GetIDLabel(us),GetRadius(us),50*GetRadius(us),GetTimer(us));

      bool canGrow = true;
      bool canDivide = false;
      bool canAura = false;
      SPoint possiblePt;
      /**********************
       *step2:Grow
       **********************/

      canGrow = TestCanGrow(us,random);
      canDivide = TestCanDivide(us,random);
      canAura=TestCanAura(us);
      //LOG.Debug("me(%d,%d) cangrow(%d),canaura(%d)",GetXLoc(us),GetYLoc(us),canGrow,canAura);
      if(canGrow)
      {
	Grow(window);
      }

      if(canDivide){
	//DivideCell(window);
      }
      /**********************
       *step3:Aura
       **********************/
      if(canAura){
	EmitAura(window);
      }
    }
  };

  template <class CC>
  Element_Tumor<CC> Element_Tumor<CC>::THE_INSTANCE;
}
#include "Element_Tumor.tcc"
#endif //ELEMENT_TUMOR_H
