/*
 * 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 "PdxLocalWriter.hpp"
#include "PdxHelper.hpp"
#include "PdxTypeRegistry.hpp"
#include "../DataOutput.hpp"
#include "DotNetTypes.hpp"
#include "../Cache.hpp"

using namespace System;

namespace Apache
{
  namespace Geode
  {
    namespace Client
    {

      namespace Internal
      {
        
          PdxLocalWriter::PdxLocalWriter(DataOutput^ dataOutput, PdxType^ pdxType)
          {
            m_dataOutput = dataOutput;
            m_pdxType = pdxType;
            m_currentOffsetIndex = 0;
            m_preserveData = nullptr;
            if (pdxType != nullptr)
              m_pdxClassName = pdxType->PdxClassName;
            //m_pdxDomainType = nullptr;
            initialize();
          }

          PdxLocalWriter::PdxLocalWriter(DataOutput^ dataOutput, PdxType^ pdxType, String^ pdxClassName)
          {
            m_dataOutput = dataOutput;
            m_pdxType = pdxType;
            m_currentOffsetIndex = 0;
            m_preserveData = nullptr;
            // m_pdxDomainType = pdxDomainType;
            m_pdxClassName = pdxClassName;
            initialize();
          }
          
          void PdxLocalWriter::initialize()
          {
            if(m_pdxType != nullptr)
            {
              m_currentOffsetIndex = 0;
              //TODO:: 1 offset below if you can avoid
              m_offsets = gcnew array<int>(m_pdxType->NumberOfVarLenFields);
            }

            //start position, this should start of c++ dataoutput buffer and then use bufferlen
            m_startPosition = m_dataOutput->GetStartBufferPosition();
            //TODO: need to use this carefully
            m_startPositionOffset = static_cast<int>(m_dataOutput->BufferLength);//data has been write
            m_dataOutput->AdvanceCursor(PdxHelper::PdxHeader);//to write pdx header
          }

          void PdxLocalWriter::AddOffset()
          {
            //bufferlength gives lenght which has been writeen to unmanged Dataoutput
            //m_startPositionOffset: from where pdx header length starts
            int bufferLen = static_cast<int>(m_dataOutput->BufferLength - m_startPositionOffset);
            int offset = bufferLen - PdxHelper::PdxHeader/* this needs to subtract*/;

            m_offsets[m_currentOffsetIndex++] = offset;
          }

          void PdxLocalWriter::EndObjectWriting()
          {
            //write header
            WritePdxHeader();
          }

          System::Byte* PdxLocalWriter::GetPdxStream(int& pdxLen)
          {
            System::Byte* stPos = m_dataOutput->GetStartBufferPosition() + m_startPositionOffset;
            int len = PdxHelper::ReadInt32(stPos);
            pdxLen = len;
            //ignore len and typeid
            return m_dataOutput->GetBytes(stPos + 8, len );
          }


          void PdxLocalWriter::WritePdxHeader()
          {
            Int32 len = calculateLenWithOffsets();
            Int32 typeId = m_pdxType->TypeId;

            //GetStartBufferPosition ; if unmanaged dataoutput get change
            System::Byte* starpos = m_dataOutput->GetStartBufferPosition() + m_startPositionOffset;
            PdxHelper::WriteInt32(starpos , len);
            PdxHelper::WriteInt32(starpos + 4, typeId);

            WriteOffsets(len);
          }

          Int32 PdxLocalWriter::calculateLenWithOffsets()
          {
            //int bufferLen = m_dataOutput->GetCursorPdx();
            int bufferLen = static_cast<int>(m_dataOutput->BufferLength - m_startPositionOffset);
            Int32 totalOffsets = 0;
            if(m_offsets->Length > 0)
              totalOffsets = m_offsets->Length -1;//for first var len no need to append offset
            Int32 totalLen = bufferLen - PdxHelper::PdxHeader + totalOffsets;
            

            if(totalLen <= 0xff)//1 byte
              return totalLen;
            else if (totalLen + totalOffsets <= 0xffff)//2 byte
              return totalLen + totalOffsets ;
            else//4 byte
              return totalLen + totalOffsets*3 ;                       
          }

          void PdxLocalWriter::WriteOffsets(Int32 len)
          {
            if(len <= 0xff)
            {
              for(int i = m_offsets->Length-1; i >0 ; i--)
                m_dataOutput->WriteByte((Byte)m_offsets[i]);

            }
            else if(len <= 0xffff)
            {
              for(int i = m_offsets->Length-1; i >0 ; i--)
								m_dataOutput->WriteUInt16((UInt16)m_offsets[i]);
            }
            else
            {
              for(int i = m_offsets->Length-1; i >0 ; i--)
                m_dataOutput->WriteUInt32((UInt32)m_offsets[i]);
            }
          }

          bool PdxLocalWriter::isFieldWritingStarted()
          {
            return true;
          }

          IPdxWriter^ PdxLocalWriter::WriteUnreadFields(IPdxUnreadFields^ unread)
          {
            if(isFieldWritingStarted())
            {
              throw gcnew IllegalStateException("WriteUnreadFields must be called before "
																									+ "any other fields are written.");
            }
           
            if(unread != nullptr)
            {
              m_preserveData = (PdxRemotePreservedData^)unread;

              m_pdxType = m_dataOutput->Cache->GetPdxTypeRegistry()->GetPdxType(m_preserveData->MergedTypeId);
              if(m_pdxType == nullptr)
              {//its local type
                //this needs to fix for IPdxTypemapper
                m_pdxType = GetLocalPdxType(m_pdxClassName);
              }
              m_offsets = gcnew array<int>(m_pdxType->NumberOfVarLenFields);
            }
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteByte( String^ fieldName, SByte value )
          {
						m_dataOutput->WriteSByte(value);
            return this;
          }

          void PdxLocalWriter::WriteByte(Byte byte)
          {
            m_dataOutput->WriteByte(byte);
          }

          IPdxWriter^ PdxLocalWriter::WriteSByte( String^ fieldName, SByte value )
          {
            m_dataOutput->WriteSByte(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteBoolean( String^ fieldName, bool value )
          {
            m_dataOutput->WriteBoolean(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteChar( String^ fieldName, Char value )
          {
            m_dataOutput->WriteChar(value);
            return this;
          }
                       
          IPdxWriter^ PdxLocalWriter::WriteUInt16( String^ fieldName, System::UInt16 value )
          {
            m_dataOutput->WriteUInt16(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteUInt32( String^ fieldName, System::UInt32 value )
          {
            m_dataOutput->WriteUInt32(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteUInt64( String^ fieldName, System::UInt64 value )
          {
            m_dataOutput->WriteUInt64(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteShort( String^ fieldName, System::Int16 value )
          {
            m_dataOutput->WriteInt16(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteInt( String^ fieldName, System::Int32 value )
          {
            m_dataOutput->WriteInt32(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteLong( String^ fieldName, Int64 value )
          {
						m_dataOutput->WriteInt64(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteFloat( String^ fieldName, float value )
          {
            m_dataOutput->WriteFloat(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteDouble( String^ fieldName, double value )
          {
            m_dataOutput->WriteDouble(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteString( String^ fieldName, String^ value )
          {
            AddOffset();
            m_dataOutput->WriteString(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteUTFHuge( String^ fieldName, String^ value )
          {
            AddOffset();
            m_dataOutput->WriteUTFHuge(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteASCIIHuge( String^ fieldName, String^ value )
          {
            AddOffset();
            m_dataOutput->WriteASCIIHuge(value);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteObject( String^ fieldName, Object^ obj )
          {
            AddOffset();
            m_dataOutput->WriteObject(obj);
            return this;
          }

          //TODO:
          //IPdxWriter^ PdxLocalWriter::WriteMap( String^ fieldName, System::Collections::IDictionary^ map );

          IPdxWriter^ PdxLocalWriter::WriteCollection( String^ fieldName, System::Collections::IList^ obj)
          {
            AddOffset();
            m_dataOutput->WriteCollection(obj);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteDate( String^ fieldName, System::DateTime date)
          {
            m_dataOutput->WriteDate(date);
            return this;
          }
          
          IPdxWriter^ PdxLocalWriter::WriteBooleanArray( String^ fieldName, array<bool>^ boolArray)
          {
            AddOffset();
            m_dataOutput->WriteBooleanArray(boolArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteCharArray(String^ fieldName, array<Char>^ charArray)
          {
            AddOffset();
            m_dataOutput->WriteCharArray(charArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteByteArray(String^ fieldName, array<Byte>^ byteArray)
          {
            AddOffset();
						m_dataOutput->WriteBytes(byteArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteSByteArray(String^ fieldName, array<SByte>^ sbyteArray)
          {
            AddOffset();
            m_dataOutput->WriteSBytes(sbyteArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteShortArray(String^ fieldName, array<System::Int16>^ shortArray)
          {
            AddOffset();
            m_dataOutput->WriteShortArray(shortArray);//TODO::this don't write typeid looks confusing
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteUnsignedShortArray(String^ fieldName, array<System::UInt16>^ ushortArray)
          {
            AddOffset();
           // m_dataOutput->WriteObject(ushortArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteIntArray(String^ fieldName, array<System::Int32>^ intArray)
          {
            AddOffset();
            m_dataOutput->WriteIntArray(intArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteUnsignedIntArray(String^ fieldName, array<System::UInt32>^ uintArray)
          {
            AddOffset();
           // m_dataOutput->WriteObject(uintArray);
            return this;
          }
					
          IPdxWriter^ PdxLocalWriter::WriteLongArray(String^ fieldName, array<Int64>^ longArray)
          {
            AddOffset();
            m_dataOutput->WriteLongArray(longArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteUnsignedLongArray(String^ fieldName, array<System::UInt64>^ ulongArray)
          {
            AddOffset();
            //m_dataOutput->WriteObject(ulongArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteFloatArray(String^ fieldName, array<float>^ floatArray)
          {
            AddOffset();
            m_dataOutput->WriteFloatArray(floatArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteDoubleArray(String^ fieldName, array<double>^ doubleArray)
          {
            AddOffset();
            m_dataOutput->WriteDoubleArray(doubleArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteStringArray(String^ fieldName, array<String^>^ stringArray)
          {
            AddOffset();
            m_dataOutput->WriteStringArray(stringArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteObjectArray(String^ fieldName, List<Object^>^ objectArray)
          {
            AddOffset();
            m_dataOutput->WriteObjectArray(objectArray);
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteArrayOfByteArrays(String^ fieldName, array<array<Byte>^>^ byteArrays)
          {
            AddOffset();
            m_dataOutput->WriteArrayOfByteArrays(byteArrays);
            return this;
          }
          
          //TODO:
          //IPdxWriter^ PdxLocalWriter::WriteEnum(String^ fieldName, Enum e) ;
          //IPdxWriter^ PdxLocalWriter::WriteInetAddress(String^ fieldName, InetAddress address);


          IPdxWriter^ PdxLocalWriter::MarkIdentityField(String^ fieldName)
          {
            return this;
          }

          IPdxWriter^ PdxLocalWriter::WriteField(String^ fieldName, Object^ fieldValue, Type^ type)
          {
            if(type->Equals(DotNetTypes::IntType))
            {
              return this->WriteInt(fieldName, (int)fieldValue);
            }
            else if(type->Equals(DotNetTypes::StringType))
            {
              return this->WriteString(fieldName, (String^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::BooleanType))
            {
              return this->WriteBoolean(fieldName, (bool)fieldValue);
            }
            else if(type->Equals(DotNetTypes::FloatType))
            {
              return this->WriteFloat(fieldName, (float)fieldValue);
            }
            else if(type->Equals(DotNetTypes::DoubleType))
            {
              return this->WriteDouble(fieldName, (double)fieldValue);
            }
            else if(type->Equals(DotNetTypes::CharType))
            {
              return this->WriteChar(fieldName, (Char)fieldValue);
            }
            else if(type->Equals(DotNetTypes::SByteType))
            {
              return this->WriteByte(fieldName, (SByte)fieldValue);
            }
            else if(type->Equals(DotNetTypes::ShortType))
            {
              return this->WriteShort(fieldName, (short)fieldValue);
            }
            else if(type->Equals(DotNetTypes::LongType))
            {
              return this->WriteLong(fieldName, (Int64)fieldValue);
            }
            else if(type->Equals(DotNetTypes::ByteArrayType))
            {
              return this->WriteByteArray(fieldName, (array<Byte>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::DoubleArrayType))
            {
              return this->WriteDoubleArray(fieldName, (array<double>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::FloatArrayType))
            {
              return this->WriteFloatArray(fieldName, (array<float>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::ShortArrayType))
            {
              return this->WriteShortArray(fieldName, (array<Int16>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::IntArrayType))
            {
              return this->WriteIntArray(fieldName, (array<System::Int32>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::LongArrayType))
            {
              return this->WriteLongArray(fieldName, (array<Int64>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::BoolArrayType))
            {
              return this->WriteBooleanArray(fieldName, (array<bool>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::CharArrayType))
            {
              return this->WriteCharArray(fieldName, (array<Char>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::StringArrayType))
            {
              return this->WriteStringArray(fieldName, (array<String^>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::DateType))
            {
              return this->WriteDate(fieldName, (DateTime)fieldValue);
            }
            else if(type->Equals(DotNetTypes::ByteArrayOfArrayType))
            {
              return this->WriteArrayOfByteArrays(fieldName, (array<array<Byte>^>^)fieldValue);
            }
            else if(type->Equals(DotNetTypes::ObjectArrayType))
            {
              return this->WriteObjectArray(fieldName, safe_cast<System::Collections::Generic::List<Object^>^>(fieldValue));
            }
            else
            {
              return this->WriteObject(fieldName, fieldValue);
            }
          }
          
          PdxType^ PdxLocalWriter::GetLocalPdxType(String^ pdxClassName)
          {
            return m_dataOutput->Cache->GetPdxTypeRegistry()->GetLocalPdxType(pdxClassName);
          }
         
          Apache::Geode::Client::Cache^ PdxLocalWriter::Cache::get() { return m_dataOutput->Cache; }
 
      }  // namespace Internal
    }  // namespace Client
  }  // namespace Geode
}  // namespace Apache
