/*
 * Copyright 2010-2012 Luca Garulli (l.garulli--at--orientechnologies.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.core.sql;

import com.orientechnologies.common.util.OResettable;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.id.OClusterPositionFactory;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Handles runtime results.
 * 
 * @author Luca Garulli
 */
public class ORuntimeResult {
  private final Object              fieldValue;
  private final Map<String, Object> projections;
  private final ODocument           value;
  private OCommandContext           context;

  public ORuntimeResult(final Object iFieldValue, final Map<String, Object> iProjections, final int iProgressive,
      final OCommandContext iContext) {
    fieldValue = iFieldValue;
    projections = iProjections;
    context = iContext;
    value = createProjectionDocument(iProgressive);
  }

  public void applyRecord(final OIdentifiable iRecord) {
    applyRecord(value, projections, context, iRecord);
  }

  /**
   * Set a single value. This is useful in case of query optimization like with indexes
   * 
   * @param iName
   *          Field name
   * @param iValue
   *          Field value
   */
  public void applyValue(final String iName, final Object iValue) {
    value.field(iName, iValue);
  }

  public ODocument getResult() {
    return getResult(value, projections);
  }

  public static ODocument createProjectionDocument(final int iProgressive) {
    final ODocument doc = new ODocument().setOrdered(true);
    // ASSIGN A TEMPORARY RID TO ALLOW PAGINATION IF ANY
    ((ORecordId) doc.getIdentity()).clusterId = -2;
    ((ORecordId) doc.getIdentity()).clusterPosition = OClusterPositionFactory.INSTANCE.valueOf(iProgressive);
    return doc;
  }

  public static ODocument applyRecord(final ODocument iValue, final Map<String, Object> iProjections,
      final OCommandContext iContext, final OIdentifiable iRecord) {
    // APPLY PROJECTIONS
    final ODocument inputDocument = (ODocument) (iRecord != null ? iRecord.getRecord() : null);

    if (iProjections.isEmpty())
      // SELECT * CASE
      inputDocument.copyTo(iValue);
    else {

      for (Entry<String, Object> projection : iProjections.entrySet()) {
        final Object v = projection.getValue();

        if (v == null)
          continue;

        final Object projectionValue;
        if (v.equals("*")) {
          // COPY ALL
          inputDocument.copyTo(iValue);
          projectionValue = null;
        } else if (v instanceof OSQLFilterItemVariable) {
          // RETURN A VARIABLE FROM THE CONTEXT
          projectionValue = ((OSQLFilterItemVariable) v).getValue(inputDocument, iValue, iContext);
        } else if (v instanceof OSQLFilterItemField)
          projectionValue = ((OSQLFilterItemField) v).getValue(inputDocument, iValue, iContext);
        else if (v instanceof OSQLFunctionRuntime) {
          final OSQLFunctionRuntime f = (OSQLFunctionRuntime) v;
          projectionValue = f.execute(inputDocument, inputDocument, iValue, iContext);
        } else
          projectionValue = v;

        if (projectionValue != null)
          if (projectionValue instanceof ORidBag)
            iValue.field(projection.getKey(), new ORidBag((ORidBag) projectionValue));
          else if (projectionValue instanceof OIdentifiable && !(projectionValue instanceof ORID)
              && !(projectionValue instanceof ORecord))
            iValue.field(projection.getKey(), ((OIdentifiable) projectionValue).getRecord());
          else if (projectionValue instanceof Iterator) {
            // make temporary value typical case graph database elemenet's iterator edges
						if(projectionValue instanceof OResettable)
							((OResettable)projectionValue).reset();

            final List<Object> iteratorValues = new ArrayList<Object>();
            final Iterator projectionValueIterator = (Iterator) projectionValue;
            while (projectionValueIterator.hasNext()) {
              final Object value = projectionValueIterator.next();
              if (value instanceof OIdentifiable && !(value instanceof ORID) && !(value instanceof ORecord))
                iteratorValues.add(((OIdentifiable) value).getRecord());
              else
                iteratorValues.add(value);
            }

            iValue.field(projection.getKey(), iteratorValues);
          } else
            iValue.field(projection.getKey(), projectionValue);

      }
    }

    return iValue;
  }

  public static ODocument getResult(final ODocument iValue, final Map<String, Object> iProjections) {
    if (iValue != null) {

      boolean canExcludeResult = false;

      for (Entry<String, Object> projection : iProjections.entrySet()) {
        if (!iValue.containsField(projection.getKey())) {
          // ONLY IF NOT ALREADY CONTAINS A VALUE, OTHERWISE HAS BEEN SET MANUALLY (INDEX?)
          final Object v = projection.getValue();
          if (v instanceof OSQLFunctionRuntime) {
            final OSQLFunctionRuntime f = (OSQLFunctionRuntime) v;
            canExcludeResult = f.filterResult();

            Object fieldValue = f.getResult();

            if (fieldValue != null)
              iValue.field(projection.getKey(), fieldValue);
          }
        }
      }

      if (canExcludeResult && iValue.isEmpty())
        // RESULT EXCLUDED FOR EMPTY RECORD
        return null;

      // AVOID SAVING OF TEMP RECORD
      iValue.unsetDirty();
    }
    return iValue;
  }

  public static ODocument getProjectionResult(final int iId, final Map<String, Object> iProjections,
      final OCommandContext iContext, final OIdentifiable iRecord) {
    return ORuntimeResult.getResult(
        ORuntimeResult.applyRecord(ORuntimeResult.createProjectionDocument(iId), iProjections, iContext, iRecord), iProjections);
  }

  public Object getFieldValue() {
    return fieldValue;
  }
}
