/*
 *
 * Copyright 2012 Luca Molino (molino.luca--AT--gmail.com)
 *
 * Licensed 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.
 */
package com.orientechnologies.orient.object.fetch;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.orientechnologies.common.reflection.OReflectionHelper;
import com.orientechnologies.orient.core.db.OUserObject2RecordHandler;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordLazyList;
import com.orientechnologies.orient.core.db.record.ORecordLazyMap;
import com.orientechnologies.orient.core.db.record.ORecordLazySet;
import com.orientechnologies.orient.core.db.record.OTrackedList;
import com.orientechnologies.orient.core.db.record.OTrackedMap;
import com.orientechnologies.orient.core.db.record.OTrackedSet;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.entity.OEntityManager;
import com.orientechnologies.orient.core.exception.OFetchException;
import com.orientechnologies.orient.core.fetch.OFetchContext;
import com.orientechnologies.orient.core.hook.ORecordHook.TYPE;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.ORecordSchemaAware;
import com.orientechnologies.orient.core.type.tree.OMVRBTreeRIDSet;
import com.orientechnologies.orient.object.db.OObjectLazyList;
import com.orientechnologies.orient.object.db.OObjectLazyMap;
import com.orientechnologies.orient.object.db.OObjectLazySet;
import com.orientechnologies.orient.object.enhancement.OObjectEntitySerializer;
import com.orientechnologies.orient.object.enumerations.OObjectEnumLazyList;
import com.orientechnologies.orient.object.enumerations.OObjectEnumLazyMap;
import com.orientechnologies.orient.object.enumerations.OObjectEnumLazySet;
import com.orientechnologies.orient.object.serialization.OObjectCustomSerializerList;
import com.orientechnologies.orient.object.serialization.OObjectCustomSerializerMap;
import com.orientechnologies.orient.object.serialization.OObjectCustomSerializerSet;
import com.orientechnologies.orient.object.serialization.OObjectSerializerHelper;

/**
 * @author luca.molino
 * 
 */
public class OObjectFetchContext implements OFetchContext {

  protected final String                    fetchPlan;
  protected final boolean                   lazyLoading;
  protected final OEntityManager            entityManager;
  protected final OUserObject2RecordHandler obj2RecHandler;

  public OObjectFetchContext(final String iFetchPlan, final boolean iLazyLoading, final OEntityManager iEntityManager,
      final OUserObject2RecordHandler iObj2RecHandler) {
    fetchPlan = iFetchPlan;
    lazyLoading = iLazyLoading;
    obj2RecHandler = iObj2RecHandler;
    entityManager = iEntityManager;
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void onBeforeMap(ORecordSchemaAware<?> iRootRecord, String iFieldName, final Object iUserObject) throws OFetchException {
    final Map map = (Map) iRootRecord.field(iFieldName);
    Map target = null;
    final Field f = OObjectEntitySerializer.getField(iFieldName, iUserObject.getClass());
    final boolean customSerialization = OObjectEntitySerializer.isSerializedType(f);
    final Class genericType = OReflectionHelper.getGenericMultivalueType(f);
    if (map instanceof ORecordLazyMap
        || (map instanceof OTrackedMap<?> && !OReflectionHelper.isJavaType(genericType) && !customSerialization && !genericType
            .isEnum())) {
      target = new OObjectLazyMap(iUserObject, (OTrackedMap<?>) map, OObjectEntitySerializer.isCascadeDeleteField(
          iUserObject.getClass(), f.getName()));
    } else if (customSerialization) {
      target = new OObjectCustomSerializerMap<TYPE>(OObjectEntitySerializer.getSerializedType(f), iRootRecord,
          (Map<Object, Object>) map);
    } else if (genericType.isEnum()) {
      target = new OObjectEnumLazyMap(genericType, iRootRecord, (Map<Object, Object>) map);
    } else {
      target = new HashMap();
    }
    OObjectSerializerHelper.setFieldValue(iUserObject, iFieldName, target);
  }

  public void onBeforeArray(ORecordSchemaAware<?> iRootRecord, String iFieldName, Object iUserObject, OIdentifiable[] iArray)
      throws OFetchException {
    OObjectSerializerHelper.setFieldValue(iUserObject, iFieldName,
        Array.newInstance(iRootRecord.getSchemaClass().getProperty(iFieldName).getLinkedClass().getJavaClass(), iArray.length));
  }

  public void onAfterDocument(ORecordSchemaAware<?> iRootRecord, ORecordSchemaAware<?> iDocument, String iFieldName,
      Object iUserObject) throws OFetchException {
  }

  public void onBeforeDocument(ORecordSchemaAware<?> iRecord, ORecordSchemaAware<?> iDocument, String iFieldName, Object iUserObject)
      throws OFetchException {
  }

  public void onAfterArray(ORecordSchemaAware<?> iRootRecord, String iFieldName, Object iUserObject) throws OFetchException {
  }

  public void onAfterMap(ORecordSchemaAware<?> iRootRecord, String iFieldName, final Object iUserObject) throws OFetchException {
  }

  public void onBeforeDocument(ORecordSchemaAware<?> iRecord, String iFieldName, final Object iUserObject) throws OFetchException {
  }

  public void onAfterDocument(ORecordSchemaAware<?> iRootRecord, String iFieldName, final Object iUserObject)
      throws OFetchException {
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void onBeforeCollection(ORecordSchemaAware<?> iRootRecord, String iFieldName, final Object iUserObject,
      final Iterable<?> iterable) throws OFetchException {
    if (iterable instanceof ORidBag)
      throw new IllegalStateException(OType.LINKBAG.name() + " can not be directly mapped to any Java collection.");

    final Field f = OObjectEntitySerializer.getField(iFieldName, iUserObject.getClass());
    final boolean customSerialization = OObjectEntitySerializer.isSerializedType(f);
    final Class genericType = OReflectionHelper.getGenericMultivalueType(f);
    Collection target;
    if (iterable instanceof ORecordLazyList
        || (iterable instanceof OTrackedList<?> && !OReflectionHelper.isJavaType(genericType) && !customSerialization && !genericType
            .isEnum())) {
      target = new OObjectLazyList(iUserObject, (List<OIdentifiable>) iterable, OObjectEntitySerializer.isCascadeDeleteField(
          iUserObject.getClass(), f.getName()));
    } else if (iterable instanceof ORecordLazySet
        || iterable instanceof OMVRBTreeRIDSet
        || (iterable instanceof OTrackedSet<?> && !OReflectionHelper.isJavaType(genericType) && !customSerialization && !genericType
            .isEnum())) {
      target = new OObjectLazySet(iUserObject, (Set) iterable, OObjectEntitySerializer.isCascadeDeleteField(iUserObject.getClass(),
          f.getName()));
    } else if (customSerialization) {
      if (iterable instanceof List<?>) {
        target = new OObjectCustomSerializerList(OObjectEntitySerializer.getSerializedType(f), iRootRecord, (List<Object>) iterable);
      } else {
        target = new OObjectCustomSerializerSet(OObjectEntitySerializer.getSerializedType(f), iRootRecord, (Set<Object>) iterable);
      }
    } else if (genericType.isEnum()) {
      if (iterable instanceof List<?>) {
        target = new OObjectEnumLazyList(genericType, iRootRecord, (List<Object>) iterable);
      } else {
        target = new OObjectEnumLazySet(genericType, iRootRecord, (Set<Object>) iterable);
      }
    } else {
      if (iterable instanceof List<?>) {
        target = new ArrayList();
      } else {
        target = new HashSet();
      }
    }
    OObjectSerializerHelper.setFieldValue(iUserObject, iFieldName, target);
  }

  public void onAfterCollection(ORecordSchemaAware<?> iRootRecord, String iFieldName, final Object iUserObject)
      throws OFetchException {
  }

  public void onAfterFetch(ORecordSchemaAware<?> iRootRecord) throws OFetchException {
  }

  public void onBeforeFetch(ORecordSchemaAware<?> iRootRecord) throws OFetchException {
  }

  public void onBeforeStandardField(Object iFieldValue, String iFieldName, Object iUserObject) {
  }

  public void onAfterStandardField(Object iFieldValue, String iFieldName, Object iUserObject) {
  }

  public OUserObject2RecordHandler getObj2RecHandler() {
    return obj2RecHandler;
  }

  public OEntityManager getEntityManager() {
    return entityManager;
  }

  public boolean isLazyLoading() {
    return lazyLoading;
  }

  public String getFetchPlan() {
    return fetchPlan;
  }

  public boolean fetchEmbeddedDocuments() {
    return true;
  }
}
