/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.plugin.typescript.dto.model;

import static org.eclipse.che.plugin.typescript.dto.DTOHelper.convertType;
import static org.eclipse.che.plugin.typescript.dto.DTOHelper.getGetterFieldName;
import static org.eclipse.che.plugin.typescript.dto.DTOHelper.getSetterFieldName;
import static org.eclipse.che.plugin.typescript.dto.DTOHelper.getWithFieldName;
import static org.eclipse.che.plugin.typescript.dto.DTOHelper.isDtoGetter;
import static org.eclipse.che.plugin.typescript.dto.DTOHelper.isDtoSetter;
import static org.eclipse.che.plugin.typescript.dto.DTOHelper.isDtoWith;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * * Model of the DTO It includes attributes/fields and methods.
 *
 * @author Florent Benoit
 */
public class DtoModel {

  /** DTO instance (interface). */
  private Class dto;

  /** Model of methods for this interface. */
  private List<MethodModel> methods;

  /** Map of all attributes found when scanning methods */
  private Map<String, Type> fieldAttributes = new HashMap<>();

  /** Model for the attributes of this interface (for generating implementation) */
  private List<FieldAttributeModel> fieldAttributeModels;

  /**
   * Build a new model for the given DTO class by scanning it.
   *
   * @param dto the interface with {@link org.eclipse.che.dto.shared.DTO} annotation
   */
  public DtoModel(Class dto) {
    this.dto = dto;
    this.methods = new ArrayList<>();
    this.fieldAttributeModels = new ArrayList<>();
    analyze();
  }

  /** Scan all getter/setter/with methods that are not inherited */
  protected void analyze() {
    Arrays.asList(this.dto.getMethods())
        .stream()
        .filter(
            method ->
                !method.isBridge()
                    && (isDtoGetter(method) || isDtoSetter(method) || isDtoWith(method)))
        .forEach(
            method -> {
              MethodModel methodModel = new MethodModel(method);

              // check method with same name already exist
              if (!methods.contains(methodModel)) {
                methods.add(methodModel);
                if (isDtoGetter(method)) {
                  analyzeDtoGetterMethod(method, methodModel);
                } else if (isDtoSetter(method)) {
                  analyzeDtoSetterMethod(method, methodModel);
                } else if (isDtoWith(method)) {
                  analyzeDtoWithMethod(method, methodModel);
                }
              }
            });

    // now convert map into list
    fieldAttributes
        .entrySet()
        .stream()
        .forEach(
            field ->
                fieldAttributeModels.add(
                    new FieldAttributeModel(field.getKey(), field.getValue())));
  }

  /**
   * Populate model from given reflect getter method
   *
   * @param method the method to analyze
   * @param methodModel the model to update
   */
  protected void analyzeDtoGetterMethod(Method method, MethodModel methodModel) {
    methodModel.setGetter(true);
    Type fieldType = method.getGenericReturnType();
    String fieldName = getGetterFieldName(method);
    fieldAttributes.put(fieldName, fieldType);
    methodModel.setFieldName(fieldName);
    methodModel.setFieldType(convertType(fieldType));
  }

  /**
   * Populate model from given reflect setter method
   *
   * @param method the method to analyze
   * @param methodModel the model to update
   */
  protected void analyzeDtoSetterMethod(Method method, MethodModel methodModel) {
    methodModel.setSetter(true);
    // add the parameter
    Type fieldType = method.getGenericParameterTypes()[0];
    String fieldName = getSetterFieldName(method);
    fieldAttributes.put(fieldName, fieldType);
    methodModel.setFieldName(fieldName);
    methodModel.setFieldType(convertType(fieldType));
  }

  /**
   * Populate model from given reflect with method
   *
   * @param method the method to analyze
   * @param methodModel the model to update
   */
  protected void analyzeDtoWithMethod(Method method, MethodModel methodModel) {
    methodModel.setWith(true);
    // add the parameter
    Type fieldType = method.getGenericParameterTypes()[0];
    String fieldName = getWithFieldName(method);
    fieldAttributes.put(fieldName, fieldType);
    methodModel.setFieldName(fieldName);
    methodModel.setFieldType(convertType(fieldType));
  }

  /** @return model of attributes */
  public List<FieldAttributeModel> getFieldAttributeModels() {
    return fieldAttributeModels;
  }

  /**
   * Gets the package name of this interface
   *
   * @return the package name of this interface
   */
  public String getPackageName() {
    return this.dto.getPackage().getName();
  }

  /**
   * Gets the short (simple) name of the interface. Like HelloWorld if FQN class is
   * foo.bar.HelloWorld
   *
   * @return the name of the interface
   */
  public String getSimpleName() {
    return this.dto.getSimpleName();
  }

  /**
   * Gets the FQN of this interface like foo.bar.HelloWorld
   *
   * @return the FQN name of this DTO interface
   */
  public String getName() {
    return this.dto.getName();
  }

  /**
   * Provides the model for every methods of the DTO that are getter/setter/with methods
   *
   * @return the list
   */
  public List<MethodModel> getMethods() {
    return this.methods;
  }
}
