/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */





#include "PdxType.hpp"
#include "PdxHelper.hpp"
#include "PdxTypeRegistry.hpp"
#include "../Log.hpp"
#include <msclr/lock.h>
#include "PdxWrapper.hpp"
#include "../Cache.hpp"
using namespace System;

namespace Apache
{
  namespace Geode
  {
    namespace Client
    {
      namespace Internal
      {
        void PdxType::AddFixedLengthTypeField(String^ fieldName, String^ className,
                                              PdxFieldTypes typeId, Int32 size)
        {
          int current = m_pdxFieldTypes->Count;
          PdxFieldType^ pfType = gcnew PdxFieldType(fieldName, className, (Byte)typeId,
                                                    current/*field index*/,
                                                    false, size, 0/*var len field idx*/);
          m_pdxFieldTypes->Add(pfType);
          //this will make sure one can't add same field name
          m_fieldNameVsPdxType->Add(fieldName, pfType);
        }

        void PdxType::AddVariableLengthTypeField(String^ fieldName, String^ className,
                                                 PdxFieldTypes typeId)
        {
          //we don't store offset of first var len field, this is the purpose of following check
          if (m_isVarLenFieldAdded)
            m_varLenFieldIdx++;//it initial value is zero so variable length field idx start with zero

          m_numberOfVarLenFields++;

          m_isVarLenFieldAdded = true;

          int current = m_pdxFieldTypes->Count;
          PdxFieldType^ pfType = gcnew PdxFieldType(fieldName, className, (Byte)typeId,
                                                    current, true, -1, m_varLenFieldIdx);
          m_pdxFieldTypes->Add(pfType);
          //this will make sure one can't add same field name
          m_fieldNameVsPdxType->Add(fieldName, pfType);
        }

        void PdxType::ToData(DataOutput^ output)
        {
          //defaulf java Dataserializable require this
          output->WriteByte(static_cast<int8_t>(DSCode::DataSerializable));
          output->WriteByte(static_cast<int8_t>(DSCode::Class));
          output->WriteObject((Object^)m_javaPdxClass);

          //pdx type
          output->WriteString(m_className);
          output->WriteBoolean(m_noJavaClass);
          output->WriteInt32(m_geodeTypeId);
          output->WriteInt32(m_varLenFieldIdx);

          output->WriteArrayLen(m_pdxFieldTypes->Count);

          for (int i = 0; i < m_pdxFieldTypes->Count; i++)
          {
            m_pdxFieldTypes[i]->ToData(output);
          }
        }

        void PdxType::FromData(DataInput^ input)
        {
          //defaulf java Dataserializable require this
          Byte val = input->ReadByte();//DS
          Byte val1 = input->ReadByte();//class
          input->ReadObject();//classname

          m_className = input->ReadString();
          //Object^ pdxObject = Serializable::GetPdxType(m_className);
          // m_pdxDomainType = pdxObject->GetType();

          /*PdxWrapper^ pdxWrapper = dynamic_cast<PdxWrapper^>(pdxObject);
          if(pdxWrapper == nullptr)
          m_pdxDomainType = pdxObject->GetType();
          else
          m_pdxDomainType = pdxWrapper->GetObject()->GetType();*/
          //Log::Debug("PdxType::FromData " + m_className);
          m_noJavaClass = input->ReadBoolean();
          m_geodeTypeId = input->ReadInt32();
          m_varLenFieldIdx = input->ReadInt32();

          int len = input->ReadArrayLen();

          bool foundVarLenType = false;
          for (int i = 0; i < len; i++)
          {
            PdxFieldType^ pft = gcnew PdxFieldType();
            pft->FromData(input);

            m_pdxFieldTypes->Add(pft);

            if (pft->IsVariableLengthType == true)
              foundVarLenType = true;
          }

          //as m_varLenFieldIdx starts with 0
          if (m_varLenFieldIdx != 0)
            m_numberOfVarLenFields = m_varLenFieldIdx + 1;
          else if (foundVarLenType)
            m_numberOfVarLenFields = 1;

          InitializeType(input->Cache);
        }

        void PdxType::InitializeType(Cache^ cache)
        {
          initRemoteToLocal(cache);//for writing
          initLocalToRemote(cache);//for reading

          generatePositionMap();

        }

        Int32 PdxType::GetFieldPosition(String^ fieldName, System::Byte* offsetPosition, Int32 offsetSize, Int32 pdxStreamlen)
        {
          PdxFieldType^ pft = nullptr;
          m_fieldNameVsPdxType->TryGetValue(fieldName, pft);

          if (pft != nullptr)
          {
            if (pft->IsVariableLengthType)
              return variableLengthFieldPosition(pft, offsetPosition, offsetSize, pdxStreamlen);
            else
              return fixedLengthFieldPosition(pft, offsetPosition, offsetSize, pdxStreamlen);
          }

          return -1;
        }

        Int32 PdxType::GetFieldPosition(Int32 fieldIdx, System::Byte* offsetPosition, Int32 offsetSize, Int32 pdxStreamlen)
        {
          PdxFieldType^ pft = m_pdxFieldTypes[fieldIdx];

          if (pft != nullptr)
          {
            if (pft->IsVariableLengthType)
              return variableLengthFieldPosition(pft, offsetPosition, offsetSize, pdxStreamlen);
            else
              return fixedLengthFieldPosition(pft, offsetPosition, offsetSize, pdxStreamlen);
          }

          return -1;
        }

        Int32 PdxType::variableLengthFieldPosition(PdxFieldType^ varLenField, System::Byte* offsetPosition, Int32 offsetSize, Int32 pdxStreamlen)
        {
          int seqId = varLenField->SequenceId;

          int offset = varLenField->VarLenOffsetIndex;

          if (offset == -1)
            return /*first var len field*/ varLenField->RelativeOffset;
          else
          {
            //we write offset from behind
            return PdxHelper::ReadInt(offsetPosition + (m_numberOfVarLenFields - offset - 1)*offsetSize, offsetSize);
          }

        }

        Int32 PdxType::fixedLengthFieldPosition(PdxFieldType^ fixLenField, System::Byte* offsetPosition, Int32 offsetSize, Int32 pdxStreamlen)
        {
          int seqId = fixLenField->SequenceId;

          int offset = fixLenField->VarLenOffsetIndex;

          if (fixLenField->RelativeOffset >= 0)
          {
            //starting fields
            return fixLenField->RelativeOffset;
          }
          else if (offset == -1) //Pdx length     
          {
            //there is no var len field so just subtracts relative offset from behind
            return pdxStreamlen + fixLenField->RelativeOffset;
          }
          else
          {
            //need to read offset and then subtract relative offset
            return PdxHelper::ReadInt(offsetPosition +
                                      (m_numberOfVarLenFields - offset - 1)*offsetSize,
                                      offsetSize)
                                      + fixLenField->RelativeOffset;
          }
        }

        PdxType^ PdxType::isLocalTypeContains(PdxType^ otherType)
        {
          if (m_pdxFieldTypes->Count >= otherType->m_pdxFieldTypes->Count)
          {
            return isContains(this, otherType);
          }
          return nullptr;
        }

        PdxType^ PdxType::isRemoteTypeContains(PdxType^ remoteType)
        {
          if (m_pdxFieldTypes->Count <= remoteType->m_pdxFieldTypes->Count)
          {
            return isContains(remoteType, this);
          }
          return nullptr;
        }

        PdxType^ PdxType::MergeVersion(PdxType^ otherVersion)
        {
          int nTotalFields = otherVersion->m_pdxFieldTypes->Count;
          PdxType^ contains = nullptr;

          if (isLocalTypeContains(otherVersion) != nullptr)
            return this;

          if (isRemoteTypeContains(otherVersion) != nullptr)
            return otherVersion;

          //need to create new one, clone of local
          PdxType^ newone = clone();

          int varLenFields = newone->m_numberOfVarLenFields;

          for each(PdxFieldType^ tmp in otherVersion->m_pdxFieldTypes)
          {
            bool found = false;
            for each(PdxFieldType^ tmpNew in newone->m_pdxFieldTypes)
            {
              if (tmpNew->Equals(tmp))
              {
                found = true;
                break;
              }
            }
            if (!found)
            {
              PdxFieldType^ newFt = gcnew PdxFieldType(tmp->FieldName,
                                                       tmp->ClassName,
                                                       tmp->TypeId,
                                                       newone->m_pdxFieldTypes->Count,//sequence id
                                                       tmp->IsVariableLengthType,
                                                       tmp->Size,
                                                       (tmp->IsVariableLengthType ? varLenFields++/*it increase after that*/ : 0));
              newone->m_pdxFieldTypes->Add(newFt);//fieldnameVsPFT will happen after that							
            }
          }

          newone->m_numberOfVarLenFields = varLenFields;
          if (varLenFields > 0)
            newone->m_varLenFieldIdx = varLenFields;

          //need to keep all versions in local version
          //m_otherVersions->Add(newone);
          return newone;
        }

        PdxType^ PdxType::isContains(PdxType^ first, PdxType^ second)
        {
          int j = 0;
          for (int i = 0; i < second->m_pdxFieldTypes->Count; i++)
          {
            PdxFieldType^ secondPdt = second->m_pdxFieldTypes[i];
            bool matched = false;
            for (; j < first->m_pdxFieldTypes->Count; j++)
            {
              PdxFieldType^ firstPdt = first->m_pdxFieldTypes[j];

              if (firstPdt->Equals(secondPdt))
              {
                matched = true;
                break;
              }
            }
            if (!matched)
              return nullptr;
          }
          return first;
        }

        PdxType^ PdxType::clone()
        {
          PdxType^ newone = gcnew PdxType(m_className, false);
          newone->m_geodeTypeId = 0;
          newone->m_numberOfVarLenFields = m_numberOfVarLenFields;

          for each(PdxFieldType^ tmp in m_pdxFieldTypes)
          {
            newone->m_pdxFieldTypes->Add(tmp);
          }
          return newone;
        }

        array<int>^ PdxType::GetLocalToRemoteMap(Cache^ cache)
        {
          if (m_localToRemoteFieldMap != nullptr)
            return m_localToRemoteFieldMap;

          msclr::lock lockInstance(m_lockObj);
          if (m_localToRemoteFieldMap != nullptr)
            return m_localToRemoteFieldMap;
          initLocalToRemote(cache);

          return m_localToRemoteFieldMap;

        }
        array<int>^ PdxType::GetRemoteToLocalMap(Cache^ cache)
        {
          //return m_remoteToLocalFieldMap;

          if (m_remoteToLocalFieldMap != nullptr)
            return m_remoteToLocalFieldMap;

          msclr::lock lockInstance(m_lockObj);
          if (m_remoteToLocalFieldMap != nullptr)
            return m_remoteToLocalFieldMap;
          initRemoteToLocal(cache);

          return m_remoteToLocalFieldMap;
        }

        /*
         * this is write data on remote type(this will always have all fields)
         */
        void PdxType::initRemoteToLocal(Cache^ cache)
        {
          //get local type from type Registry and then map local fields.
          //need to generate static map
          PdxType^ localPdxType = cache->GetPdxTypeRegistry()->GetLocalPdxType(m_className);
          m_numberOfFieldsExtra = 0;

          if (localPdxType != nullptr)
          {
            IList<PdxFieldType^>^ localPdxFields = localPdxType->m_pdxFieldTypes;
            Int32 fieldIdx = 0;

            m_remoteToLocalFieldMap = gcnew array<Int32>(m_pdxFieldTypes->Count);

            for each(PdxFieldType^ remotePdxField in m_pdxFieldTypes)
            {
              bool found = false;

              for each(PdxFieldType^ localPdxfield in localPdxFields)
              {
                if (localPdxfield->Equals(remotePdxField))
                {
                  found = true;
                  m_remoteToLocalFieldMap[fieldIdx++] = 1;//field there in remote type
                  break;
                }
              }

              if (!found)
              {
                //while writing take this from weakhashmap
                //local field is not in remote type
                if (remotePdxField->IsVariableLengthType)
                  m_remoteToLocalFieldMap[fieldIdx++] = -1;//local type don't have this fields
                else
                  m_remoteToLocalFieldMap[fieldIdx++] = -2;//local type don't have this fields
                m_numberOfFieldsExtra++;
              }
            }
          }
        }
        void PdxType::initLocalToRemote(Cache^ cache)
        {
          PdxType^ localPdxType = cache->GetPdxTypeRegistry()->GetLocalPdxType(m_className);

          if (localPdxType != nullptr)
          {
            //Log::Debug("In initLocalToRemote: " + m_geodeTypeId);
            IList<PdxFieldType^>^ localPdxFields = localPdxType->m_pdxFieldTypes;

            Int32 fieldIdx = 0;
            //type which need to read/write should control local type
            m_localToRemoteFieldMap = gcnew array<Int32>(localPdxType->m_pdxFieldTypes->Count);

            for (int i = 0; i < m_localToRemoteFieldMap->Length && i < m_pdxFieldTypes->Count; i++)
            {
              if (localPdxFields[fieldIdx]->Equals(m_pdxFieldTypes[i]))
              {
                //fields are in same order, we can read as it is
                m_localToRemoteFieldMap[fieldIdx++] = -2;
              }
              else
                break;
            }

            for (; fieldIdx < m_localToRemoteFieldMap->Length;)
            {
              PdxFieldType^ localPdxField = localPdxType->m_pdxFieldTypes[fieldIdx];
              bool found = false;

              for each(PdxFieldType^ remotePdxfield in m_pdxFieldTypes)
              {
                if (localPdxField->Equals(remotePdxfield))
                {
                  found = true;
                  //store pdxfield type position to get the offset quickly
                  m_localToRemoteFieldMap[fieldIdx++] = remotePdxfield->SequenceId;
                  break;
                }
              }

              if (!found)
              {
                //local field is not in remote type
                m_localToRemoteFieldMap[fieldIdx++] = -1;
              }
            }
          }
        }

        void PdxType::generatePositionMap()
        {
          //1. all static fields offsets
          //--read next var len offset and subtract offset
          //--if no var len then take length of stream - (len of offsets)
          //2. there is no offser for first var len field

          bool foundVarLen = false;
          int lastVarLenSeqId = 0;
          int prevFixedSizeOffsets = 0;
          //set offsets from back first
          PdxFieldType^ previousField = nullptr;
          for (int i = m_pdxFieldTypes->Count - 1; i >= 0; i--)
          {
            PdxFieldType^ tmpft = m_pdxFieldTypes[i];
            m_fieldNameVsPdxType[tmpft->FieldName] = tmpft;

            if (tmpft->IsVariableLengthType)
            {
              tmpft->VarLenOffsetIndex = tmpft->VarLenFieldIdx;
              tmpft->RelativeOffset = 0;
              foundVarLen = true;
              lastVarLenSeqId = tmpft->VarLenFieldIdx;
            }
            else
            {
              if (foundVarLen)
              {
                tmpft->VarLenOffsetIndex = lastVarLenSeqId;
                //relative offset is subtracted from var len offsets
                tmpft->RelativeOffset = (-tmpft->Size + previousField->RelativeOffset);
              }
              else
              {
                tmpft->VarLenOffsetIndex = -1;//Pdx header length
                //relative offset is subtracted from var len offsets
                tmpft->RelativeOffset = -tmpft->Size;
                if (previousField != nullptr)//boundary condition
                  tmpft->RelativeOffset = (-tmpft->Size + previousField->RelativeOffset);
              }
            }

            previousField = tmpft;
          }

          foundVarLen = false;
          prevFixedSizeOffsets = 0;
          //now do optimization till you don't fine var len
          for (int i = 0; i < m_pdxFieldTypes->Count && !foundVarLen; i++)
          {
            PdxFieldType^ tmpft = m_pdxFieldTypes[i];

            if (tmpft->IsVariableLengthType)
            {
              tmpft->VarLenOffsetIndex = -1;//first var len field
              tmpft->RelativeOffset = prevFixedSizeOffsets;
              foundVarLen = true;
            }
            else
            {
              tmpft->VarLenOffsetIndex = 0;//no need to read offset
              tmpft->RelativeOffset = prevFixedSizeOffsets;
              prevFixedSizeOffsets += tmpft->Size;
            }
          }
        }

        bool PdxType::Equals(Object^ otherObj)
        {
          if (otherObj == nullptr)
            return false;

          PdxType^ ot = dynamic_cast<PdxType^>(otherObj);

          if (ot == nullptr)
            return false;

          if (ot == this)
            return true;

          if (ot->m_pdxFieldTypes->Count != m_pdxFieldTypes->Count)
            return false;

          for (int i = 0; i < m_pdxFieldTypes->Count; i++)
          {
            if (!ot->m_pdxFieldTypes[i]->Equals(m_pdxFieldTypes[i]))
              return false;
          }
          return true;
        }

        Int32 PdxType::GetHashCode()
        {
          int hash = m_cachedHashcode;
          if (hash == 0)
          {
            hash = 1;
            hash = hash * 31 + m_className->GetHashCode();
            for (int i = 0; i < m_pdxFieldTypes->Count; i++)
            {
              hash = hash * 31 + m_pdxFieldTypes[i]->GetHashCode();
            }
            if (hash == 0)
            {
              hash = 1;
            }
            m_cachedHashcode = hash;
          }
          return m_cachedHashcode;
        }
      }  // namespace Internal
    }  // namespace Client
  }  // namespace Geode
}  // namespace Apache
