package jp.ac.osaka_u.sparql;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Session;

import jp.ac.osaka_u.sanken.sparql.EndpointSettings;
import jp.ac.osaka_u.sanken.sparql.FindCondition;
import jp.ac.osaka_u.sanken.sparql.SparqlAccessor;
import jp.ac.osaka_u.sanken.sparql.SparqlAccessorFactory;
import jp.ac.osaka_u.sanken.sparql.SparqlResultSet;
import jp.ac.osaka_u.sanken.sparql.TargetPredicate;
import net.arnx.jsonic.JSON;

import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.RDFNode;

/**
 * TODO 未使用なので最終的に確認の上削除
 * Servlet implementation class SpeciesFinder
 */
public class SparqlFinder extends HttpServlet {
	private static final long serialVersionUID = 1L;

	private History history;

	private static final String DATA_TYPE = "species";

	private static final int FIND_TYPE_PART = 1;
	private static final int FIND_TYPE_FULL = 2;
	
    /**
     * @see HttpServlet#HttpServlet()
     */
    public SparqlFinder() {
        super();
        // TODO Auto-generated constructor stub
    }
    
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		process(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		process(request, response);
	}


	private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		String mode = (String)request.getParameter("mode");

		HttpSession session = request.getSession(true);

		this.history = HistoryManager.getHistoryData(DATA_TYPE, request);
/*
		if (mode == null){
			error(response, "mode null");
			return;
		} else{
			error(response, "mode null");
		}
*/


	}
/*
	private void find(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
		String param = (String)request.getParameter("param"); // 検索条件
		List<Map<String, String>> findConditionMap = null;
		if (param != null){
			findConditionMap = (List<Map<String, String>>)JSON.decode(param);
		}
		List<FindCondition> findConditions = new ArrayList<FindCondition>();

		List<Map<String, String>> targetList = null;
		String target = (String)request.getParameter("target"); // 検索対象
		if (target != null){
			targetList = (List<Map<String, String>>)JSON.decode(target);
		}
		List<TargetPredicate> targetPredicates = new ArrayList<TargetPredicate>();
		
		String limitStr = (String)request.getParameter("limit"); // リミット
		Integer limit = null;
		limit = Integer.parseInt(limitStr);

		String offsetStr = (String)request.getParameter("offset"); // オフセット
		Integer offset = null;
		offset = Integer.parseInt(offsetStr);

		
		
		String head = (String)request.getParameter("head"); // 検索ヘッダ
		if (head != null){
			head = URLDecoder.decode(head, "UTF-8");
		}

		if (findConditionMap != null && targetList != null){
			for (Map<String, String> p : findConditionMap){
				findConditions.add(new FindCondition(URLDecoder.decode(p.get("predicate"), "UTF-8"), URLDecoder.decode(p.get("word"), "UTF-8"), p.get("type"), p.get("and_or")));
			}
			for (Map<String, String> t : targetList){
				targetPredicates.add(new TargetPredicate(t.get("valiable"), t.get("pred")));
			}
			this.history.initHistory();

			find(head, findConditions, targetPredicates, limit, offset, request, response);

		}
		HistoryManager.updateHistoryData(this.history, DATA_TYPE, request);
	}

	private void findMenuQuery(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
		String target = (String)request.getParameter("target"); // 検索対象
		List<Map<String, String>> targetList = null;
		if (target != null){
			targetList = (List<Map<String, String>>)JSON.decode(target);
		}
		List<TargetPredicate> targetPredicates = new ArrayList<TargetPredicate>();
		
		
		String menuQuery = (String)request.getParameter("menuquery"); // 検索対象
		menuQuery = URLDecoder.decode(menuQuery, "UTF-8");
		menuQuery = URLDecoder.decode(menuQuery, "UTF-8");
		String head = (String)request.getParameter("head"); // 検索ヘッダ
		if (head != null){
			head = URLDecoder.decode(head, "UTF-8");
		}

		String limitStr = (String)request.getParameter("limit"); // リミット
		Integer limit = null;
		limit = Integer.parseInt(limitStr);

		String offsetStr = (String)request.getParameter("offset"); // オフセット
		Integer offset = null;
		offset = Integer.parseInt(offsetStr);

		
		if (targetList != null && menuQuery != null){
			for (Map<String, String> t : targetList){
				targetPredicates.add(new TargetPredicate(t.get("valiable"), t.get("pred")));
			}

			this.history.initHistory();

			find(head, menuQuery, targetPredicates, limit, offset, request, response);

		}
		HistoryManager.updateHistoryData(this.history, DATA_TYPE, request);
	}

	private void getMenuQuery(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
		String param = (String)request.getParameter("param"); // 述語
		List<FindCondition> conds = new ArrayList<FindCondition>();
		List<Map<String, String>> paramList = null;
		if (param != null){
			paramList = (List<Map<String, String>>)JSON.decode(param);
		}
		if (paramList != null){
			for (Map<String, String> p : paramList){
				conds.add(new FindCondition(URLDecoder.decode(p.get("predicate"), "UTF-8"), URLDecoder.decode(p.get("word"), "UTF-8"), p.get("type"), p.get("and_or")));
			}
		}
		count(conds, request, response);
	}

	private void count(List<FindCondition> conditions, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Map<String, String> ret = new HashMap<String, String>();
		EndpointSettings es = getEndpointSettings(request);

		if (es == null){
			error(response, "endpoint hasn't set");
			return;
		}
		String query = getFindQuery(conditions);
		int count = count(conditions, es);
		ret.put("query", query);
		ret.put("count", String.valueOf(count));

		String json = JSON.encode(ret);
		
		response(request, response, json);
	}

	
	private void find(String head, List<FindCondition> conditions, List<TargetPredicate> targetList, Integer limit, Integer offset, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		EndpointSettings es = getEndpointSettings(request);

		if (es == null){
			error(response, "endpoint hasn't set");
			return;
		}
		
		SparqlAccessor sa = SparqlAccessorFactory.createSparqlAccessor(es);

		List<Map<String, String>> ret = new ArrayList<Map<String, String>>();
		try {
			SparqlResultSet rs = sa.find(conditions, targetList, head, limit, offset);
			List<Map<String, RDFNode>> results = rs.getDefaultResult();
			
			// 結果をまずはlistに追加
			Map<String, String> prevData = new HashMap<String, String>();
			if (results != null){
				for (Map<String, RDFNode> result : results){
					HashMap<String, String> map = new HashMap<String, String>();

					// subjectは必ず指定
					RDFNode s = result.get("s");
					String prevS = prevData.get("s");
					if (prevS != null && s != null && getString(s).equals(prevS)){
						map.put("s", "<same>same</same>"); // TODO 前データと同じ
					} else {
						map.put("s", getString(s));
					}
					prevData.put("s", getString(s));

					for (TargetPredicate preds : targetList){
						String prevRDF = prevData.get(preds.valiable);

						RDFNode valiable = result.get(preds.valiable);
						if (prevRDF != null && valiable != null && getString(valiable).equals(prevRDF)){
							map.put(preds.valiable, "<same>same</same>"); // TODO 前データと同じ
						} else {
							map.put(preds.valiable, getString(valiable));
						}
						prevData.put(preds.valiable, getString(valiable));
					}
					ret.add(map);
				}
			}

			ResultPack rp = new ResultPack();
			rp.setResults(ret);
			rp.setHasNext(rs.isHasNext());
			rp.setHasPrev(false);

			
			
			String json = JSON.encode(rp);
			
			response(request, response, json);
		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}


	}

	private void find(String head, String conditions, List<TargetPredicate> targetList, Integer limit, Integer offset, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		EndpointSettings es = getEndpointSettings(request);

		if (es == null){
			error(response, "endpoint hasn't set");
			return;
		}

		SparqlAccessor sa = SparqlAccessorFactory.createSparqlAccessor(es);

		List<Map<String, String>> ret = new ArrayList<Map<String, String>>();
		try {
			SparqlResultSet rs = sa.find(conditions, targetList, head, limit, offset);
			List<Map<String, RDFNode>> results = rs.getDefaultResult();
			
			// 結果をまずはlistに追加
			Map<String, String> prevData = new HashMap<String, String>();
			if (results != null){
				for (Map<String, RDFNode> result : results){
					HashMap<String, String> map = new HashMap<String, String>();

					// subjectは必ず指定
					RDFNode s = result.get("s");
					String prevS = prevData.get("s");
					if (prevS != null && s != null && getString(s).equals(prevS)){
						map.put("s", "<same>same</same>"); // TODO 前データと同じ
					} else {
						map.put("s", getString(s));
					}
					prevData.put("s", getString(s));

					for (TargetPredicate preds : targetList){
						String prevRDF = prevData.get(preds.valiable);

						RDFNode valiable = result.get(preds.valiable);
						if (prevRDF != null && valiable != null && getString(valiable).equals(prevRDF)){
							map.put(preds.valiable, "<same>same</same>"); // TODO 前データと同じ
						} else {
							map.put(preds.valiable, getString(valiable));
						}
						prevData.put(preds.valiable, getString(valiable));
					}
					ret.add(map);
				}
			}

			ResultPack rp = new ResultPack();
			rp.setResults(ret);
			rp.setHasNext(rs.isHasNext());
			rp.setHasPrev(false);

			
			
			String json = JSON.encode(rp);
			
			response(request, response, json);
		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
	}

	

	private int count(List<FindCondition> conditions, EndpointSettings es){
		String query = null;

		// query header
		query =
			"prefix solr:<http://franz.com/ns/allegrograph/4.5/solr/>\n" +
			"prefix dbp:<http://ja.dbpedia.org/property/>\n" +
			"prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>\n" +
			"prefix foaf: <http://xmlns.com/foaf/0.1/>\n" +
			"\n" +
			"select (count(?s) as ?count) ";

		query += "{\n";
		// 検索条件
		query += getFindQuery(conditions);

		// query footer
		query += "}\n";

		return count(query, es);

	}

	/*
	private ResultPack find(String head, List<FindCondition> conditions, List<TargetPredicate> targetList, EndpointSettings es){
		String query = null;

		// query header
		query =
			"prefix solr:<http://franz.com/ns/allegrograph/4.5/solr/>\n" +
			"prefix dbp:<http://ja.dbpedia.org/property/>\n" +
			"prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>\n" +
			"prefix foaf: <http://xmlns.com/foaf/0.1/>\n" +
			"\n" +
			"select distinct ?s ";
		for (TargetPredicate preds : targetList){
			query += "?"+preds.valiable + " ";
		}

		query += "{\n";
		if (head != null){
			query += head;
		}
		// 検索条件
		query += getFindQuery(conditions);

		// query footer
		for (TargetPredicate preds : targetList){
			query += "OPTIONAL {?s " + preds.predicate + " ?"+preds.valiable + ". }\n";
		}
		query += "}\n";

		return findWord(query, targetList, es);

	}*/
/*
	private ResultPack find(String head, String conditions, List<TargetPredicate> targetList, EndpointSettings es){
		String query = null;

		// query header
		query =
			"prefix solr:<http://franz.com/ns/allegrograph/4.5/solr/>\n" +
			"prefix dbp:<http://ja.dbpedia.org/property/>\n" +
			"prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>\n" +
			"prefix foaf: <http://xmlns.com/foaf/0.1/>\n" +
			"\n" +
			"select distinct ?s ";
		for (TargetPredicate preds : targetList){
			query += "?"+preds.valiable + " ";
		}

		query += "{\n";
		if (head != null){
			query += head;
		}
		// 検索条件
		query += conditions;

		// query footer
		for (TargetPredicate preds : targetList){
			query += "OPTIONAL {?s " + preds.predicate + " ?"+preds.valiable + ". }\n";
		}
		query += "}\n";

		return findWord(query, targetList, es);

	}
*/
	/*
	private ResultPack findWord(String query, List<TargetPredicate> targetList, EndpointSettings es){
		ResultPack rp = new ResultPack();
		List<Map<String, String>> ret = new ArrayList<Map<String, String>>();

		SparqlAccessor sa = SparqlAccessorFactory.createSparqlAccessor(es);
		try {

			List<Map<String, RDFNode>> results = sa.executeQuery(query);

			Map<String, String> prevData = new HashMap<String, String>();
			if (results != null){
				for (Map<String, RDFNode> result : results){
					HashMap<String, String> map = new HashMap<String, String>();

					// subjectは必ず指定
					RDFNode s = result.get("s");
					String prevS = prevData.get("s");
					if (prevS != null && s != null && getString(s).equals(prevS)){
						map.put("s", "<same>same</same>"); // TODO 前データと同じ
					} else {
						map.put("s", getString(s));
					}
					prevData.put("s", getString(s));

					for (TargetPredicate preds : targetList){
						String prevRDF = prevData.get(preds.valiable);

						RDFNode valiable = result.get(preds.valiable);
						if (prevRDF != null && valiable != null && getString(valiable).equals(prevRDF)){
							map.put(preds.valiable, "<same>same</same>"); // TODO 前データと同じ
						} else {
							map.put(preds.valiable, getString(valiable));
						}
						prevData.put(preds.valiable, getString(valiable));
					}
					ret.add(map);
				}
			}

//			rp.setInputWord(word);
			rp.setResults(ret);
			rp.setHasNext(this.history.hasNext());
			rp.setHasPrev(false);

		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}


		return rp;
	}*/
/*
	// TODO これもAccessorに持っていくべき
	private int count(String query, EndpointSettings es){
		ResultPack rp = new ResultPack();
		List<Map<String, String>> ret = new ArrayList<Map<String, String>>();

		SparqlAccessor sa = SparqlAccessorFactory.createSparqlAccessor(es);
		try {

			List<Map<String, RDFNode>> results = sa.executeQuery(query);

			Map<String, String> prevData = new HashMap<String, String>();
			if (results != null){
				for (Map<String, RDFNode> result : results){
					HashMap<String, String> map = new HashMap<String, String>();

					RDFNode node = result.get("count");
					if (node.isLiteral()){
						Literal l = node.asLiteral();
						return l.getInt();
					}
				}
			}

		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}


		return -1;
	}

	
	
	private String getFindQuery(List<FindCondition> conditions){
		StringBuilder sb = new StringBuilder();

		int index = 0;
		for (FindCondition condition : conditions){
		
			int findType = condition.matchType;
			
			if (condition.andOr != null){
				sb.append(getPartQuery(condition.andOr.equals("and")));
			}
			sb.append(getPartQuery(condition.word, condition.predicate, index++, findType));
		}
		
		return sb.toString();
	}
	
	private String getPartQuery(String word, String type, int count, int findType){
		StringBuilder sb = new StringBuilder();
		sb.append("{\n");
		if (findType == FIND_TYPE_PART){
			sb.append("{\n");
			sb.append("    {?s " + type + " ?o" + count + ".}\n");
			sb.append("}\n");
			sb.append("  FILTER regex(?o" + count + ", \"" + word + "\")\n");
		} else {
			sb.append("{  ?s " + type + " \"" + word + "\"} union\n");
			sb.append("{  ?s " + type + " \"" + word + "\"@en} union\n");
			sb.append("{  ?s " + type + " \"" + word + "\"@ja}\n");
		}
		sb.append("}\n");
		return sb.toString();
	}
	
	private String getPartQuery(boolean isAnd){
		if (isAnd){
			return "";
		} else {
			return "union ";
		}
	}


	private String getString(RDFNode node){
		if (node == null){
			return null;
		}
		if (node.isLiteral()){
			return (node.asLiteral()).getString();
		} else {
			return (node.toString());
		}
	}

	private void getBioClasses(HttpServletResponse response, EndpointSettings es) throws IOException{

		List<String> bios = getBioClasses(es);

		String json = JSON.encode(bios);

		response.setContentType("application/json; charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.print(json);
		out.flush();

	}

	private List<String> getBioClasses(EndpointSettings es){
		List<String> bios = new ArrayList<String>();
		SparqlAccessor sa = SparqlAccessorFactory.createSparqlAccessor(es);

		String query =	"prefix dbp:<http://ja.dbpedia.org/property/>\n" +
						"select distinct ?o {\n" +
						"      ?s dbp:綱 ?o;\n" +
						"            dbp:界 \"動物界\"@ja.\n" +
						"  FILTER(isLiteral(?o))\n" +
						"}";

		try {
			List<Map<String, RDFNode>> results = sa.executeQuery(query);

			if (results != null){
				for (Map<String, RDFNode> result : results){
					RDFNode node = result.get("o");
					if (node.isLiteral()){
						bios.add(node.asLiteral().getString());
					}
				}
			}

		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}

		return bios;
	}
	
	private void response(HttpServletRequest request, HttpServletResponse response, String json) throws ServletException, IOException {
		response.setHeader("Access-Control-Allow-Origin", "*");
		response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, OPTIONS");
		response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
		response.setHeader("Access-Control-Max-Age", "-1");
		String cb = (String)request.getParameter("callback");

		
		response.setContentType("text/javascript; charset=UTF-8");
//		response.setContentType("application/json; charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.print(cb + "(" + json + ")");
		out.flush();
	}

	private EndpointSettings getEndpointSettings(HttpServletRequest request){
		HttpSession session = request.getSession(true);
		return (EndpointSettings)session.getAttribute("session");

	}

	private void error(HttpServletResponse response, String word) throws IOException{
		response.setContentType("application/json; charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.print("{\"result\":\"" + word +"\"}");
		out.flush();
	}
	

*/	
}
