#include "pxt.h"
#include <map>

MicroBit uBit;

namespace pxt {
    int incr(uint32_t e)
    {
      if (e) {
        if (hasVTable(e))
          ((RefObject*)e)->ref();
        else
          ((RefCounted*)e)->incr();
      }
      return e;
    }

    void decr(uint32_t e)
    {
      if (e) {
        if (hasVTable(e))
          ((RefObject*)e)->unref();
        else
          ((RefCounted*)e)->decr();
      }
    }

    Action mkAction(int reflen, int totallen, int startptr)
    {
      check(0 <= reflen && reflen <= totallen, ERR_SIZE, 1);
      check(reflen <= totallen && totallen <= 255, ERR_SIZE, 2);
      check(bytecode[startptr] == 0xffff, ERR_INVALID_BINARY_HEADER, 3);
      check(bytecode[startptr + 1] == 0, ERR_INVALID_BINARY_HEADER, 4);

      uint32_t tmp = (uint32_t)&bytecode[startptr];

      if (totallen == 0) {
        return tmp; // no closure needed
      }

      void *ptr = ::operator new(sizeof(RefAction) + totallen * sizeof(uint32_t));
      RefAction *r = new (ptr) RefAction();
      r->len = totallen;
      r->reflen = reflen;
      r->func = (ActionCB)((tmp + 4) | 1);
      memset(r->fields, 0, r->len * sizeof(uint32_t));

      return (Action)r;
    }

    uint32_t runAction3(Action a, int arg0, int arg1, int arg2)
    {
      if (hasVTable(a))
        return ((RefAction*)a)->runCore(arg0, arg1, arg2);
      else {
        check(*(uint16_t*)a == 0xffff, ERR_INVALID_BINARY_HEADER, 4);
        return ((ActionCB)((a + 4) | 1))(NULL, arg0, arg1, arg2);
      }
    }

    uint32_t runAction2(Action a, int arg0, int arg1)
    {
      return runAction3(a, arg0, arg1, 0);
    }

    uint32_t runAction1(Action a, int arg0)
    {
      return runAction3(a, arg0, 0, 0);
    }

    uint32_t runAction0(Action a)
    {
      return runAction3(a, 0, 0, 0);
    }

    RefRecord* mkClassInstance(int vtableOffset)
    {
      VTable *vtable = (VTable*)&bytecode[vtableOffset];

      intcheck(vtable->methods[0] == &RefRecord_destroy, ERR_SIZE, 3);
      intcheck(vtable->methods[1] == &RefRecord_print, ERR_SIZE, 4);

      void *ptr = ::operator new(vtable->numbytes);
      RefRecord *r = new (ptr) RefRecord(PXT_VTABLE_TO_INT(vtable));
      memset(r->fields, 0, vtable->numbytes - sizeof(RefRecord));
      return r;
    }

    uint32_t RefRecord::ld(int idx)
    {
      //intcheck((reflen == 255 ? 0 : reflen) <= idx && idx < len, ERR_OUT_OF_BOUNDS, 1);
      return fields[idx];
    }

    uint32_t RefRecord::ldref(int idx)
    {
      //printf("LD %p len=%d reflen=%d idx=%d\n", this, len, reflen, idx);
      //intcheck(0 <= idx && idx < reflen, ERR_OUT_OF_BOUNDS, 2);
      uint32_t tmp = fields[idx];
      incr(tmp);
      return tmp;
    }

    void RefRecord::st(int idx, uint32_t v)
    {
      //intcheck((reflen == 255 ? 0 : reflen) <= idx && idx < len, ERR_OUT_OF_BOUNDS, 3);
      fields[idx] = v;
    }

    void RefRecord::stref(int idx, uint32_t v)
    {
      //printf("ST %p len=%d reflen=%d idx=%d\n", this, len, reflen, idx);
      //intcheck(0 <= idx && idx < reflen, ERR_OUT_OF_BOUNDS, 4);
      decr(fields[idx]);
      fields[idx] = v;
    }

    void RefObject::destroy() {
      ((RefObjectMethod)getVTable()->methods[0])(this);
    }

    void RefObject::print() {
      ((RefObjectMethod)getVTable()->methods[1])(this);
    }

    void RefRecord_destroy(RefRecord *r) {
        auto tbl = r->getVTable();
        uint8_t *refmask = (uint8_t*)&tbl->methods[tbl->userdata & 0xff];
        int len = (tbl->numbytes >> 2) - 1;
        for (int i = 0; i < len; ++i) {
          if (refmask[i]) decr(r->fields[i]);
          r->fields[i] = 0;
        }
        //RefRecord is allocated using placement new
        r->~RefRecord();
        ::operator delete(r);
    }

    void RefRecord_print(RefRecord *r)
    {
      printf("RefRecord %p r=%d size=%d bytes\n", r, r->refcnt, r->getVTable()->numbytes);
    }

    uint32_t Segment::get(uint32_t i)
    {
#ifdef DEBUG_BUILD
      printf("In Segment::get index:%u\n", i);
      this->print();
#endif            
      
      if (i < length)
      {
        if (data[i] != Segment::MissingValue)
        {
          return data[i];          
        }
        error(ERR_MISSING_VALUE);
        return 0;
      }
      error(ERR_OUT_OF_BOUNDS);
      return 0;
    }

    void Segment::set(uint32_t i, uint32_t value) 
    {
        if (i < size)
        {
          data[i] = value;
        }
        else if (i < Segment::MaxSize)
        {
          growByMin(i + 1);
          data[i] = value;
        }
        if (length <= i)
        {
           length = i + 1; 
        }        

#ifdef DEBUG_BUILD
        printf("In Segment::set\n");
        this->print();
#endif            
        
        return;
    }      

    uint16_t Segment::growthFactor(uint16_t size)
    {
      if (size == 0)
      {
        return 4;
      }
      if (size < 64)
      {
        return size * 2; // Double
      }
      if (size < 512)
      {
        return size * 5/3; //Grow by 1.66 rate
      }
      return size + 256; //Grow by constant rate
    }

    void Segment::growByMin(uint16_t minSize)
    {
      growBy(max(minSize, growthFactor(size)));
    }

    void Segment::growBy(uint16_t newSize) 
    {
#ifdef DEBUG_BUILD
         printf("growBy: %d\n", newSize);
         this->print();
#endif      
      if (size < newSize)
      {
         //this will throw if unable to allocate
         uint32_t *tmp = (uint32_t*)(::operator new(newSize * sizeof(uint32_t)));

         //Copy existing data
         if (size)
         {
           memcpy(tmp, data, size * sizeof(uint32_t));
         }

         //fill the rest with missing values;
         for(uint16_t i = size; i < newSize; i++)
         {
           tmp[i] = Segment::MissingValue;
         }

         //free older segment;
         ::operator delete(data); 

         data = tmp; 
         size = newSize;

#ifdef DEBUG_BUILD
         printf("growBy - after reallocation\n");
         this->print();
#endif         
         
      }
      //else { no shrinking yet; }
      return;
    }

    void Segment::ensure(uint16_t newSize)
    {
      if (newSize < size)
      {
        return;
      }
      growByMin(newSize);
    }

    void Segment::setLength(uint32_t newLength)
    {
      if (newLength > size)
      {
        ensure(length);        
      }
      length = newLength;
      return;
    }

    void Segment::push(uint32_t value) 
    { 
      this->set(length, value);
    }
    
    uint32_t Segment::pop() 
    {
#ifdef DEBUG_BUILD
      printf("In Segment::pop\n");
      this->print();
#endif            
      
      if (length > 0)
      {
        uint32_t value = data[length];
        data[length] = Segment::MissingValue;
        --length;
        return value;
      }
      error(ERR_OUT_OF_BOUNDS);      
      return 0;
    }

    //this function removes an element at index i and shifts the rest of the elements to
    //left to fill the gap   
    uint32_t Segment::remove(uint32_t i) 
    {
#ifdef DEBUG_BUILD
      printf("In Segment::remove index:%u\n", i);
      this->print();
#endif                  
      if (i < length)
      {
        //value to return
        uint32_t ret = data[i];
        if (i + 1 < length)
        {
          //Move the rest of the elements to fill in the gap.
          memmove(data + i, data + i + 1, (length - i - 1) * sizeof(uint32_t));
        }
        length--;        
        data[length] = Segment::MissingValue;        
#ifdef DEBUG_BUILD
        printf("After Segment::remove index:%u\n", i);
        this->print();
#endif  
        return ret;
      }
      error(ERR_OUT_OF_BOUNDS);
      return 0;
    }

    //this function inserts element value at index i by shifting the rest of the elements right.     
    void Segment::insert(uint32_t i, uint32_t value) 
    {
#ifdef DEBUG_BUILD
      printf("In Segment::insert index:%u value:%u\n", i, value);
      this->print();
#endif                  

      if (i < length)
      {
        ensure(length + 1);
        if (i + 1 < length)
        {
          //Move the rest of the elements to fill in the gap.
          memmove(data + i + 1, data + i, (length - i) * sizeof(uint32_t));
        }

        data[i] = value;        
        length++;
      }
      else
      {
        //This is insert beyond the length, just call set which will adjust the length
        set(i, value);
      }
#ifdef DEBUG_BUILD
      printf("After Segment::insert index:%u\n", i);
      this->print();
#endif                   
    }

    void Segment::print()
    {
      printf("Segment: %x, length: %u, size: %u\n", data, (uint)length, (uint)size);
      for(uint i = 0; i < size; i++)
      {
        printf("%d ",(uint)data[i]);
      }
      printf("\n");
    }

    bool Segment::isValidIndex(uint32_t i)
    {
      if (i > length || data[i] == Segment::MissingValue)
      {
        return false;
      }
      return true;
    }

    bool Segment::getNextValidIndex(uint32_t i, uint32_t *result)
    {
      while (i < length)
      {
        if (data[i] != Segment::MissingValue)
        {
           *result = i;

#ifdef DEBUG_BUILD
           printf("In Segment::getNextValidIndex result=%u\n",i);
           this->print();
#endif                  
           return true;
        }
        i++;
      }
      return false;
    }

    void Segment::destroy()
    {
#ifdef DEBUG_BUILD
      printf("In Segment::destroy\n");
      this->print();
#endif          
      length = size = 0;
      ::operator delete(data);
      data = nullptr;
    }

    void RefCollection::push(uint32_t x) 
    {
      if (isRef()) incr(x);
      head.push(x);
    }

    uint32_t RefCollection::pop() 
    {
      uint32_t ret = head.pop();
      if (isRef())
      {
        incr(ret);  
      } 
      return ret;
    }

    uint32_t RefCollection::getAt(int i) 
    {
      if (head.isValidIndex(i)) 
      {
        uint32_t tmp = head.get(i);
        if (isRef())
        {
          incr(tmp);
        }
        return tmp;
      }
      else 
      {
        error(ERR_OUT_OF_BOUNDS);
        return 0;
      }
    }

    uint32_t RefCollection::removeAt(int i) 
    {
      if (!head.isValidIndex((uint32_t)i))
      {
        error(ERR_OUT_OF_BOUNDS);
        return 0;
      }
      if (isRef())
      {
        decr(head.get(i));
      } 
      return head.remove(i);
    }

    void RefCollection::insertAt(int i, uint32_t value) 
    {
      if (i < length())
      {
        head.insert(i, value);
        if (isRef())
        {
          incr(value);
        } 
      }
      else
      {
        error(ERR_OUT_OF_BOUNDS);
      }
    }

    void RefCollection::setAt(int i, uint32_t value) 
    {
      if (isRef()) 
      {
        if (head.isValidIndex((uint32_t)i))
        {
          decr(head.get(i));
        }
        incr(value);
      }
      head.set(i, value);
    }

    int RefCollection::indexOf(uint32_t x, int start) 
    {
      if (isString()) 
      {
        StringData *xx = (StringData*)x;
        uint32_t i = start;
        while(head.getNextValidIndex(start, &i))
        {
          StringData *ee = (StringData*)head.get(i);
          if (xx->len == ee->len && memcmp(xx->data, ee->data, xx->len) == 0)
          {
            return (int)i;
          }
          start = i;
        }
      } 
      else 
      {
        uint32_t i = start;
        while(head.getNextValidIndex(start, &i))
        {
          if (head.get(i) == x)
          {
            return (int)i;
          }
          start = i;
        }
      }

      return -1;
    }

    int RefCollection::removeElement(uint32_t x) 
    {
      int idx = indexOf(x, 0);
      if (idx >= 0) {
        removeAt(idx);
        return 1;
      }
      return 0;
    }

    namespace Coll0 {
      PXT_VTABLE_BEGIN(RefCollection, 0, 0)
      PXT_VTABLE_END
    }
    namespace Coll1 {
      PXT_VTABLE_BEGIN(RefCollection, 1, 0)
      PXT_VTABLE_END
    }
    namespace Coll3 {
      PXT_VTABLE_BEGIN(RefCollection, 3, 0)
      PXT_VTABLE_END
    }

    RefCollection::RefCollection(uint16_t flags) : RefObject(0) {
      switch (flags) {
        case 0:
          vtable = PXT_VTABLE_TO_INT(&Coll0::RefCollection_vtable);
          break;
        case 1:
          vtable = PXT_VTABLE_TO_INT(&Coll1::RefCollection_vtable);
          break;
        case 3:
          vtable = PXT_VTABLE_TO_INT(&Coll3::RefCollection_vtable);
          break;
        default:
          error(ERR_SIZE);
          break;
      }
    }

    void RefCollection::destroy()
    {
      if (this->isRef())
      {
        uint32_t start = 0;
        uint32_t i = 0;
        while(head.getNextValidIndex(start, &i))
        {
          decr(this->head.get(i));
          start = i;
        }
      }
      this->head.destroy();
      delete this;
    }

    void RefCollection::print()
    {
      printf("RefCollection %p r=%d flags=%d size=%d\n", this, refcnt, getFlags(), head.getLength());
      head.print();
    }

    PXT_VTABLE_CTOR(RefAction) {}

    // fields[] contain captured locals
    void RefAction::destroy()
    {
      for (int i = 0; i < this->reflen; ++i) {
        decr(fields[i]);
        fields[i] = 0;
      }
      //RefAction is allocated using placement new
      this->~RefAction();
      ::operator delete(this);
    }

    void RefAction::print()
    {
      printf("RefAction %p r=%d pc=0x%lx size=%d (%d refs)\n", this, refcnt, (const uint8_t*)func - (const uint8_t*)bytecode, len, reflen);
    }

    void RefLocal::print()
    {
      printf("RefLocal %p r=%d v=%d\n", this, refcnt, v);
    }

    void RefLocal::destroy()
    {
      delete this;
    }

    PXT_VTABLE_CTOR(RefLocal) {
      v = 0;
    }

    PXT_VTABLE_CTOR(RefRefLocal) {
      v = 0;
    }

    void RefRefLocal::print()
    {
      printf("RefRefLocal %p r=%d v=%p\n", this, refcnt, (void*)v);
    }

    void RefRefLocal::destroy()
    {
      decr(v);
      delete this;
    }

    PXT_VTABLE_BEGIN(RefMap, 0, RefMapMarker)
    PXT_VTABLE_END
    RefMap::RefMap() : PXT_VTABLE_INIT(RefMap) {}

    void RefMap::destroy() {
      for (unsigned i = 0; i < data.size(); ++i) {
        if (data[i].key & 1) {
          decr(data[i].val);
        }
        data[i].val = 0;
      }
      data.resize(0);
      delete this;
    }

    int RefMap::findIdx(uint32_t key) {
      for (unsigned i = 0; i < data.size(); ++i) {
        if (data[i].key >> 1 == key)
          return i;
      }
      return -1;
    }

    void RefMap::print()
    {
      printf("RefMap %p r=%d size=%d\n", this, refcnt, data.size());
    }


#ifdef DEBUG_MEMLEAKS
  std::set<RefObject*> allptrs;
  void debugMemLeaks()
  {
    printf("LIVE POINTERS:\n");
    for(std::set<RefObject*>::iterator itr = allptrs.begin();itr!=allptrs.end();itr++)
    {
      (*itr)->print();
    }
    printf("\n");
  }
#else
  void debugMemLeaks() {}
#endif


    // ---------------------------------------------------------------------------
    // An adapter for the API expected by the run-time.
    // ---------------------------------------------------------------------------

    map<pair<int, int>, Action> handlersMap;

    MicroBitEvent lastEvent;

    // We have the invariant that if [dispatchEvent] is registered against the DAL
    // for a given event, then [handlersMap] contains a valid entry for that
    // event.
    void dispatchEvent(MicroBitEvent e) {

      lastEvent = e;

      Action curr = handlersMap[{ e.source, e.value }];
      if (curr)
        runAction1(curr, e.value);

      curr = handlersMap[{ e.source, MICROBIT_EVT_ANY }];
      if (curr)
        runAction1(curr, e.value);
    }

    void registerWithDal(int id, int event, Action a) {
      Action prev = handlersMap[{ id, event }];
      if (prev)
        decr(prev);
      else
        uBit.messageBus.listen(id, event, dispatchEvent);
      incr(a);
      handlersMap[{ id, event }] = a;
    }

    void fiberDone(void *a)
    {
      decr((Action)a);
      release_fiber();
    }


    void runInBackground(Action a) {
      if (a != 0) {
        incr(a);
        create_fiber((void(*)(void*))runAction0, (void*)a, fiberDone);
      }
    }


  void error(ERROR code, int subcode)
  {
    printf("Error: %d [%d]\n", code, subcode);
    uBit.panic(42);
  }

  uint16_t *bytecode;
  uint32_t *globals;
  int numGlobals;

  uint32_t *allocate(uint16_t sz)
  {
    uint32_t *arr = new uint32_t[sz];
    memset(arr, 0, sz * 4);
    return arr;
  }

  void checkStr(bool cond, const char *msg)
  {
    if (!cond) {
      while (true) {
        uBit.display.scroll(msg, 100);
        uBit.sleep(100);
      }
    }
  }

  int templateHash()
  {
    return ((int*)bytecode)[4];
  }

  int programHash()
  {
    return ((int*)bytecode)[6];
  }

  int getNumGlobals()
  {
    return bytecode[16];
  }

  void exec_binary(int32_t *pc)
  {
    // XXX re-enable once the calibration code is fixed and [editor/embedded.ts]
    // properly prepends a call to [internal_main].
    // ::touch_develop::internal_main();

    // unique group for radio based on source hash
    // ::touch_develop::micro_bit::radioDefaultGroup = programHash();

    // repeat error 4 times and restart as needed
    microbit_panic_timeout(4);

    int32_t ver = *pc++;
    checkStr(ver == 0x4209, ":( Bad runtime version");

    bytecode = *((uint16_t**)pc++);  // the actual bytecode is here
    globals = allocate(getNumGlobals());

    // just compare the first word
    checkStr(((uint32_t*)bytecode)[0] == 0x923B8E70 &&
             templateHash() == *pc,
             ":( Failed partial flash");

    uint32_t startptr = (uint32_t)bytecode;
    startptr += 48; // header
    startptr |= 1; // Thumb state

    ((uint32_t (*)())startptr)();

#ifdef DEBUG_MEMLEAKS
    pxt::debugMemLeaks();
#endif

    return;
  }

  void start()
  {
    exec_binary((int32_t*)functionsAndBytecode);
  }
}

// vim: ts=2 sw=2 expandtab