/* $Id: ExternalSearchEngineController.java 654 2013-02-08 10:05:15Z ohura $ */
package smart_gs.image_search;

import image_search.Query;

import java.awt.Rectangle;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.lang.InterruptedException;

import javax.swing.JOptionPane;

import smart_gs.logical.Spread;
import smart_gs.util.CanonicalPathString;
import smart_gs.util.Pair;
import smart_gs.image_search.logical.TextType;
import smart_gs.image_search.normal.swingui.SetSearchOptionPanel;
import sml_editor.logical.LineDirection;

import jp.ac.hokudai.meme.core_smart_gs.searcher.FoundRect;
import jp.ac.hokudai.meme.core_smart_gs.searcher.InputStreamThread;
import jp.ac.hokudai.meme.core_smart_gs.searcher.PagedPolygon;
import jp.ac.hokudai.meme.core_smart_gs.searcher.PagedRect;
import jp.ac.hokudai.meme.core_smart_gs.searcher.PolygonParser;
import jp.ac.hokudai.meme.core_smart_gs.searcher.ResultRects;

public class ExternalSearchEngineController {

	private ResultRects resultRects = new ResultRects();
	
	static final String COM_ASSIGN = "assign";
	static final String COM_SEARCH = "search";
	static final String COM_EXIT = "exit";
	static final String PREFIX_P0 = "p0";
	static final String PREFIX_X = "x";
	static final String PREFIX_Y = "y";
	static final String REGEXP_RECORD = "r(\\d+)d(\\d+\\.?\\d+)(.*)";
	static final String MSG_OK = "OK";
	static final String RECORD_EOD = "EOD";
	static final String ERRMSG_PARSE = "Internal Error: parse error";
	static final double CAPACITY = 200000000;
	static final int maxRank = 100; // rank starts with 1 not 0.
	boolean cocurrentSearch = true;
	
	public void setConcurrentSearch() {
		cocurrentSearch = true;
	}
	public void setSequentialSearch() {
		cocurrentSearch = false;
	}
	
	public class DscSearchThread extends Thread {
		private Process p;
		private File assignListFile;
		private ArrayList<String> tmpResultStrings;
		private InputStreamThread it,et;
		private BufferedWriter bw;
		int startIndexToBeSearched = 0;
		ArrayList<String> resultStrings;
		Query query;
		Pair<Integer,List<Spread>>  pair;

		DscSearchThread (Query query0,Pair<Integer,List<Spread>> pair0) {
			query = query0;
			pair = pair0;
			assignListFile = makeAssignListFile(pair.getLeft(),pair.getRight());
			tmpResultStrings = new ArrayList<String>();
		}
				
		
		public void run() {

			try {
				p = Runtime.getRuntime().exec(smart_gs.SmartGS.getImageSearchExefilePathString());
				// DscSearchからの標準出力を受け付けるスレッド
				it = new InputStreamThread(p.getInputStream(), InputStreamThread.TYPE_STDOUT, this);
				it.start();
				// DscSearchからの標準エラー出力を受け付けるスレッド
				et = new InputStreamThread(p.getErrorStream(), InputStreamThread.TYPE_STDERR, this);
				et.start();
				// DscSearchの標準入力へ書き込むBufferedWriter       	
				bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
			} catch (IOException e) {
				throw new RuntimeException(e);
			}

			try {
				bw.write(COM_ASSIGN + " " + CanonicalPathString.get(assignListFile));
				System.out.println(COM_ASSIGN + " " + CanonicalPathString.get(assignListFile));
				bw.newLine();
				bw.flush();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}

			String queryString = getStringFromRectangle(query.getRectangle(),query.getSpread());
			
			int first = 1, count = maxRank;
			try {
				bw.write(COM_SEARCH + " " + queryString + " " + first + " " + count);
				System.out.println(COM_SEARCH + " " + queryString + " " + first + " " + count);
				bw.newLine();
				bw.flush();		
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			
			try {
				bw.write(COM_EXIT);
				bw.newLine();
				bw.flush();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}

			try {
				it.join();
				et.join();
				p.getInputStream().close();
				p.getOutputStream().close();
				p.getErrorStream().close();
				p.destroy();
				assignListFile.delete();
			} catch (IOException e) {
				throw new RuntimeException(e);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}		
		}

		public void addSearchResult(String ret) {
			if(ret.equals(MSG_OK)) {
				return;
			}
			tmpResultStrings.add(ret);		
		}


		public ArrayList<String> getResultStringList() {
			return tmpResultStrings;
		}

	}		
	
	public ResultRects search(Query query, List<Spread> spreads) {
		List<Spread> sourceSpreadList = spreads;
		DscSearchThread[] dscSearchThread;
		int startIndex = 0;
		int max;
		max = sourceSpreadList.size();
		double tmpSpreadsListTotalSize = 0;
		List<Spread> tmpSpreadsListToSearch = new ArrayList<Spread>();
		ArrayList<Pair<Integer,List<Spread>>> dividedListsWithStartIndex = new ArrayList<Pair<Integer,List<Spread>>>();
		double capacity = CAPACITY * SetSearchOptionPanel.getPos();
		if (SetSearchOptionPanel.isConcurrent()) {
			setConcurrentSearch();
		} else {
			setSequentialSearch();
		}

		for (int i = 0; i < max;) {
			Spread spread = sourceSpreadList.get(i);
			File dscFile = spread.getDscFile();
			long ln = dscFile.length();
			if (! (ln < capacity)) {
				JOptionPane.showMessageDialog(null, "Dsc file for " +  Spread.getFileNameWithoutExtension(dscFile) + " is too large to search");
				return new ResultRects();
			}

			if (tmpSpreadsListTotalSize + ln > capacity) {
				dividedListsWithStartIndex.add(new Pair<Integer,List<Spread>>(startIndex,tmpSpreadsListToSearch));
				startIndex += tmpSpreadsListToSearch.size();
				tmpSpreadsListToSearch = new ArrayList<Spread>();
				tmpSpreadsListTotalSize = 0;
			}
			else {
				tmpSpreadsListToSearch.add(spread);
				tmpSpreadsListTotalSize += ln;
				i++;
				if (i == max) {
					dividedListsWithStartIndex.add(new Pair<Integer,List<Spread>>(startIndex,tmpSpreadsListToSearch));
				}
			}
		}

		int sizeOfDivided = dividedListsWithStartIndex.size();
		
		dscSearchThread = new DscSearchThread[sizeOfDivided];

		for (int i = 0; i<sizeOfDivided; i++) {
			dscSearchThread[i] = new DscSearchThread(query,dividedListsWithStartIndex.get(i));
			dscSearchThread[i].start();
			if (!cocurrentSearch) {
				try {
					dscSearchThread[i].join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
		if (cocurrentSearch) {
			for (int i = 0; i<sizeOfDivided; i++) {
				try {
					dscSearchThread[i].join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
		ResultRects[] resultRects = new ResultRects[sizeOfDivided];
		resultRects[0] = getResultRectsFromStrings(dscSearchThread[0].getResultStringList());
		for (int i = 1; i<sizeOfDivided; i++) {
			Integer j = dividedListsWithStartIndex.get(i).getLeft();
			resultRects[i] = getResultRectsFromStrings(dscSearchThread[i].getResultStringList());
			resultRects[i] = resultRectsRemoveZeroPageResultsAndIncrementPageNumberBy(j-1,resultRects[i]);
		}

		return mergeResults(resultRects);
	}

	public ResultRects resultRectsRemoveZeroPageResultsAndIncrementPageNumberBy(int incr, ResultRects resultRects) {
		ResultRects newResultRects = new ResultRects();
		for (int r = 1; r<= maxRank; r++) {
			PagedRect[] pagedRectArray = resultRects.getPRects(r);
			double score = resultRects.getScore(r);
			ArrayList<PagedRect> newPagedRectsList = new ArrayList<PagedRect>();
			for (int i=0; i< pagedRectArray.length; i++) {
				int pageIndex = pagedRectArray[i].getPageIndex();
				if (pageIndex != 0) {
					PagedRect newPagedRect = new PagedRect(i+incr,pagedRectArray[i].getRect());
					newPagedRectsList.add(newPagedRect);
				}
			}
			PagedRect[] tmp = new PagedRect[newPagedRectsList.size()];
			for (int i = 0; i<newPagedRectsList.size(); i++) tmp[i] = newPagedRectsList.get(i);
			newResultRects.addResult(new FoundRect(tmp,score));
		}
		return newResultRects;
	}

	//ResultRects[] resultRectsArray is the array of the results returned by the processes of DscSearch.
	//ResutlRects[x] (x>0) are all filtered so that (1) no 0 page result, (2) page numbers(indexes) are increased so that
	//they are the same as the page numbers in the specification of pages given to externalSearcher.
	//They are ordered as the the order of division.
	private ResultRects mergeResults(ResultRects[] resultRectsArray) {
		ResultRects mergedResultRects = resultRectsArray[0];
		int size = resultRectsArray.length;
		for (int i=1;i<size;i++){
			mergedResultRects = merge(mergedResultRects,resultRectsArray[i]);
		}
		return mergedResultRects;
	}

	private ResultRects merge(ResultRects leftRRs, ResultRects rightRRs) {
		ResultRects ans = new ResultRects();
		int leftRank = 1, rightRank = 1;
		double leftScore, rightScore;
		for (int i=1;i<=maxRank;i++) {
			leftScore = leftRRs.getScore(leftRank);
			rightScore = leftRRs.getScore(rightRank);
			if (leftScore < rightScore) {
				ans.addResult(leftRRs.getFoundRect(leftRank));
				leftRank++;
			} else if (leftScore > rightScore) {
				ans.addResult(rightRRs.getFoundRect(rightRank));
				rightRank++;
			} else {
				PagedRect[] leftPRs = leftRRs.getFoundRect(leftRank).getPRects();
				PagedRect[] rightPRs = rightRRs.getFoundRect(rightRank).getPRects();
				PagedRect[] mergedPRs = new PagedRect[leftPRs.length+rightPRs.length];
				for (int j=0;j<leftPRs.length;j++) mergedPRs[j] = leftPRs[j];
				for (int j=0;j<rightPRs.length;j++) mergedPRs[j+leftPRs.length] = rightPRs[j];
				ans.addResult(new FoundRect(mergedPRs,leftScore));
				leftRank++;
				rightRank++;
			}
		}
		return ans;
	}
	private File makeAssignListFile(Integer index, List<Spread> spreads){
		String assignListPath = smart_gs.SmartGS.getImageSearchAssignlistfilePathString(index);
		File file = new File(assignListPath);
		try {
			HashMap<String, String> dscLocation = new HashMap<String, String>();
			
			file.createNewFile();
	        FileOutputStream fos = new FileOutputStream(assignListPath);
	        OutputStreamWriter osw = new OutputStreamWriter(fos);
	        BufferedWriter bw = new BufferedWriter(osw);
			for(int i = 0; i < spreads.size(); i++){
				if(spreads.get(i).getDscFile() == null){
					continue;
				}

				bw.write(CanonicalPathString.get(spreads.get(i).getFile()));
				bw.newLine();
				
				String bmp = spreads.get(i).getFile().getParent();
				String dsc = spreads.get(i).getDscFile().getParent();
				
				dscLocation.put(bmp, dsc);
			}
			bw.close();
			osw.close();
			fos.close();
			
			for(Iterator<String> i = dscLocation.keySet().iterator(); i.hasNext(); ){
				String key = i.next();
				File f = new File(key+"/DscLocation");
		        FileOutputStream fosd = new FileOutputStream(f);
		        OutputStreamWriter oswd = new OutputStreamWriter(fosd);
		        BufferedWriter bwd = new BufferedWriter(oswd);
		        bwd.write(dscLocation.get(key));
		        bwd.close();
				oswd.close();
				fosd.close();
				
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return file;
	}
		
	public String getStringFromRectangle(Rectangle rect, Spread spread) {
		StringBuffer queryStringBuffer = new StringBuffer();
		queryStringBuffer.append(PREFIX_P0);
		queryStringBuffer.append(PREFIX_X);
		queryStringBuffer.append((int)rect.getX());
		queryStringBuffer.append(PREFIX_Y);
		queryStringBuffer.append((int)rect.getY());
		if (spread.getLineDirection() == LineDirection.HORIZONTAL) {
			queryStringBuffer.append(PREFIX_X);
			queryStringBuffer.append((int)rect.getX() + (int)rect.getWidth());
			queryStringBuffer.append(PREFIX_Y);
			queryStringBuffer.append((int)rect.getY());
		} else {
			queryStringBuffer.append(PREFIX_X);
			queryStringBuffer.append((int)rect.getX());
			queryStringBuffer.append(PREFIX_Y);
			queryStringBuffer.append((int)rect.getY() + (int)rect.getHeight());
		}
		return queryStringBuffer.toString();
	}

	public ResultRects getResultRectsFromStrings(ArrayList<String> strs) {
        for (Iterator<String> iterator = strs.iterator(); iterator.hasNext();) {
        	String recordString = (String) iterator.next();
			if(recordString.equals(RECORD_EOD)) {
				break;
			}
			Pattern pat = Pattern.compile(REGEXP_RECORD);
			Matcher mat = pat.matcher(recordString);
			if (mat.find()) {
			} else {
				throw new RuntimeException(new ParseException(ERRMSG_PARSE, 0));
			}
			PolygonParser parser = new PolygonParser(mat.group(3));
			try {
				List<PagedPolygon> polygon = parser.getPolygon();
				PagedRect[] pagedRects = new PagedRect[polygon.size()];
				int idx = 0;
				for (Iterator<PagedPolygon> ite_p= polygon.iterator(); ite_p.hasNext();idx++) {
					PagedPolygon pagedPolygon = (PagedPolygon) ite_p.next();
					pagedRects[idx] = new PagedRect(pagedPolygon.getPageNum(), pagedPolygon.getPolygon().getBounds(), -1, -1);
				}
				FoundRect foundRect = new FoundRect(pagedRects, Double.parseDouble(mat.group(2)));
				resultRects.addResult(foundRect);
			} catch (ParseException e) {
				throw new RuntimeException(e);
			}			
        }
        return resultRects;
	}
}
