import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class LanguageFileCompare {
  private static Logger log = LoggerFactory.getLogger(LanguageFileCompare.class);

  public static void main(String[] args) {
    if (args.length > 0) {
      Path originPath = Paths.get(args[0]);
      JsonNode twJson = getJsonNode(originPath);
      if (twJson != null) {
        Set<String> originSet = new HashSet<>();
        addKeys("", twJson, originSet, new ArrayList<>());

        IntStream.range(1, args.length)
            .forEach(i -> findMissingKeys(Paths.get(args[i]), originSet));
      }
    }
    checkProperties();
  }

  private static JsonNode getJsonNode(Path path) {
    String ext = path.getFileName().toString();
    try {
      ObjectMapper objectMapper = new ObjectMapper();
      if (ext.endsWith(".json")) {
        /* CHM、HRB */
        return objectMapper.readTree(new String(Files.readAllBytes(path)));
      } else if (ext.endsWith(".js")) {
        /* CHMB use js file to translate */
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine engine = scriptEngineManager.getEngineByName("Nashorn");
        engine.eval(new FileReader(path.toFile()));
        Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
        return objectMapper.valueToTree(bindings.get("local"));
      }
    } catch (Exception e) {
      log.error("{} is not exists or file not correct.", path.getFileName(), e);
      System.exit(1);
    }
    return null;
  }

  private static void addKeys(
      String currentPath, JsonNode jsonNode, Set<String> set, List<Integer> suffix) {
    if (jsonNode.isObject()) {
      ObjectNode objectNode = (ObjectNode) jsonNode;
      Iterator<Map.Entry<String, JsonNode>> iter = objectNode.fields();
      String pathPrefix = currentPath.isEmpty() ? "" : currentPath + "-";

      while (iter.hasNext()) {
        Map.Entry<String, JsonNode> entry = iter.next();
        addKeys(pathPrefix + entry.getKey(), entry.getValue(), set, suffix);
      }

    } else if (jsonNode.isValueNode()) {
      StringBuilder sb = new StringBuilder(currentPath);
      if (currentPath.contains("-")) {
        for (Integer suffix1 : suffix) {
          sb.append("-").append(suffix1);
        }
      }
      set.add(sb.toString());
    }
  }

  private static void findMissingKeys(Path targetPath, Set<String> originSet) {
    if (!targetPath.toFile().exists()) return;

    Set<String> targetSet = new HashSet<>();
    JsonNode json = getJsonNode(targetPath);
    if (json == null) return;

    addKeys("", json, targetSet, new ArrayList<>());

    Set<String> missingKey =
        originSet
            .parallelStream()
            .filter(key -> !targetSet.contains(key))
            .collect(Collectors.toSet());
    if (!missingKey.isEmpty()) {
      log.error(
          "Missing Key on {} \nMissing Keys: {}",
          targetPath.getFileName(),
          missingKey.stream().sorted().collect(Collectors.toList()));
      System.exit(1);
    }
  }

  private static void checkProperties() {
    String path = "./src/main/resources/i18n/";
    String extension = ".properties";

    Map<String, List<String>> map =
        Arrays.stream(
                Optional.ofNullable(
                        Paths.get(path).toFile().list((dir, name) -> name.endsWith(extension)))
                    .orElse(new String[0]))
            .map(s -> s.split(extension)[0])
            .collect(
                Collectors.groupingBy(
                    filename -> filename.split("_")[0],
                    Collectors.mapping(l -> path + l + extension, Collectors.toList())));

    for (Map.Entry<String, List<String>> entry : map.entrySet()) {
      List<String> twList =
          entry
              .getValue()
              .stream()
              .filter(fileName -> fileName.contains("zh_TW"))
              .collect(Collectors.toList());
      List<String> otherList =
          entry
              .getValue()
              .stream()
              .filter(fileName -> !fileName.contains("zh_TW"))
              .collect(Collectors.toList());
      if (twList.isEmpty() || otherList.isEmpty()) continue;

      try {
        Set<String> originKeySet = getKeys(twList.get(0));
        for (String target : otherList) {
          Set<String> targetKeySet = getKeys(target);
          Set<String> missingSet =
              originKeySet
                  .parallelStream()
                  .filter(k -> !targetKeySet.contains(k))
                  .collect(Collectors.toSet());
          if (!missingSet.isEmpty()) {
            log.error(
                "Missing Key on {} \nMissing Keys: {}",
                Paths.get(target).getFileName(),
                missingSet);
            System.exit(1);
          }
        }
      } catch (Exception e) {
        log.error("check i18n properties error.", e);
        System.exit(1);
      }
    }
  }

  private static Set<String> getKeys(String fileName) throws IOException {
    Properties properties = new Properties();
    properties.load(new FileInputStream(fileName));
    return new HashSet(Collections.list(properties.propertyNames()));
  }
}
