/*
 * 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.
 */

#pragma once


#include "geode_defs.hpp"
#include "IPdxSerializer.hpp"
#include "PdxIdentityFieldAttribute.hpp"
using namespace System;
using namespace System::Reflection;
using namespace System::Reflection::Emit;

namespace Apache
{
  namespace Geode
  {
    namespace Client
    {

      /// <summary>
      /// Enumerated type for pdx FieldType
      /// </summary>
      public enum class FieldType
      {
        BOOLEAN,
				BYTE,
				CHAR,
				SHORT,
				INT,
				LONG,
				FLOAT,
				DOUBLE,
				DATE,
				STRING,
				OBJECT,
				BOOLEAN_ARRAY,
				CHAR_ARRAY,
				BYTE_ARRAY,
				SHORT_ARRAY,
				INT_ARRAY,
				LONG_ARRAY,
				FLOAT_ARRAY,
				DOUBLE_ARRAY,
				STRING_ARRAY,
				OBJECT_ARRAY,
				ARRAY_OF_BYTE_ARRAYS,
        SBYTE,
        USHORT,
        UINT,
        ULONG,
        SBYTE_ARRAY,
        USHORT_ARRAY,
        UINT_ARRAY,
        ULONG_ARRAY,
        GUID,
        DECIMAL
      };

        ref class FieldWrapper;

		    /// <summary>
        /// This class uses .NET reflection in conjunction with
        /// <see cref="IPdxSerializer"/> to perform
        /// automatic serialization of domain objects. The implication is that the domain
        /// classes do not need to implement the <see cref="IPdxSerializable"> interface.       
        /// This implementation will serialize all relevant fields.
        /// This will not serialize the fields which has defined attribute NonSerialized.
        /// This will not serialize the static, literal and readonly fields.
        ///
        /// Use <see cref="PdxIdentityFieldAttribute"> to define member field as identity field.
        /// Identity fields are used for hashcode creation and equals methods.
        ///
        /// </summary>
        public ref class ReflectionBasedAutoSerializer : IPdxSerializer
        {
        public:

          virtual bool ToData( Object^ o,IPdxWriter^ writer );

          virtual Object^ FromData(String^ o, IPdxReader^ reader );

         /// <summary>
         /// Controls the field name that will be used in pdx for a field being auto serialized.
         /// Override this method to customize the field names that will be generated by auto serialization.
         /// It allows you to convert a local, language dependent name, to a more portable name.
         /// The returned name is the one that will show up in a <see cref="IPdxInstance" /> and that
         /// one that will need to be used to access the field when doing a query.
         /// <para>
         /// The default implementation returns the name obtained from <code>fi</code>.
         /// <para>
         /// This method is only called the first time it sees a new class. The result
         /// will be remembered and used the next time the same class is seen.
         /// </summary>
         /// <param name="fi"> the field whose name is returned.</param>
         /// <param name"type"> type the original class being serialized that owns this field.</param>
         /// <returns> the name of the field </returns>
         
          virtual String^ GetFieldName(FieldInfo^ fi, Type^ type);

         /// <summary>
         /// Controls what fields of a class that is auto serialized will be marked
         /// as pdx identity fields.
         /// Override this method to customize what fields of an auto serialized class will be
         /// identity fields.
         /// Identity fields are used when a <see cref="IPdxInstance" /> computes its hash code
         /// and checks to see if it is equal to another object.
         /// <para>
         /// The default implementation only marks fields that match an "#identity=" pattern
         /// as identity fields.
         /// <para>
         /// This method is only called the first time it sees a new class. The result
         /// will be remembered and used the next time the same class is seen.
         /// </summary>
         /// <param name="fi"> the field to test to see if it is an identity field.</param>
         /// <param name="type"> the original class being serialized that owns this field.</param>
         /// <returns> true if the field should be marked as an identity field; false if not. </returns>
         
          virtual bool IsIdentityField(FieldInfo^ fi, Type^ type);

         /// <summary>
         /// Controls what pdx field type will be used when auto serializing.
         /// Override this method to customize what pdx field type will be used
         /// for a given domain class field.
         /// <para>
         /// The default implementation uses type of field.
         /// <para>
         /// This method is only called the first time it sees a new class. The result
         /// will be remembered and used the next time the same class is seen.
         /// </summary> 
         /// <param name="fi"> the field whose pdx field type needs to be determined </param>
         /// <param name="type"> the original class being serialized that owns this field.</param>
          /// <returns> the pdx field type of the given domain class field.</returns>         
         
          virtual FieldType GetFieldType(FieldInfo^ fi, Type^ type);
  
         /// <summary>
         /// Controls what fields of a class will be auto serialized by this serializer.
         /// Override this method to customize what fields of a class will be auto serialized.
         /// The default implementation:
         /// <list type="bullet">
         /// <item>
         /// <description> excludes NonSerialized fields</description>
         /// </item>
         /// <item>
         /// <description> excludes static fields</description>
         /// </item>
         /// <item>
         /// <description> excludes literal fields</description>
         /// </item>
         /// <item>
         /// <description> excludes readonly fields </description>
         /// </item>
         /// </list>
         /// All other fields are included.
         /// This method is only called the first time it sees a new class. The result
         /// will be remembered and used the next time the same class is seen.
         /// </summary>
         /// <param name="fi"> the field being considered for serialization</param>
         /// <param name="type"> the original class being serialized that owns this field.</param>
          /// <returns> true if the field should be serialized as a pdx field; false if it should be ignored.</returns>
         
          virtual bool IsFieldIncluded(FieldInfo^ fi, Type^ type);

         /// <summary>
         /// Controls what field value is written during auto serialization.
         /// Override this method to customize the data that will be written
         /// during auto serialization.
         /// </summary>
         /// <param name="fi"> the field in question</param>
         /// <param name="type"> the original class being serialized that owns this field.</param>
         /// <param name="originalValue"> the value of the field that was read from the domain object.</param> 
          /// <returns> the actual value to write for this field. Return <code>originalValue</code>
          ///   if you decide not to transform the value. </returns>
         
          virtual Object^ WriteTransform(FieldInfo^ fi, Type^ type, Object^ originalValue);

         /// <summary>
         /// Controls what field value is read during auto deserialization.
         /// Override this method to customize the data that will be read
         /// during auto deserialization.
         /// This method will only be called if {@link #transformFieldValue}
         /// returned true.
          /// </summary>
          /// <param name="fi"> the field in question </param>
         /// <param name="type"> the original class being serialized that owns this field.
         ///   Note that this field may have been inherited from a super class by this class.</param>
         /// <param value="serializeValue"> the value of the field that was serialized for this field.</param>
          /// <returns> the actual value to write for this field. Return <code>serializedValue</code>
          ///   if you decide not to transform the value. </returns>         
          virtual Object^ ReadTransform(FieldInfo^ fi, Type^ type, Object^ serializeValue);          
        
          /// <summary>
          /// Overirde this method to create default instance of <code>className</code>
          /// Otherwise it will create instance using zer arg public constructor
          /// </summary>
          /// <param name="className"> name of the class to create default instance </param>
          /// <returns> the defaulf instance </returns>

          virtual Object^ CreateObject(String^ className, Cache^ cache);

          ReflectionBasedAutoSerializer();
        private:

          FieldType getPdxFieldType( Type^ type);

          void serializeFields(Object^ o,IPdxWriter^ writer );

          Object^ deserializeFields(String^ o, IPdxReader^ reader);
          
          bool IsPdxIdentityField(FieldInfo^ fi);

          System::Collections::Generic::Dictionary<String^, List<FieldWrapper^>^>^ classNameVsFieldInfoWrapper;
                                
          List<FieldWrapper^>^ GetFields(Type^ domaimType);

          static Type^ PdxIdentityFieldAttributeType = nullptr;
        };
    }  // namespace Client
  }  // namespace Geode
}  // namespace Apache

