/*
    _____               __  ___          __            ____        _      __
   / ___/__ ___ _  ___ /  |/  /__  ___  / /_____ __ __/ __/_______(_)__  / /_
  / (_ / _ `/  ' \/ -_) /|_/ / _ \/ _ \/  '_/ -_) // /\ \/ __/ __/ / _ \/ __/
  \___/\_,_/_/_/_/\__/_/  /_/\___/_//_/_/\_\\__/\_, /___/\__/_/ /_/ .__/\__/
                                               /___/             /_/
                                             
  See Copyright Notice in gmMachine.h

*/

#include "gmConfig.h"
#include "gmArrayLib.h"
#include "gmThread.h"
#include "gmMachine.h"
#include "gmHelpers.h"

//
//
// Implementation of array binding
//
//

#if GM_ARRAY_LIB

// Statics and globals
gmType GM_ARRAY = GM_NULL;
gmVariable gmUserArray::m_null;


bool gmUserArray::Construct(gmMachine * a_machine, int a_size)
{
  m_null.Nullify();
  m_array = NULL;
  m_size = 0;
  return Resize(a_machine, a_size);
}


void gmUserArray::Destruct(gmMachine * a_machine)
{
  if(m_array)
  {
    a_machine->Sys_Free(m_array);
    m_array = NULL;
  }
  m_size = 0;
}


bool gmUserArray::Resize(gmMachine * a_machine, int a_size)
{
  if(a_size < 0) a_size = 0;
  int copysize = (a_size > m_size) ? m_size : a_size;
  gmVariable * array = (gmVariable *) a_machine->Sys_Alloc(sizeof(gmVariable) * a_size);
  // copy contents.
  if(m_array)
  {
    memcpy(array, m_array, sizeof(gmVariable) * copysize);
    if(a_size > copysize)
    {
      memset(array + copysize, 0, sizeof(gmVariable) * (a_size - copysize));
    }
    a_machine->Sys_Free(m_array);
  }
  else
  {
    memset(array, 0, sizeof(gmVariable) * a_size);
  }
  m_array = array;
  m_size = a_size;
  return true;
}

#if GM_USE_INCGC
bool gmUserArray::Trace(gmMachine * a_machine, gmGarbageCollector* a_gc, const int a_workLeftToGo, int& a_workDone)
{
  int i;
  for(i = 0; i < m_size; ++i)
  {
    if(m_array[i].IsReference())
    {
      gmObject * object = GM_MOBJECT(a_machine, m_array[i].m_value.m_ref);
      a_gc->GetNextObject(object);
      ++a_workDone;
    }
  }
  
  ++a_workDone;
  return true;
}
#else //GM_USE_INCGC
void gmUserArray::Mark(gmMachine * a_machine, gmuint32 a_mark)
{
  int i;
  for(i = 0; i < m_size; ++i)
  {
    if(m_array[i].IsReference())
    {
      gmObject * object = GM_MOBJECT(a_machine, m_array[i].m_value.m_ref);
      if(object->NeedsMark(a_mark)) object->Mark(a_machine, a_mark);
    }
  }
}
#endif //GM_USE_INCGC


bool gmUserArray::Shift(int a_shift)
{
  if(a_shift < 0) // shift left
  {
    a_shift = -a_shift;
    if(a_shift >= m_size)
    {
      memset(m_array, 0, m_size);
    }
    else
    {
      int size = m_size - a_shift;
      memmove(m_array, m_array + a_shift, sizeof(gmVariable) * size);
      memset(m_array + size, 0, sizeof(gmVariable) * a_shift);
    }
  }
  else if(a_shift > 0) // shift right
  {
    if(a_shift >= m_size)
    {
      memset(m_array, 0, m_size);
    }
    else
    {
      int size = m_size - a_shift;
      memmove(m_array + a_shift, m_array, sizeof(gmVariable) * size);
      memset(m_array, 0, sizeof(gmVariable) * a_shift);
    }
  }
  return true;
}


int gmUserArray::Move(int a_dest, int a_src, int a_size)
{
  int start = a_src;
  int dest = a_dest;
  int size = a_size;

  if(start < 0)
  {
    size += start;
    dest -= start;
    start = 0;
  }

  if(dest < 0)
  {
    size += dest;
    start -= dest;
    dest = 0;
  }

  if(size <= 0) return 0;
  if(start >= m_size) return 0;
  if(dest >= m_size) return 0;
  if((dest + size) < 0) return 0;

  if(start + size > m_size)
  {
    size = m_size - start;
  }
  if(dest + size > m_size)
  {
    size = m_size - dest;
  }
  if(size <= 0) return 0;

  GM_ASSERT(dest >= 0);
  GM_ASSERT(start >= 0);
  GM_ASSERT(start + size <= m_size);
  GM_ASSERT(dest + size <= m_size);

  memmove(m_array + dest, m_array + start, sizeof(gmVariable) * size);

  return size;
}

gmUserArray* gmUserArray_Create(gmMachine* a_machine, int a_size)
{
  gmUserArray * newArray = (gmUserArray *) a_machine->Sys_Alloc(sizeof(gmUserArray));
  newArray->Construct(a_machine, a_size);
  return newArray;
}


// functions

static int GM_CDECL gmfArray(gmThread * a_thread) // size
{
  GM_INT_PARAM(size, 0, 0);
  gmUserArray * array = (gmUserArray *) a_thread->GetMachine()->Sys_Alloc(sizeof(gmUserArray));
  array->Construct(a_thread->GetMachine(), size);
  a_thread->PushNewUser(array, GM_ARRAY);
  return GM_OK;
}

static int GM_CDECL gmfArraySize(gmThread * a_thread) // return size
{
  gmUserObject * arrayObject = a_thread->ThisUserObject();
  GM_ASSERT(arrayObject->m_userType == GM_ARRAY);
  if(arrayObject->m_user)
  {
    gmUserArray * array = (gmUserArray *) arrayObject->m_user;
    a_thread->PushInt(array->Size());
  }
  return GM_OK;
}

static int GM_CDECL gmfArrayResize(gmThread * a_thread) // size
{
  GM_INT_PARAM(size, 0, 0);
  gmUserObject * arrayObject = a_thread->ThisUserObject();
  GM_ASSERT(arrayObject->m_userType == GM_ARRAY);
  if(arrayObject->m_user)
  {
    gmUserArray * array = (gmUserArray *) arrayObject->m_user;
    array->Resize(a_thread->GetMachine(), size);
  }
  return GM_OK;
}

static int GM_CDECL gmfArrayShift(gmThread * a_thread) // shift
{
  GM_CHECK_NUM_PARAMS(1);
  GM_CHECK_INT_PARAM(shift, 0);

  gmUserObject * arrayObject = a_thread->ThisUserObject();
  GM_ASSERT(arrayObject->m_userType == GM_ARRAY);
  if(arrayObject->m_user)
  {
    gmUserArray * array = (gmUserArray *) arrayObject->m_user;
    array->Shift(shift);
  }
  return GM_OK;
}

static int GM_CDECL gmfArrayMove(gmThread * a_thread) // dst, src, size
{
  GM_CHECK_NUM_PARAMS(3);
  GM_CHECK_INT_PARAM(dst, 0);
  GM_CHECK_INT_PARAM(src, 1);
  GM_CHECK_INT_PARAM(size, 2);

  gmUserObject * arrayObject = a_thread->ThisUserObject();
  GM_ASSERT(arrayObject->m_userType == GM_ARRAY);
  if(arrayObject->m_user)
  {
    gmUserArray * array = (gmUserArray *) arrayObject->m_user;
    array->Move(dst, src, size);
  }
  return GM_OK;
}

static void GM_CDECL gmArrayGetInd(gmThread * a_thread, gmVariable * a_operands)
{
  gmUserObject * arrayObject = (gmUserObject *) GM_OBJECT(a_operands->m_value.m_ref);
  GM_ASSERT(arrayObject->m_userType == GM_ARRAY);
  gmUserArray * array = (gmUserArray *) arrayObject->m_user;
  if(a_operands[1].m_type == GM_INT)
  {
    int index = a_operands[1].m_value.m_int;
    *a_operands = array->GetAt(index);
    return;
  }
  a_operands->Nullify();
}

static void GM_CDECL gmArraySetInd(gmThread * a_thread, gmVariable * a_operands)
{
  gmUserObject * arrayObject = (gmUserObject *) GM_OBJECT(a_operands->m_value.m_ref);
  GM_ASSERT(arrayObject->m_userType == GM_ARRAY);
  gmUserArray * array = (gmUserArray *) arrayObject->m_user;
  if(a_operands[1].m_type == GM_INT)
  {
    int index = a_operands[1].m_value.m_int;

#if GM_USE_INCGC
    //Apply write barrier
    gmVariable oldVar = array->GetAt(index);
    if(oldVar.IsReference())
    {
      a_thread->GetMachine()->GetGC()->WriteBarrier((gmObject*)oldVar.m_value.m_ref);
    }
#endif //GM_USE_INCGC

    array->SetAt(index, a_operands[2]);
  }
}

#if GM_USE_INCGC
static void GM_CDECL gmGCDestructArrayUserType(gmMachine * a_machine, gmUserObject* a_object)
{
  if(a_object->m_user) 
  {
    gmUserArray * array = (gmUserArray *) a_object->m_user;
    array->Destruct(a_machine);
    a_machine->Sys_Free(array);
  }
  a_object->m_user = NULL;
}

static bool GM_CDECL gmGCTraceArrayUserType(gmMachine * a_machine, gmUserObject* a_object, gmGarbageCollector* a_gc, const int a_workLeftToGo, int& a_workDone)
{
  if(a_object->m_user) 
  {
    gmUserArray * array = (gmUserArray *) a_object->m_user;
    return array->Trace(a_machine, a_gc, a_workLeftToGo, a_workDone);
  }
  return true;
}

#else //GM_USE_INCGC
static void GM_CDECL gmMarkArrayUserType(gmMachine * a_machine, gmUserObject * a_object, gmuint32 a_mark)
{
  if(a_object->m_user) 
  {
    gmUserArray * array = (gmUserArray *) a_object->m_user;
    array->Mark(a_machine, a_mark);
  }
}

static void GM_CDECL gmGCArrayUserType(gmMachine * a_machine, gmUserObject * a_object, gmuint32 a_mark)
{
  if(a_object->m_user) 
  {
    gmUserArray * array = (gmUserArray *) a_object->m_user;
    array->Destruct(a_machine);
    a_machine->Sys_Free(array);
  }
  a_object->m_user = NULL;
}
#endif //GM_USE_INCGC

// libs

static gmFunctionEntry s_arrayLib[] = 
{ 
  /*gm
    \lib gm
  */
  /*gm
    \function array
    \brief array will create a fixed size array object
    \param int size optional (0)
    \return array
  */
  {"array", gmfArray},
};

static gmFunctionEntry s_arrayTypeLib[] = 
{ 
  /*gm
    \lib array
  */
  /*gm
    \function Size
    \brief Size will return the current size of the fixed array
    \return int array size
  */
  {"Size", gmfArraySize},
  /*gm
    \function Resize
    \brief Resize will resize the array to a new size
    \param int size optional (0)
    \return null
  */
  {"Resize", gmfArrayResize},
  /*gm
    \function Shift
    \brief Shift will shift slide the array elements by a delta, nulls are shifted in
    \param int delta
    \return null
  */
  {"Shift", gmfArrayShift},
  /*gm
    \function Move
    \brief Move will perform a non destructive move on the array
    \param int dst
    \param int src
    \param int size
    \return null
  */
  {"Move", gmfArrayMove},
};

void gmBindArrayLib(gmMachine * a_machine)
{
  gmUserArray::m_null.Nullify(); //Init static null

  a_machine->RegisterLibrary(s_arrayLib, sizeof(s_arrayLib) / sizeof(s_arrayLib[0]));
  GM_ARRAY = a_machine->CreateUserType("array");
  a_machine->RegisterTypeLibrary(GM_ARRAY, s_arrayTypeLib, sizeof(s_arrayTypeLib) / sizeof(s_arrayTypeLib[0]));
#if GM_USE_INCGC
  a_machine->RegisterUserCallbacks(GM_ARRAY, gmGCTraceArrayUserType, gmGCDestructArrayUserType);
#else //GM_USE_INCGC
  a_machine->RegisterUserCallbacks(GM_ARRAY, gmMarkArrayUserType, gmGCArrayUserType);
#endif //GM_USE_INCGC
  a_machine->RegisterTypeOperator(GM_ARRAY, O_GETIND, NULL, gmArrayGetInd);
  a_machine->RegisterTypeOperator(GM_ARRAY, O_SETIND, NULL, gmArraySetInd);
}

#endif // GM_ARRAY_LIB
