package net.qihoo.xlearning.jobhistory;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import net.qihoo.xlearning.api.XLearningConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapreduce.v2.app.webapp.App;
import org.apache.hadoop.yarn.webapp.Controller;
import org.apache.hadoop.yarn.webapp.View;
import org.apache.hadoop.fs.Path;
import com.google.inject.Inject;

import static org.apache.hadoop.yarn.util.StringHelper.join;

import org.apache.hadoop.fs.FSDataInputStream;
import net.qihoo.xlearning.conf.XLearningConfiguration;
import net.qihoo.xlearning.webapp.AMParams;
import org.apache.hadoop.yarn.webapp.WebApp;
import org.apache.hadoop.yarn.webapp.WebApps;

public class HsController extends Controller implements AMParams {
  private final App app;
  public Path jobLogPath;
  private final Configuration conf;
  private static final Log LOG = LogFactory.getLog(HsController.class);

  @Inject
  HsController(App app, Configuration conf, RequestContext ctx) {
    super(ctx);
    this.app = app;
    this.conf = conf;
  }

  @Override
  public void index() {
    setTitle("JobHistory");
  }

  public void job() throws IOException {
    XLearningConfiguration xlearningConf = new XLearningConfiguration();
    jobLogPath = new Path(conf.get(XLearningConfiguration.XLEARNING_HISTORY_LOG_DIR,
        XLearningConfiguration.DEFAULT_XLEARNING_HISTORY_LOG_DIR) + "/" + $(APP_ID) + "/" + $(APP_ID));
    LOG.info("jobLogPath:" + jobLogPath);
    String line = null;
    try {
      FSDataInputStream in = jobLogPath.getFileSystem(xlearningConf).open(jobLogPath);
      BufferedReader br = new BufferedReader(new InputStreamReader(in));
      line = br.readLine();
      in.close();
    } catch (IOException e) {
      LOG.info("open and read the log from " + jobLogPath + " error, " + e);
    }

    if (line == null) {
      set(CONTAINER_NUMBER, String.valueOf(0));
    } else {
      Gson gson = new Gson();
      Map<String, Object> readLog = new TreeMap<>();
      readLog = (Map) gson.fromJson(line, readLog.getClass());
      int i = 0;
      int workeri = 0;
      int psi = 0;
      set(OUTPUT_TOTAL, String.valueOf(0));
      set(TIMESTAMP_TOTAL, String.valueOf(0));
      set(WORKER_NUMBER, String.valueOf(0));
      set(PS_NUMBER, String.valueOf(0));
      set(CHIEF_WORKER_MEMORY, "");
      set(EVALUATOR_WORKER_MEMORY, "");
      Boolean cpuStatisticsFlag = false;
      Boolean cpuMetricsFlag = false;
      set(CONTAINER_CPU_METRICS_ENABLE, "false");
      for (String info : readLog.keySet()) {
        if (info.equals(AMParams.APP_TYPE)) {
          if (readLog.get(info) != null) {
            if (readLog.get(info).equals("XLEARNING")) {
              set(APP_TYPE, "XLearning");
            } else {
              char[] appType = String.valueOf(readLog.get(info)).toLowerCase().toCharArray();
              appType[0] -= 32;
              set(APP_TYPE, String.valueOf(appType));
            }
          } else {
            set(APP_TYPE, "XLearning");
          }
        } else if (info.equals(AMParams.BOARD_INFO)) {
          set(BOARD_INFO_FLAG, "true");
          set(BOARD_INFO, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.OUTPUT_PATH)) {
          if (readLog.get(info) instanceof ArrayList<?>) {
            List<String> outputList = (ArrayList<String>) readLog.get(info);
            if (outputList.size() == 0 || outputList.get(0).equals("-")) {
              set(OUTPUT_TOTAL, String.valueOf(0));
            } else {
              int j = 0;
              for (String output : outputList) {
                set(OUTPUT_PATH + j, output + conf.get(XLearningConfiguration.XLEARNING_INTERREAULST_DIR, XLearningConfiguration.DEFAULT_XLEARNING_INTERRESULT_DIR));
                j++;
              }
              set(OUTPUT_TOTAL, String.valueOf(j));
            }
          } else {
            set(OUTPUT_TOTAL, String.valueOf(0));
          }
        } else if (info.equals(AMParams.TIMESTAMP_LIST)) {
          if (readLog.get(info) instanceof ArrayList<?>) {
            List<String> savedTimeList = (ArrayList<String>) readLog.get(info);
            if (savedTimeList.size() == 0 || savedTimeList.get(0).equals("-")) {
              set(TIMESTAMP_TOTAL, String.valueOf(0));
            } else {
              int j = 0;
              for (String timeStamp : savedTimeList) {
                set(TIMESTAMP_LIST + j, timeStamp);
                j++;
              }
              set(TIMESTAMP_TOTAL, String.valueOf(j));
            }
          } else {
            set(TIMESTAMP_TOTAL, String.valueOf(0));
          }
        } else if (info.indexOf("container") > -1) {
          set(CONTAINER_ID + i, info);
          if (readLog.get(info) instanceof Map<?, ?>) {
            Map<String, String> containerMessage = (Map<String, String>) readLog.get(info);
            set(CONTAINER_HTTP_ADDRESS + i, containerMessage.get(AMParams.CONTAINER_HTTP_ADDRESS));
            if (containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.EVALUATOR) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.CHIEF)) {
              set(CONTAINER_ROLE + i, XLearningConstants.WORKER + "/" + containerMessage.get(AMParams.CONTAINER_ROLE));
            } else {
              set(CONTAINER_ROLE + i, containerMessage.get(AMParams.CONTAINER_ROLE));
            }
            set(CONTAINER_STATUS + i, containerMessage.get(AMParams.CONTAINER_STATUS));
            set(CONTAINER_START_TIME + i, containerMessage.get(AMParams.CONTAINER_START_TIME));
            set(CONTAINER_FINISH_TIME + i, containerMessage.get(AMParams.CONTAINER_FINISH_TIME));
            set(CONTAINER_REPORTER_PROGRESS + i, containerMessage.get(AMParams.CONTAINER_REPORTER_PROGRESS));
            set(CONTAINER_LOG_ADDRESS + i, containerMessage.get(AMParams.CONTAINER_LOG_ADDRESS));
            if (containerMessage.containsKey(AMParams.CONTAINER_CPU_METRICS)) {
              String cpuMetrics = containerMessage.get(AMParams.CONTAINER_CPU_METRICS);
              if (cpuMetrics != null) {
                Gson gson2 = new GsonBuilder()
                    .registerTypeAdapter(
                        new TypeToken<ConcurrentHashMap<String, Object>>() {
                        }.getType(),
                        new JsonDeserializer<ConcurrentHashMap<String, Object>>() {
                          @Override
                          public ConcurrentHashMap<String, Object> deserialize(
                              JsonElement json, Type typeOfT,
                              JsonDeserializationContext context) throws JsonParseException {
                            ConcurrentHashMap<String, Object> treeMap = new ConcurrentHashMap<>();
                            JsonObject jsonObject = json.getAsJsonObject();
                            Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                            for (Map.Entry<String, JsonElement> entry : entrySet) {
                              treeMap.put(entry.getKey(), entry.getValue());
                            }
                            return treeMap;
                          }
                        }).create();

                Type type = new TypeToken<ConcurrentHashMap<String, Object>>() {
                }.getType();
                ConcurrentHashMap<String, Object> map = gson2.fromJson(cpuMetrics, type);
                if (map.size() > 0) {
                  cpuMetricsFlag = true;
                  if (containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.WORKER) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.EVALUATOR) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.CHIEF)) {
                    set("workerCpuMemMetrics" + workeri, new Gson().toJson(map.get("CPUMEM")));
                    if (map.containsKey("CPUUTIL")) {
                      set("workerCpuUtilMetrics" + workeri, new Gson().toJson(map.get("CPUUTIL")));
                    }
                  } else {
                    set("psCpuMemMetrics" + psi, new Gson().toJson(map.get("CPUMEM")));
                    if (map.containsKey("CPUUTIL")) {
                      set("psCpuUtilMetrics" + psi, new Gson().toJson(map.get("CPUUTIL")));
                    }
                  }
                }
              }
            }

            if (containerMessage.containsKey(AMParams.CONTAINER_CPU_STATISTICS)) {
              String cpuStatistics = containerMessage.get(AMParams.CONTAINER_CPU_STATISTICS);
              if (cpuStatistics != null && !cpuStatistics.equals("")) {
                Type type = new TypeToken<Map<String, List<Double>>>() {
                }.getType();
                Map<String, List<Double>> map = new Gson().fromJson(cpuStatistics, type);
                if (map.size() > 0) {
                  if (containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.WORKER) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.EVALUATOR) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.CHIEF)) {
                    set("worker" + CONTAINER_CPU_STATISTICS_MEM + USAGE_AVG + workeri, String.format("%.2f", map.get("CPUMEM").get(0)));
                    set("worker" + CONTAINER_CPU_STATISTICS_MEM + USAGE_MAX + workeri, String.format("%.2f", map.get("CPUMEM").get(1)));
                    set("worker" + CONTAINER_CPU_STATISTICS_UTIL + USAGE_AVG + workeri, String.format("%.2f", map.get("CPUUTIL").get(0)));
                    set("worker" + CONTAINER_CPU_STATISTICS_UTIL + USAGE_MAX + workeri, String.format("%.2f", map.get("CPUUTIL").get(1)));
                  } else {
                    set("ps" + CONTAINER_CPU_STATISTICS_MEM + USAGE_AVG + psi, String.format("%.2f", map.get("CPUMEM").get(0)));
                    set("ps" + CONTAINER_CPU_STATISTICS_MEM + USAGE_MAX + psi, String.format("%.2f", map.get("CPUMEM").get(1)));
                    set("ps" + CONTAINER_CPU_STATISTICS_UTIL + USAGE_AVG + psi, String.format("%.2f", map.get("CPUUTIL").get(0)));
                    set("ps" + CONTAINER_CPU_STATISTICS_UTIL + USAGE_MAX + psi, String.format("%.2f", map.get("CPUUTIL").get(1)));
                  }
                  cpuStatisticsFlag = true;
                }
              }
            }

            if (containerMessage.containsKey(AMParams.CONTAINER_CPU_USAGE_WARN_MEM)) {
              if (containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.WORKER) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.EVALUATOR) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.CHIEF)) {
                set("worker" + CONTAINER_CPU_USAGE_WARN_MEM + workeri, containerMessage.get(CONTAINER_CPU_USAGE_WARN_MEM));
              } else {
                set("ps" + CONTAINER_CPU_USAGE_WARN_MEM + psi, containerMessage.get(CONTAINER_CPU_USAGE_WARN_MEM));
              }
            }

            if (containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.WORKER) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.EVALUATOR) || containerMessage.get(AMParams.CONTAINER_ROLE).equals(XLearningConstants.CHIEF)) {
              set("WORKER_CONTAINER_ID" + workeri, info);
              workeri++;
            } else {
              set("PS_CONTAINER_ID" + psi, info);
              psi++;
            }
            i++;
          }
        } else if (info.equals(AMParams.WORKER_NUMBER)) {
          set(WORKER_NUMBER, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.PS_NUMBER)) {
          set(PS_NUMBER, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.WORKER_VCORES)) {
          set(WORKER_VCORES, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.PS_VCORES)) {
          set(PS_VCORES, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.WORKER_MEMORY)) {
          set(WORKER_MEMORY, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.PS_MEMORY)) {
          set(PS_MEMORY, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.CHIEF_WORKER_MEMORY)) {
          set(CHIEF_WORKER_MEMORY, String.valueOf(readLog.get(info)));
        } else if (info.equals(AMParams.EVALUATOR_WORKER_MEMORY)) {
          set(EVALUATOR_WORKER_MEMORY, String.valueOf(readLog.get(info)));
        }
      }
      set(CONTAINER_NUMBER, String.valueOf(i));
      if (cpuMetricsFlag) {
        set(CONTAINER_CPU_METRICS_ENABLE, String.valueOf(true));
      } else {
        set(CONTAINER_CPU_METRICS_ENABLE, String.valueOf(false));
      }
      if (cpuStatisticsFlag) {
        set(CONTAINER_CPU_STATISTICS, String.valueOf(true));
      } else {
        set(CONTAINER_CPU_STATISTICS, String.valueOf(false));
      }

      if ($(BOARD_INFO).equals("-")) {
        String boardInfo = "Board server don't start, You can set argument \"--boardEnable true\" in your submit script to start.";
        set(BOARD_INFO, boardInfo);
      } else {
        String boardLogDir = $(BOARD_INFO);
        if ($(APP_TYPE).equals("Tensorflow")) {
          set(BOARD_INFO, String.format("tensorboard --logdir=%s", boardLogDir));
        } else {
          set(BOARD_INFO, String.format("VisualDL not support the hdfs path for logdir. Please download the log from %s first. Then using \" visualDL \" to start the board", boardLogDir));
        }
      }
    }

    if (Boolean.parseBoolean($(CONTAINER_CPU_METRICS_ENABLE))) {
      try {
        WebApps.Builder.class.getMethod("build", WebApp.class);
      } catch (NoSuchMethodException e) {
        if (Controller.class.getClassLoader().getResource("webapps/static/xlWebApp") == null) {
          LOG.warn("Don't have the xlWebApp Resource.");
          set(CONTAINER_CPU_METRICS_ENABLE, String.valueOf(false));
        }
      }
    }
    setTitle(join($(APP_TYPE) + " Application ", $(APP_ID)));
    render(jobPage());
  }

  protected Class<? extends View> jobPage() {
    return HsJobPage.class;
  }

  /**
   * Render the logs page.
   */
  public void logs() {
    render(HsLogsPage.class);
  }

}