/**
 * This file is part of Eclipse Steady.
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.java.sign.gson;

import java.lang.reflect.Type;

import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

import org.eclipse.steady.java.sign.ASTConstructBodySignature;
import org.eclipse.steady.java.sign.ASTUtil;
import org.eclipse.steady.sign.Signature;

import ch.uzh.ifi.seal.changedistiller.model.classifiers.EntityType;
import ch.uzh.ifi.seal.changedistiller.model.classifiers.SourceRange;
import ch.uzh.ifi.seal.changedistiller.model.entities.SourceCodeEntity;
import ch.uzh.ifi.seal.changedistiller.treedifferencing.Node;

/**
 *  This class is used to deserialize an AST JSON representation of a construct body object into its corresponding
 *  java Signatures object
 *
 *JSON representation of ASTConstructBodySignature:
 *
 *Node:
 *    - fLabel : EntityType
 *    - fValue : String
 *
 *    -- fMatched : boolean
 *    -- fOrdered : boolean
 *
 *    - fEntity : SourceCodeEntity
 *		 -  fUniqueName : String  //same information as fValue above
 *		 -  fType : EntityType    //same information ast fLabel above
 *		 -  fModifiers : int	 // this is always zero (Not sure if i should have it in the json object)
 *		 -  fAssociatedEntities : List&lt;SourceCodeEntity&gt;
 *		 -  SourceRange fRange : SourceRange
 *				- end : int
 *				- start : int
 *    -  fAssociatedNodes : List&lt;Node&gt;
 */
public class ASTSignatureDeserializer implements JsonDeserializer<Signature> {

  // Values for JsonElement Names, used as a member name during deserialization
  // We need only to change these value if the name/value pair of a JsonElement changes in the json
  // representation of the signature
  // For instance, if we want to  compress  the json object for not storing a large data in the DB.
  private static String AST = "ast";
  private static String ENTITY_TYPE = "EntityType";
  private static String VALUE = "Value";
  private static String CHILDREN = "C";

  private static String SOURCE_CODE_ENTITY = "SourceCodeEntity";
  private static String SRC_ENTITY_MODIFIERS = "Modifiers";
  private static String SRC_ENTITY_SOURCE_RANGE = "SourceRange";
  private static String SRC_ENTITY_SOURCE_RANGE_END = "End";
  private static String SRC_ENTITY_SOURCE_RANGE_START = "Start";

  // ASTConstructBodySignature
  // private ASTConstructBodySignature astConstructSign = null;

  // Signature
  // private Signature sign = null;

  /** {@inheritDoc} */
  @Override
  public Signature deserialize(
      final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
      throws JsonParseException {

    // The Deserialization logic goes here
    final JsonObject jsonObject = json.getAsJsonObject();
    final JsonObject astRootJsonObject =
        jsonObject.get(AST).getAsJsonArray().get(0).getAsJsonObject();

    String fValue = astRootJsonObject.get(VALUE).getAsString(); // Retrieve the Value
    String strLabel = astRootJsonObject.get(ENTITY_TYPE).getAsString();
    EntityType fLabel = ASTUtil.getJavaEntityType(strLabel); // Retrieve the EntityType

    // Instantiate the ASTConstructBodySignature object here (We can have instance of Node object
    // with the above two informations)
    ASTConstructBodySignature astConstructSign = new ASTConstructBodySignature(fLabel, fValue);

    // Create the Root Node
    astConstructSign.setRoot(astConstructSign);

    // TODO : Check if these setting are really necessary to compute the "diff" as an input to
    // changedistiller
    // Settings for order and matching
    // astConstructSign.enableInOrder();
    // astConstructSign.enableMatched();

    // Retrieve the Children
    JsonElement jsonChildrenElement = astRootJsonObject.get(CHILDREN);
    if (jsonChildrenElement != null) {

      JsonArray jsonChildrenArray = astRootJsonObject.get(CHILDREN).getAsJsonArray();
      // Add the children of a Node
      final Node[] children = new Node[jsonChildrenArray.size()];

      for (int i = 0; i < children.length; i++) {
        final JsonObject jsonChildren = jsonChildrenArray.get(i).getAsJsonObject();

        // Get the child Node element and add it
        Node newChild = getNodeChildElement(jsonChildren);
        astConstructSign.add(newChild);
      }
    }
    // Retrieve the SourceCodeEntity
    SourceCodeEntity srcCodeEntity = null;
    JsonObject srcCodeEntityJsonObject =
        astRootJsonObject.get(SOURCE_CODE_ENTITY).getAsJsonObject();
    int modifiers = srcCodeEntityJsonObject.get(SRC_ENTITY_MODIFIERS).getAsInt();

    // SourceRange JsonObject
    JsonObject srcCodeEntitySrcRangeJsonObject =
        srcCodeEntityJsonObject.get(SRC_ENTITY_SOURCE_RANGE).getAsJsonObject();
    int srcRangeStart =
        srcCodeEntitySrcRangeJsonObject.get(SRC_ENTITY_SOURCE_RANGE_START).getAsInt();
    int srcRangeEnd = srcCodeEntitySrcRangeJsonObject.get(SRC_ENTITY_SOURCE_RANGE_END).getAsInt();
    SourceRange srcRange = new SourceRange(srcRangeStart, srcRangeEnd);
    srcCodeEntity = new SourceCodeEntity(fValue, fLabel, modifiers, srcRange);
    astConstructSign.setEntity(srcCodeEntity);

    return astConstructSign;
  }

  private Node getNodeChildElement(JsonObject jsonObject) {

    String fValue = jsonObject.get(VALUE).getAsString(); // Retrieve the Value
    String strLabel = jsonObject.get(ENTITY_TYPE).getAsString();
    EntityType fLabel = ASTUtil.getJavaEntityType(strLabel); // Retrieve the EntityType

    // Instantiate the Node object here
    Node childNode = new Node(fLabel, fValue);

    // Retrieve the SourceCodeEntity
    SourceCodeEntity srcCodeEntity = null;
    JsonObject srcCodeEntityJsonObject = jsonObject.get(SOURCE_CODE_ENTITY).getAsJsonObject();
    int modifiers = srcCodeEntityJsonObject.get(SRC_ENTITY_MODIFIERS).getAsInt();

    // SourceRange JsonObject
    JsonObject srcCodeEntitySrcRangeJsonObject =
        srcCodeEntityJsonObject.get(SRC_ENTITY_SOURCE_RANGE).getAsJsonObject();
    int srcRangeStart =
        srcCodeEntitySrcRangeJsonObject.get(SRC_ENTITY_SOURCE_RANGE_START).getAsInt();
    int srcRangeEnd = srcCodeEntitySrcRangeJsonObject.get(SRC_ENTITY_SOURCE_RANGE_END).getAsInt();
    SourceRange srcRange = new SourceRange(srcRangeStart, srcRangeEnd);
    srcCodeEntity = new SourceCodeEntity(fValue, fLabel, modifiers, srcRange);

    JsonElement jsonChildrenElement = jsonObject.get(CHILDREN);
    if (jsonChildrenElement != null) {

      JsonArray jsonChildrenArray = jsonChildrenElement.getAsJsonArray();

      // Add the children of a Node
      final Node[] children = new Node[jsonChildrenArray.size()];

      for (int i = 0; i < children.length; i++) {
        final JsonObject jsonChildren = jsonChildrenArray.get(i).getAsJsonObject();

        // Get the child Node element and add it
        Node newChild = getNodeChildElement(jsonChildren);
        childNode.add(newChild);
      }
    }

    childNode.setEntity(srcCodeEntity);
    return childNode;
  }
}
