package org.patriques.output.technicalindicators;

import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import org.patriques.input.technicalindicators.Interval;
import org.patriques.output.AlphaVantageException;
import org.patriques.output.JsonParser;

import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * Since the format for the technical indicator responses differ slightly but on the whole
 * have the same structure the {@code TechnicalIndicatorParser} extracts the similarity of
 * the parsing to this class.
 *
 * @see JsonParser
 * @param <Data> the response for each individual Response, i.e MACD, EMA etc.
 */
public abstract class TechnicalIndicatorParser<Data> extends JsonParser<Data> {

  private final Interval interval;

  public TechnicalIndicatorParser(Interval interval) {
    this.interval = interval;
  }

  /**
   * The specifics of the resolution is pushed down to each response type, i.e MACD, EMA etc.
   *
   * @param metaData the meta data
   * @param indicatorData the indicator data
   * @return the response for each individual response, i.e MACD, EMA etc.
   */
  abstract Data resolve(Map<String, String> metaData,
                        Map<String, Map<String, String>> indicatorData);

  /**
   * Gets the key for the indicators, this differs for each response type, i.e MACD, EMA etc.
   * This is used by the resolve method below.
   *
   * @return the indicator data key
   */
  abstract String getIndicatorKey();

  /**
   * Helper method for resolving local date time. If the key to the data object doesn´t specify hours and minutes it
   * returns the hour at beginning of that day.
   *
   * @param key the key to the data object, i.e: "2017-12-01 11:15" or "2017-12-01" depending on interval
   * @return the {@link LocalDateTime} instance
   */
  protected LocalDateTime resolveDate(String key) {
    switch (interval) {
      case MONTHLY:
      case WEEKLY:
      case DAILY:
    		if(key.matches(DATE_WITH_TIME_PATTERN)) {
    			return LocalDate.parse(key, DATE_WITH_TIME_FORMAT).atStartOfDay();
    		}
    		else if (key.matches(DATE_WITH_SIMPLE_TIME_PATTERN)) {
    			return LocalDate.parse(key, DATE_WITH_SIMPLE_TIME_FORMAT).atStartOfDay();
    		}
    		else if (key.matches(SIMPLE_DATE_PATTERN)) {
    			return LocalDate.parse(key, SIMPLE_DATE_FORMAT).atStartOfDay();
    		}
      default:
        return LocalDateTime.parse(key, DATE_WITH_SIMPLE_TIME_FORMAT);
    }
  }

  @Override
  protected Data resolve(JsonObject rootObject) {
    Type metaDataType = new TypeToken<Map<String, String>>() {
    }.getType();
    Type dataType = new TypeToken<Map<String, Map<String, String>>>() {
    }.getType();
    try {
      Map<String, String> metaData = GSON.fromJson(rootObject.get("Meta Data"), metaDataType);
      Map<String, Map<String, String>> indicatorData = GSON.fromJson(rootObject.get(getIndicatorKey()), dataType);
      return resolve(metaData, indicatorData);
    } catch (JsonSyntaxException e) {
      throw new AlphaVantageException("technical indicators api change", e);
    }
  }
}