/**
   * takes a query object and calls the appropriate sub component that answers the given query
   *
   * @param query the query that has to be answered
   * @return a string in the answer exchange format that will be sent to the frontend
   */
  public static String handleQuery(Query query) {
    // interval search
    String answer;
    if (query.x_Search == null) {
      // check if necessary fields are given
      if (query.i_Start == -1 || query.i_End == -1) {
        // query.response_format = "Wrong interval given";
        return "Wrong interval given";
      } else if (query.s_Chromosome == -1) {
        // query.response_format = "No chromosome given";
        return "No chromosome given";
      } else if (query.s_Source == -1) {
        // query.response_format = "No source given";
        return "No source given";
      }
      // correct query format
      else {
        // Send query to IndexController
        // Translation from query to intervalst.query
        // needs to be unified
        /*
        short s_SourceNo=(short)0;
        if (query.source.equals("dbSNP")) {
          				s_SourceNo = (short) 0;
          			}
          			if (query.source.equals("1000GenomesProject")){
          				s_SourceNo =(short) 1;
          			}
          			*/

        Query[] q_Query = {query};
        QueryAnswer temp_answer = indexController.answerQuery(q_Query);
        answer = answerQuery(temp_answer);

        /*
          		//	byte temp = query.chromosome.get();
          		//	byte[] b={temp};
          			//CRITICAL PART. change query format into intervallst.query . answerquery throws nullpointer
          			byte[] b=new byte[0];
          			byte[] c=new byte[0];
          			int i=query.position[0];
          			int j=query.position[1];
        IntervalST.Query intervalst_query=new IntervalST.Query(1,i,j,s_SourceNo,Integer.toString(query.chromosome).getBytes(),null,null);
        IntervalST.Query[] queries={intervalst_query};
        IntervalST.Answer x_Result=indexController.answerQuery(queries);
                    System.out.println("Length of AnswerList: "+x_Result.x_List.size());
                    if (query.hasDetail) {
                    	String sequence = Chromosome_reader.retrieve_sequence(i,j,Integer.toString(query.chromosome));
                    }
                    return query;
                    */
      }
    }
    // gene name search
    else {
      // test for prefix query
      if (query.b_isPrefix) {
        /*
        // This needs to be replaced!
        String[] genenames = GeneTranslator.completeGeneName(query.search);
        */
        String[] genenames = {"FOXP2", "FOXP4"};
        JSONObject obj = new JSONObject();
        obj.put("source", query.s_Source);
        obj.put("chromosome", query.s_Chromosome);
        JSONArray names = new JSONArray();
        for (int i = 0; i < genenames.length; i++) {
          names.add(genenames[i]);
        }
        obj.put("prefix", names);
        StringWriter out = new StringWriter();
        try {
          obj.writeJSONString(out);
        } catch (IOException e) {
          e.printStackTrace();
        }
        answer = out.toString();

      } else {
        if (query.x_Search == "getInitialSources") {
          System.out.println("getInitialSources");
          return null;
        } else {
          String[] interval = GeneTranslator.translateToIntervall(query.x_Search);
          JSONObject pos_obj = new JSONObject();
          JSONObject obj = new JSONObject();
          pos_obj.put("from", Integer.valueOf(interval[0]));
          pos_obj.put("to", Integer.valueOf(interval[1]));
          obj.put("search", query.x_Search);
          obj.put("position", pos_obj);

          StringWriter out = new StringWriter();
          try {
            obj.writeJSONString(out);
          } catch (IOException e) {
            e.printStackTrace();
          }
          answer = out.toString();
        }
      }
    }
    // queryID_pool.add(query.queryID);
    return answer;
  }
 /** @see Servlet#init() */
 public void init() throws ServletException {
   int success = geneTranslator.buildGeneTranslator();
   indexController = new IndexController();
 }
/** Servlet implementation class QueryReceiver */
@WebServlet("/QueryReceiver")
public class QueryReceiver extends GenericServlet {
  private static final long serialVersionUID = 1L;

  /** @see GenericServlet#GenericServlet() */
  static GeneTranslator geneTranslator = new GeneTranslator();

  int success = geneTranslator.buildGeneTranslator();
  static IndexController indexController;
  static HashSet<Integer> queryID_pool = new HashSet<Integer>(10000);

  public static class Filter {
    boolean male;
    boolean female;
    Integer relfrq;
    String[] origin;
  }

  /*
  public static class Query{
  	public short source;
  	public short chromosome;
  	public int from;
  	public int to;
  	public int zoom_level;
  	public boolean hasDetail;
  	public boolean isPrefix;
  	public String search;
  	public String[] origin;
  	public short gender;
  	public int relfrq;


  	Query() {
  		this.from = -1;
  		this.to = -1;
  		this.zoom_level = -1;
  		this.source = (short) -1;
  		this.gender = (short) -1;
  		this.relfrq = -1;
  		this.chromosome = -1;
  	}







  }
  */

  /**
   * tries to parse the given json message into a query object
   *
   * @param json_file a json that fulfills the exchange format between frontend and middleware
   * @return a query object that holds the information in the json message
   */
  public static Query parse(String json_file) {

    try {
      Query query = new Query();
      Object obj = JSONValue.parse(json_file);
      // System.out.println(obj);
      JSONObject j = (JSONObject) obj;
      // System.out.println(j);
      // sources
      if (j.get("source") != null) {
        /*
        JSONArray jArray = (JSONArray)j.get("sources");
        ArrayList<String> sources = new ArrayList<String>();
        for (int i = 0; i < jArray.size(); i++){
        	sources.add(jArray.get(i).toString());
        }
        */
        if (((String) (j.get("source"))).equals("dbSNP")) {
          query.s_Source = (short) 0;
        }
        if (((String) (j.get("source"))).equals("1000GenomesProject")) {
          query.s_Source = (short) 1;
        }
      }
      // chromosome
      if (j.get("chromosome") != null) {
        if (((String) j.get("chromosome")).equals("X")) {
          query.s_Chromosome = 23;
        } else if (((String) j.get("chromosome")).equals("Y")) {
          query.s_Chromosome = 24;
        } else if (((String) j.get("chromosome")).equals("MT")) {
          query.s_Chromosome = 25;
        } else {
          query.s_Chromosome = (short) Integer.parseInt((String) j.get("chromosome"));
        }
      }
      // position
      if (j.get("intervall") != null) {
        JSONObject pos_obj = (JSONObject) j.get("intervall");
        query.i_Start = (int) (long) pos_obj.get("from");
        query.i_End = (int) (long) pos_obj.get("to");
        query.i_ZoomLevel = (int) (long) pos_obj.get("size");
      }
      // gene search
      if (j.get("search") != null) {
        query.x_Search = (String) j.get("search");
      }
      // prefix
      if (j.get("prefix") != null) {
        query.b_isPrefix = (boolean) j.get("prefix");
      } else {
        query.b_isPrefix = false;
      }
      /*
      // subintervals
      if (j.get("subintervals") != null){
      	query.subintervals = (int)(long) j.get("subintervals");
      }
      */
      // hasDetail flag
      if (j.get("hasDetail") != null) {
        query.b_hasDetail = (boolean) j.get("hasDetail");
      }
      if (j.get("filter") != null) {
        JSONObject fil_obj = (JSONObject) j.get("filter");
        if (((boolean) fil_obj.get("male")) && !((boolean) fil_obj.get("female"))) {
          query.s_Gender = (short) 0;
        } else if (!((boolean) fil_obj.get("male")) && ((boolean) fil_obj.get("female"))) {
          query.s_Gender = (short) 1;
        }
        query.i_RelFrq = (int) (long) fil_obj.get("relfrq");
        JSONArray orig_array = (JSONArray) fil_obj.get("origin");
        query.s_Country = new short[orig_array.size()];
        for (int i = 0; i < orig_array.size(); i++) {
          query.s_Country[i] = IndexController.getNumber((String) orig_array.get(i));
        }
      }
      return query;
    } catch (Exception e) {
      System.out.println("Unknown query format");
      return null;
    }

    /*
    try{
    	Query query = new Query();
    	Integer randomID = get_random_number();
    	query.queryID = randomID;
    	queryID_pool.remove(randomID);
    	Object obj = JSONValue.parse(json_file);
    	//System.out.println(obj);
    	JSONObject j = (JSONObject) obj;
    	//System.out.println(j);
    	// sources
    	if (j.get("source") != null){
    		query.source = (String)j.get("source");
    	}
    	// chromosome
    	if (j.get("chromosome") != null){
    		query.chromosome = (int) (long) j.get("chromosome");
    	}
    	// position
    	if (j.get("intervall") != null){
    		JSONObject pos_obj = (JSONObject) j.get("intervall");
    		Integer[] pos = new Integer[2];
    		pos[0] =  (int)(long)pos_obj.get("from");
    		pos[1] =  (int)(long)pos_obj.get("to");
    		query.zoom_level = (int)(long)pos_obj.get("size");
    		query.position = pos;
    	}
    	// gene search
    	if (j.get("search") != null){
    		query.search = (String) j.get("search");
    	}
    	// prefix
    	if (j.get("prefix") != null){
    		query.isPrefix = (boolean)j.get("prefix");
    	}
    	else{
    		query.isPrefix = false;
    	}

    	// hasDetail flag
    	if (j.get("hasDetail") != null){
    		query.hasDetail = (boolean) j.get("hasDetail");
    	}
    	if (j.get("filter") != null){
    		Filter fil = new Filter();
    		JSONObject fil_obj = (JSONObject)j.get("filter");
    		fil.male = (boolean)fil_obj.get("male");
    		fil.female = (boolean) fil_obj.get("female");
    		fil.relfrq = (int) (long)fil_obj.get("origin");
    		JSONArray orig_array = (JSONArray) fil_obj.get("origin");
    		String[] orig = new String[orig_array.size()];
    		for (int i = 0; i < orig_array.size(); i++){
    			orig[i] = (String)orig_array.get(i);
    		}
    		fil.origin = orig;
    		query.filter = fil;
    	}
    	return query;
    }
    catch(Exception e){
    	System.out.println("Unknown query format");
    	return null;
    }
    */
  }

  /**
   * counts the subintervals of the mutations in the answer object
   *
   * @param answer an answer object that was generated through a query
   * @return Integer array holding the amount of mutations that intersect the subintervals
   */
  public static Integer[] count_mutations(QueryAnswer answer) {
    Integer[] pos = answer.position;
    float interval_length = pos[1] - pos[0];
    int subinterval_length = answer.zoom_level;
    int number_subints = (int) Math.ceil(interval_length / subinterval_length);
    Integer[] counts = new Integer[number_subints];
    for (int i = 0; i < counts.length; i++) {
      counts[i] = 0;
    }
    int[] subint_left_boundaries = new int[number_subints];
    int left = pos[0];
    for (int i = 0; i < number_subints; i++) {
      subint_left_boundaries[i] = left + i * subinterval_length;
    }

    for (IntervalST.Mutation mut : answer.mutations) {
      int mut_low = mut.i_Low;
      int mut_high = mut.i_High;
      int last_index = subint_left_boundaries.length - 1;
      for (int i = 0; i < last_index; i++) {
        int lower = Math.max(subint_left_boundaries[i], mut_low);
        int higher = Math.min(subint_left_boundaries[i + 1], mut_high);
        if (lower <= higher) {
          counts[i]++;
        }
      }
      if (mut_high >= subint_left_boundaries[last_index]) {
        counts[last_index]++;
      }
    }

    return counts;
  }
  /**
   * parses an answer object into the json exchange format for an answer
   *
   * @param answer the answer object that should be translated
   * @return json string in the exchange format between frontend and middleware
   */
  public static String answerQuery(QueryAnswer answer) {
    JSONObject obj = new JSONObject();
    obj.put("source", answer.source);
    // add chromosome attribute to json answer
    obj.put("chromosome", answer.chromosome);
    JSONObject from_to = new JSONObject();
    from_to.put("from", answer.position[0]);
    from_to.put("to", answer.position[1]);
    obj.put("position", from_to);
    String response;
    if (answer.hasDetail) {
      JSONObject details = new JSONObject();
      details.put("refseq", answer.refseq);
      // create json_array for found mutations
      JSONArray mutations_array = new JSONArray();
      for (IntervalST.Mutation x : answer.mutations) {
        // json object for single mutation, that was found
        JSONObject mut = new JSONObject();

        // name of mutation, need to convert from byte array to string
        mut.put("name", new String(x.b_RefName));

        // json object for position of mutation's interval, with "to" and "from" as attributes
        JSONObject mut_pos = new JSONObject();
        mut_pos.put("from", x.i_Low);
        mut_pos.put("to", x.i_High);
        mut.put("position", mut_pos);

        // json object for metadata
        JSONObject meta = new JSONObject();

        // json object for gender
        JSONObject gender = new JSONObject();
        // -1 for no filter on gender, 0 for male and 1 for female
        if (x.s_Gender == -1) {
          gender.put("m", true);
          gender.put("w", true);
        } else if (x.s_Gender == 0) {
          gender.put("m", true);
          gender.put("w", false);
        } else if (x.s_Gender == 1) {
          gender.put("m", false);
          gender.put("w", true);
        } else {
          gender = null;
        }
        meta.put("gender", gender);

        // add country
        meta.put("origin", IndexController.getCountry(x.s_Country));

        // downloadtime
        meta.put("downloadtime", x.s_Date);

        // add metadata json object to mutation json object
        mut.put("metadata", meta);

        // add sequence of basis pairs, first need to convert integer to string
        if (answer.chromosome == 23) {
          mut.put("mutationseq", Chromosome_reader.retrieve_sequence(x.i_Low, x.i_High, "X"));
        } else if (answer.chromosome == 24) {
          mut.put("mutationseq", Chromosome_reader.retrieve_sequence(x.i_Low, x.i_High, "Y"));
        } else if (answer.chromosome == 25) {
          mut.put("mutationseq", Chromosome_reader.retrieve_sequence(x.i_Low, x.i_High, "MT"));
        } else {
          mut.put(
              "mutationseq",
              Chromosome_reader.retrieve_sequence(x.i_Low, x.i_High, answer.chromosome.toString()));
        }
        // add json object for mutation x to json array for all mutations that were found
        mutations_array.add(mut);
      }
      details.put("mutations", mutations_array);
      obj.put("details", details);
      StringWriter out = new StringWriter();
      try {
        obj.writeJSONString(out);
      } catch (IOException e) {
        e.printStackTrace();
      }
      response = out.toString();

    } else {
      obj.put("details", null);
      // function to count occurrences of mutations in subintervals
      Integer[] counts = count_mutations(answer);
      JSONArray mutation_counts = new JSONArray();
      for (int i = 0; i < counts.length; i++) {
        mutation_counts.add(counts[i]);
      }
      obj.put("graph", mutation_counts);
      StringWriter out = new StringWriter();
      try {
        obj.writeJSONString(out);
      } catch (IOException e) {
        e.printStackTrace();
      }
      response = out.toString();
    }
    if (response != null) {
      return response;
    } else {
      return "Something went wrong while creating answer";
    }
  }

  /**
   * takes a query object and calls the appropriate sub component that answers the given query
   *
   * @param query the query that has to be answered
   * @return a string in the answer exchange format that will be sent to the frontend
   */
  public static String handleQuery(Query query) {
    // interval search
    String answer;
    if (query.x_Search == null) {
      // check if necessary fields are given
      if (query.i_Start == -1 || query.i_End == -1) {
        // query.response_format = "Wrong interval given";
        return "Wrong interval given";
      } else if (query.s_Chromosome == -1) {
        // query.response_format = "No chromosome given";
        return "No chromosome given";
      } else if (query.s_Source == -1) {
        // query.response_format = "No source given";
        return "No source given";
      }
      // correct query format
      else {
        // Send query to IndexController
        // Translation from query to intervalst.query
        // needs to be unified
        /*
        short s_SourceNo=(short)0;
        if (query.source.equals("dbSNP")) {
          				s_SourceNo = (short) 0;
          			}
          			if (query.source.equals("1000GenomesProject")){
          				s_SourceNo =(short) 1;
          			}
          			*/

        Query[] q_Query = {query};
        QueryAnswer temp_answer = indexController.answerQuery(q_Query);
        answer = answerQuery(temp_answer);

        /*
          		//	byte temp = query.chromosome.get();
          		//	byte[] b={temp};
          			//CRITICAL PART. change query format into intervallst.query . answerquery throws nullpointer
          			byte[] b=new byte[0];
          			byte[] c=new byte[0];
          			int i=query.position[0];
          			int j=query.position[1];
        IntervalST.Query intervalst_query=new IntervalST.Query(1,i,j,s_SourceNo,Integer.toString(query.chromosome).getBytes(),null,null);
        IntervalST.Query[] queries={intervalst_query};
        IntervalST.Answer x_Result=indexController.answerQuery(queries);
                    System.out.println("Length of AnswerList: "+x_Result.x_List.size());
                    if (query.hasDetail) {
                    	String sequence = Chromosome_reader.retrieve_sequence(i,j,Integer.toString(query.chromosome));
                    }
                    return query;
                    */
      }
    }
    // gene name search
    else {
      // test for prefix query
      if (query.b_isPrefix) {
        /*
        // This needs to be replaced!
        String[] genenames = GeneTranslator.completeGeneName(query.search);
        */
        String[] genenames = {"FOXP2", "FOXP4"};
        JSONObject obj = new JSONObject();
        obj.put("source", query.s_Source);
        obj.put("chromosome", query.s_Chromosome);
        JSONArray names = new JSONArray();
        for (int i = 0; i < genenames.length; i++) {
          names.add(genenames[i]);
        }
        obj.put("prefix", names);
        StringWriter out = new StringWriter();
        try {
          obj.writeJSONString(out);
        } catch (IOException e) {
          e.printStackTrace();
        }
        answer = out.toString();

      } else {
        if (query.x_Search == "getInitialSources") {
          System.out.println("getInitialSources");
          return null;
        } else {
          String[] interval = GeneTranslator.translateToIntervall(query.x_Search);
          JSONObject pos_obj = new JSONObject();
          JSONObject obj = new JSONObject();
          pos_obj.put("from", Integer.valueOf(interval[0]));
          pos_obj.put("to", Integer.valueOf(interval[1]));
          obj.put("search", query.x_Search);
          obj.put("position", pos_obj);

          StringWriter out = new StringWriter();
          try {
            obj.writeJSONString(out);
          } catch (IOException e) {
            e.printStackTrace();
          }
          answer = out.toString();
        }
      }
    }
    // queryID_pool.add(query.queryID);
    return answer;
  }
  /** @see Servlet#init() */
  public void init() throws ServletException {
    int success = geneTranslator.buildGeneTranslator();
    indexController = new IndexController();
  }

  /** @see Servlet#service(ServletRequest request, ServletResponse response) */
  public void service(ServletRequest request, ServletResponse response)
      throws ServletException, IOException {
    // TODO Auto-generated method stub
    String input_string = request.getParameter("text");
    PrintWriter out = response.getWriter();
    Query parsed_query = parse(input_string);
    // Wrong input format -> null returned in parse function
    if (parsed_query == null) {
      String message = "Unknown query format";
      out.write(message.toCharArray());
    }
    // correct input format
    else {
      String answer = handleQuery(parsed_query);
      out.write(answer);
    }
  }
}