package jamiebalfour.parsers.json;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import jamiebalfour.zpe.parser.ZenithParsingEngine;
import jamiebalfour.zpe.types.Null;
import jamiebalfour.zpe.types.ZPEOrderedAssociativeArray;

/*
 * Simple JSON parser that use the ZPEParser to translate
 * it to other types.
 * Open sourced 2017 by Jamie B Balfour.
 */

public class ZenithJSONParser {

  ZenithParsingEngine parser;

  public static void main(String[] args) {
    // Run
    try {
      String s = jamiebalfour.HelperFunctions.ReadFileAsString("sysinfo.json", "utf-8");
      ZenithJSONParser jbjson = new ZenithJSONParser();
      Object json = jbjson.ParseJSONString(s);
      System.out.println(json);
      System.out.println(jbjson.ParseJSONObject(json));
      System.out.println("Done");
    } catch (IOException e) {
      System.err.println("File not found!");
    }

  }

  private String line_trimmer(String s) {
    // Small line trimmer that removes ; and comments

    String out = "";

    Scanner scanner = new Scanner(s);
    while (scanner.hasNextLine()) {
      String ln = scanner.nextLine();

      ln = ln.trim();

      if (!ln.equals(""))
        out += ln + " ";
    }

    scanner.close();

    return out;
    // Line trimmer ends here
  }

  public String ParseJSONObject(Object o) {

    if (o instanceof Map) {
      return MapToJSON(o);
    } else if (o instanceof List) {
      return ListToJSON(o);
    } else if (o instanceof jamiebalfour.zpe.core.ZPEObject) {
      return ZPEObjectToJSON(o);
    }
    return "";
  }

  //We need to return an object as we may return a list or an assoc array.
  public Object ParseJSONString(String json) {

    // Initiate parser and bytecodes
    ZenithJSONParserByteCodes bytecodes = new ZenithJSONParserByteCodes();
    parser = new ZenithParsingEngine(line_trimmer(json), false, bytecodes);

    Object out = null;

    parser.getNextSymbol();

    if (parser.getCurrentSymbol() == ZenithJSONParserByteCodes.LSQBR) {
      out = JSON_To_List();
    } else if (parser.getCurrentSymbol() == ZenithJSONParserByteCodes.LBRACE) {
      out = JSON_To_Map();
    }

    return out;

  }

  private String GetInnerValue(Object o) {

    if (o instanceof Map) {
      return MapToJSON(o);
    } else if (o instanceof List) {
      return ListToJSON(o);
    } else if (o instanceof jamiebalfour.zpe.core.ZPEObject) {
      return ZPEObjectToJSON(o);
    } else if (o instanceof Integer || o instanceof Double || o instanceof Long) {
        return o.toString();
    } else {
      return "\"" + o.toString() + "\"";
    }

  }

  private String ZPEObjectToJSON(Object obj) {

    jamiebalfour.zpe.core.ZPEObject o = (jamiebalfour.zpe.core.ZPEObject) obj;

    String out = "{";
    int size = o.getProperties().length;
    int i = 0;
    for(Object var : o.getProperties()) {
      Object v = o.getProperty(var.toString());
      if(v instanceof String) {
        v = "\"" + v.toString() + "\"";
      }
      out += "\"" + var.toString() + "\"" + ":" + v.toString();
      if (i + 1 != size) {
        out += ",";
      }
      i++;
    }

    out += "}";

    return out;
  }

  private String ListToJSON(Object o) {

    @SuppressWarnings("rawtypes")
    List x = (List) o;

    String out = "[";

    for (int i = 0; i < x.size(); i++) {
      out += GetInnerValue(x.get(i));
      if (i + 1 != x.size()) {
        out += ",";
      }
    }

    return out + "]";
  }

  private String MapToJSON(Object o) {

    @SuppressWarnings("rawtypes")
    Map x = (Map) o;

    String out = "{";
    Object[] s;
    //Maintain order of ordered assoc arrays
    if(x instanceof ZPEOrderedAssociativeArray) {
      s = ((ZPEOrderedAssociativeArray) x).keys();
    } else {
      s = x.keySet().toArray();
    }


    for (int i = 0; i < s.length; i++) {
      out += "\"" + s[i] + "\"" + " : " + GetInnerValue(x.get(s[i]));
      if (i + 1 != x.size()) {
        out += ",";
      }
    }

    return out + "}";

  }

  private Object JSONValueToValueObject() {
    int symb = parser.getCurrentSymbol();
    if (symb == ZenithJSONParserByteCodes.STRING || symb == ZenithJSONParserByteCodes.DOUBLE
        || symb == ZenithJSONParserByteCodes.INT || symb == ZenithJSONParserByteCodes.BOOL) {
      return parser.getCurrentWord();
    }
    if (symb == ZenithJSONParserByteCodes.NULL) {
      return new Null();
    }
    if (symb == ZenithJSONParserByteCodes.LSQBR) {
      return JSON_To_List();
    }
    if (symb == ZenithJSONParserByteCodes.LBRACE) {
      return JSON_To_Map();
    }

    throw_error("ZenithJSONParser: Unexpected value");
    System.exit(-1);
    return null;
  }

  private jamiebalfour.zpe.types.ZPEList JSON_To_List() {

    jamiebalfour.zpe.types.ZPEList out = null;
    out = new jamiebalfour.zpe.types.ZPEList();

    if (parser.getCurrentSymbol() != ZenithJSONParserByteCodes.LSQBR) {
      throw_error("No LSQBR found.");
    }

    parser.getNextSymbol();

    if(parser.getCurrentSymbol() == ZenithJSONParserByteCodes.RSQBR) {
      return out;
    }


    while (true) {

      out.add(JSONValueToValueObject());

      if (parser.getNextSymbol() != ZenithJSONParserByteCodes.COMMA) {
        break;
      }
      parser.getNextSymbol();
    }

    if (parser.getCurrentSymbol() != ZenithJSONParserByteCodes.RSQBR) {
      throw_error("No RSQBR found.");
    }
    return out;

  }

  private jamiebalfour.zpe.types.ZPEAssociativeArray JSON_To_Map() {

    jamiebalfour.zpe.types.ZPEAssociativeArray out = null;

    // Using ZPETypes, we use the ZPEAssociativeArray
    out = new jamiebalfour.zpe.types.ZPEAssociativeArray();

    //Must start with a brace {
    if (parser.getCurrentSymbol() != ZenithJSONParserByteCodes.LBRACE) {
      throw_error("No LBRACE found.");
    }

    parser.getNextSymbol();

    if (parser.getCurrentSymbol() == ZenithJSONParserByteCodes.RBRACE) {
        return out;
    }

    while (true) {
      //We must always find a string as the next symbol
      if (parser.getCurrentSymbol() != ZenithJSONParserByteCodes.STRING) {
        throw_error("No STRING found.");
      }

      //Then our key would be this string
      String key = parser.getCurrentWord();

      //Next a colon
      if (parser.getNextSymbol() != ZenithJSONParserByteCodes.COLON) {
        throw_error("No COLON found.");
      }

      //Skip the colon
      parser.getNextSymbol();

      //Get the object as a value
      Object value = JSONValueToValueObject();

      //Finally put the key to value pair in output
      out.put(key, value);

      //If we've got no more, then look no further
      if (parser.getNextSymbol() != ZenithJSONParserByteCodes.COMMA) {
        break;
      }

      parser.getNextSymbol();
    }

    if (parser.getCurrentSymbol() != ZenithJSONParserByteCodes.RBRACE) {
      throw_error("No RBRACE found.");
    }

    return out;
  }

  /*@SuppressWarnings("unchecked")
  private jamiebalfour.zpe.core.ZPEObject JSON_To_Object() {

    jamiebalfour.zpe.core.ZPEObject out = null;

    // Using ZPETypes, we use the ZPEAssociativeArray
    out = new jamiebalfour.zpe.core.ZPEObject();

    //Must start with a brace {
    if (parser.GetCurrentSymbol() != ZenithJSONParserByteCodes.LBRACE) {
      throw_error("No LBRACE found.");
    }

    while (true) {
      //We must always find a string as the next symbol
      if (parser.GetNextSymbol() != ZenithJSONParserByteCodes.STRING) {
        throw_error("No STRING found.");
      }

      //Then our key would be this string
      String key = parser.GetCurrentWord();

      //Next a colon
      if (parser.GetNextSymbol() != ZenithJSONParserByteCodes.COLON) {
        throw_error("No COLON found.");
      }

      //Skip the colon
      parser.GetNextSymbol();

      //Get the object as a value
      Object value = JSONValueToValueObject();

      //Finally put the key to value pair in output
      out.setVariable(key, value);

      //If we've got no more, then look no further
      if (parser.GetNextSymbol() != ZenithJSONParserByteCodes.COMMA) {
        break;
      }
    }

    if (parser.GetCurrentSymbol() != ZenithJSONParserByteCodes.RBRACE) {
      throw_error("No RBRACE found.");
    }

    return out;
  }*/

  private void throw_error(String s) {
    System.err.println(s);
    System.exit(1);
  }

}
