/* $Id: Spread.java 671 2013-02-28 16:35:14Z shayashi $ */
package smart_gs.logical;

import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import smart_gs.GSConstants;
import smart_gs.drawing_tool.view.View;
import smart_gs.logical.visitor.Visitor;
import smart_gs.util.Pair;
import smart_gs.util.SegfoToLineConverter;
import smart_gs.util.XMLToLineConverter;
import smart_gs.logical.Preference;
import sml_editor.logical.LineDirection;

public class Spread extends GSResource {

	private SpreadDirectory spreadDirParent;
	private ImageIcon imageIcon;
	private int width;
	private int height;
	private File imageFile;
	private File dscLineSegXmlFile;
	private File lineSegXmlFile;
	private File dscLineSegSegfoFile;
	private File lineSegSegfoFile;
	private List<LineSegment> lines;
	private List<LineSegment> linesForDsc;
	private List<Region> regions;
	private LineDirection lineDirection = LineDirection.UNDEFINED;

	private List<Sheet> sheets;
	private Sheet currentSheet;

//	These four are for the main window (frame) of SMART-GS.
	protected File dscFile;
	private FirstIDAT firstIDAT;
	private SecondIDAT secondIDAT;
	private ThirdIDAT thirdIDAT;
	
//	2011.03.02 shayashi
	private String firstIDATName = null;
	public String getFirstIDATName() {
		if (firstIDATName != null) return firstIDATName;
		return Preference.getInstance().getFirstIDATName();
	}
	public String getFirstIDATNameToSave() {
		if (firstIDATName != null) return firstIDATName;
		return "";
	}	
	public void setFirstIDATName(String firstIDATName) {
		if (firstIDATName.equals(""))
			this.firstIDATName = null;
		else 
			this.firstIDATName = firstIDATName;
	}
	
	private String secondIDATName = null;
	public String getSecondIDATName() {
		if (secondIDATName != null) return secondIDATName;
		return Preference.getInstance().getSecondIDATName();
	}
	public String getSecondIDATNameToSave() {
		if (secondIDATName != null) return secondIDATName;
		return "";
	}
	public void setSecondIDATName(String secondIDATName) {
		if (secondIDATName.equals(""))
			this.secondIDATName = null;
		else 
			this.secondIDATName = secondIDATName;
	}

	private String thirdIDATName = null;
	public String getThirdIDATName() {
		if (thirdIDATName != null) return thirdIDATName;
		return Preference.getInstance().getThirdIDATName();
	}
	public String getThirdIDATNameToSave() {
		if (thirdIDATName != null) return thirdIDATName;
		return "";
	}
	public void setThirdIDATName(String thirdIDATName) {
		if (thirdIDATName.equals(""))
			this.thirdIDATName = null;
		else 
			this.thirdIDATName = thirdIDATName;
	}
	
	public void printGSEditorMemory (){

	}
	
	public static String IMAGE_FOLDER_PATH = Preference.getInstance().getImageFolderPathString();
//	The below is for the future network version.
	public static String DSC_FOLDER_PATH = Preference.getInstance().getDscFolderPathString();

	private String writer = "unknown";
	
	//2007/10/22 kazuhiro kobayashi
	private static int pageIndex = 0;
	private int _pageIndex;
	// TODO ADD NorthGrid
	// 表示用のインデック
	// pageIndexを変更すると影響が出る可能性があるので暫定的に設置
	// pageIndexは最大値と最小値のチェックにしか使っていないみたいなので
	// 今後統合する方向
//	pageViewIndex まである！ shayahsi
//	pageIndex が、最大値を保持している static 変数！
	// Shayashi: Spread に unique な index がないのは変でないか？
	// Spread Tree 中の位置としての index と、Spread の index の
	// 双方を考える必要がある。現在のものは、位置をかえると番号が変わるのだから、
	// SpreadTree 内の index。それが Spread の名前に反映されるのも変！（ただし、
	// これは実装上の　compromise であろう。直せるか？
	private int _pageVIewIndex;

	public Spread(SpreadDirectory parent, File file, String uri, String originalURI, String version) {
		super();
		this.regions = new ArrayList<Region>();
		this.spreadDirParent = parent;
		this.imageFile = file;
		this.setURIs(uri, originalURI);
		this.setVersion(version);
		this.dscLineSegXmlFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "dsc/" + this.getFileNameWithoutExtension() + ".xml");
		this.lineSegXmlFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + this.getFileNameWithoutExtension() + ".xml");
		this.dscLineSegSegfoFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "dsc/" + this.getFileNameWithoutExtension() + ".segfo");
		this.lineSegSegfoFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + this.getFileNameWithoutExtension() + ".segfo");
		this.firstIDAT = new FirstIDAT(this);
		this.secondIDAT = new SecondIDAT(this);
		this.thirdIDAT = new ThirdIDAT(this);
		this.checkDscFile();
		this.loadLines();
		this.loadLinesForDsc();
		this.setTransformations();
		this.setId(this.hashCode());

		this.sheets = new ArrayList<Sheet>();
		currentSheet = makeSheet();
		
		//2007/10/22 kazuhiro kobayashi 
		this._pageVIewIndex = this._pageIndex = pageIndex++;
		this.name = this.getFileNameWithoutExtension();
//		//2007/11/20 kazuhiro kobayashi
//		this.systemName = this.getFile().getName();
//		if(this.userName==null){
//			this.userName = this.getFileNameWithoutExtension();
//		}
	}
	
	private double aFromDscToImg, xFromDscToImg, yFromDscToImg;
	private double aFromImgToDsc, xFromImgToDsc, yFromImgToDsc;
	private boolean transformationIsNecessary = false;
	
	public boolean isTransformationIsNecessary() {
		return transformationIsNecessary;
	}
	public void setTransformationIsNecessary(boolean transformationIsNecessary) {
		this.transformationIsNecessary = transformationIsNecessary;
	}
	
	public void setTransformations() {
		double u,v,u1,v1,s,t,s1,t1;
		Point2D u_v_PointOfLines, s_t_PointOfLines;
		Point2D u1_v1_PointOfLines, s1_t1_PointOfLines;

		if ((dscLineSegXmlFile.exists() ||  dscLineSegSegfoFile.exists())&& (lineSegXmlFile.exists() || lineSegSegfoFile.exists())) {
			// TODO Refactor! This assumes that there are two points in the (existing) first line.
			u_v_PointOfLines = linesForDsc.get(0).getPoints().get(0);
			u1_v1_PointOfLines = lines.get(0).getPoints().get(0);
			s_t_PointOfLines = linesForDsc.get(0).getPoints().get(1);
			s1_t1_PointOfLines = lines.get(0).getPoints().get(1);

			u = u_v_PointOfLines.getX();
			v = u_v_PointOfLines.getY();
			s = s_t_PointOfLines.getX();
			t = s_t_PointOfLines.getY();
			u1 = u1_v1_PointOfLines.getX();
			v1 = u1_v1_PointOfLines.getY();
			s1 = s1_t1_PointOfLines.getX();
			t1 = s1_t1_PointOfLines.getY();

			if (u != s) {
				aFromDscToImg = (u1 - s1)/(u - s);
			} else {
				aFromDscToImg = (v1 - t1)/(v - t);
			}
			xFromDscToImg = s1-aFromDscToImg*s;
			yFromDscToImg = t1-aFromDscToImg*t;


			if (u1 != s1) {
				aFromImgToDsc = (u - s)/(u1 - s1);
			} else {
				aFromImgToDsc = (v - t)/(v1 - t1);
			}
			xFromImgToDsc = s-aFromImgToDsc*s1;
			yFromImgToDsc = t-aFromImgToDsc*t1;
			
			this.setTransformationIsNecessary(true);
		}
	}
	
	public AffineTransform getAffineTranformFromDscToImg () {
		return new AffineTransform(aFromDscToImg,0,0,aFromDscToImg,xFromDscToImg,yFromDscToImg);
	}
	
	public AffineTransform getAffineTranformFromImgToDsc () {
		return new AffineTransform(aFromImgToDsc,0,0,aFromImgToDsc,xFromImgToDsc,yFromImgToDsc);
	}
	
	public Spread(SpreadDirectory parent, File file) {
		super();
		this.regions = new ArrayList<Region>();
		this.spreadDirParent = parent;
		this.imageFile = file;
		this.setURIs();
		this.dscLineSegXmlFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "dsc/" + this.getFileNameWithoutExtension() + ".xml");
		this.lineSegXmlFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + this.getFileNameWithoutExtension() + ".xml");
		this.dscLineSegSegfoFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "dsc/" + this.getFileNameWithoutExtension() + ".segfo");
		this.lineSegSegfoFile = new File(DSC_FOLDER_PATH + this.spreadDirParent.getPath() + this.getFileNameWithoutExtension() + ".segfo");
		this.firstIDAT = new FirstIDAT(this);
		this.secondIDAT = new SecondIDAT(this);
		this.thirdIDAT = new ThirdIDAT(this);
		this.checkDscFile();
		this.loadLines();
		System.out.println();
		this.loadLinesForDsc();
		this.setTransformations();
		this.setId(this.hashCode());

		this.sheets = new ArrayList<Sheet>();
		currentSheet = makeSheet();
		
		this._pageVIewIndex = this._pageIndex = pageIndex++;
		this.name = this.getFileNameWithoutExtension();
	}

	public static String getFileNameWithoutExtension(File file) {
		String str = file.getName();
		int index = str.lastIndexOf('.');
		return str.substring(0, index);
	}

	public File getFile() {
		return this.imageFile;
	}
	
	public File getImageFile() {
		return this.imageFile;
	}
	public File getDscLineSegFile() {
		return this.dscLineSegXmlFile;
	}
	public File getLineSegFile() {
		return this.lineSegXmlFile;
	}

	public void loadLines() {
		if (this.lines == null) {
			String filenameXml = DSC_FOLDER_PATH + this.spreadDirParent.getPath()
				+ this.getFileNameWithoutExtension() + ".xml";
			File fileXml = new File(filenameXml);
			String filenameSegfo = DSC_FOLDER_PATH + this.spreadDirParent.getPath()
				+ this.getFileNameWithoutExtension() + ".segfo";
			File fileSegfo = new File(filenameSegfo);
			
			if (fileXml.exists()) {
				Pair<LineDirection,List<LineSegment>> tmp = new XMLToLineConverter(this).getLines(new File(filenameXml));
				if (tmp == null) return;
				this.lineDirection = tmp.getLeft();
				this.lines = tmp.getRight();
				return;
			} else if (fileSegfo.exists()) {
				Pair<LineDirection,List<LineSegment>> tmp = new SegfoToLineConverter(this).getLines(new File(filenameSegfo));
				this.lineDirection = tmp.getLeft();
				this.lines = tmp.getRight();
				return;
			} else {
			this.lines = null;
			return;
			}
		}
	}
	
	public void loadLinesForDsc() {
		if (this.linesForDsc == null) {
			String filenameXml = DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "dsc/" 
			+ this.getFileNameWithoutExtension() + ".xml";
			File fileXml = new File(filenameXml);
			String filenameSegfo = DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "dsc/" 
			+ this.getFileNameWithoutExtension() + ".segfo";
			File fileSegfo = new File(filenameSegfo);

			Pair<LineDirection,List<LineSegment>> tmp = null;
			if (fileXml.exists()) {
				tmp = new XMLToLineConverter(this).getLines(fileXml);

			} else if (fileSegfo.exists()) {
				tmp = new SegfoToLineConverter(this).getLines(fileSegfo);
			}

			if (tmp == null) return;
				
			if (! tmp.getLeft().equals(lineDirection)) {
				System.out.printf("Warning: line Direction is %s, but, in DSC lineSegment File %s, line direction is %s%n",
						lineDirection.equals(LineDirection.HORIZONTAL)?"horizontal":
							(lineDirection.equals(LineDirection.VERTICAL)?"vertical":"undefined"),
							filenameSegfo,
							(tmp.getLeft()).equals(LineDirection.HORIZONTAL)?"horizontal":"vertical");
			}
			this.linesForDsc = tmp.getRight();
			return;
		} else {
			this.linesForDsc = null;
			return;
		}
	}

	void loadImage() {
		File file = new File(this.imageFile.getAbsolutePath());
		if (!(file.exists() && file.isFile()) ) {
//			imageIcon = null;
//			return;;
			this.imageIcon = new ImageIcon((new File("./icons/no_image_found_large.png")).getAbsolutePath());
		} else {
			this.imageIcon = new ImageIcon(this.imageFile.getAbsolutePath());
		}
		this.width = this.imageIcon.getIconWidth();
		this.height = this.imageIcon.getIconHeight();
		if (width <0 || height <0) {
			imageIcon = null;
			return;
		}
	}

	public int getWidth() {
		if (this.imageIcon == null) {
			this.loadImage();
		}
		return this.width;
	}

	public int getHeight() {
		if (this.imageIcon == null) {
			this.loadImage();
		}
		return this.height;
	}

	public Image getImage() {
		if (this.imageIcon == null) {
			this.loadImage();
		}
		if (imageIcon != null){
			return this.imageIcon.getImage();
		} else {
			return null;
		}
	}

	public ImageIcon getImageIcon() {
		if (this.imageIcon == null) {
			this.loadImage();
		}
		return imageIcon;
	}

	public void addRegion(Region region) {
		this.regions.add(region);
		// kazuhiro kobayashi 10/5
		// currentSheet.addNormalRegions(region);
		currentSheet.addRegion(region);
	}

	// Return regions to show
	public List<Region> getRegions() {
		return this.regions;
	}

	public List<Region> getAllRegions() {
		return this.currentSheet.getAllRegions();
	}

	public void addLineSegment(LineSegment line) {
		this.lines.add(line);
	}

	public List<LineSegment> getLines() {
		if (this.lines == null) {
			this.loadLines();
		}
		return this.lines;
	}
	
	public void setLines(List<LineSegment> lines) {
		this.lines = lines;
	}
	
	public void rewriteLineSegIndexes() {
		for (int i = 0; i<lines.size();i++){
			lines.get(i).setIndex(i);
		}
}
	public List<LineSegment> getLinesForDsc() {
		if (this.linesForDsc == null) {
			this.loadLinesForDsc();
		}
		return this.linesForDsc;
	}

	public SpreadDirectory getSpreadDirParent() {
		return this.spreadDirParent;
	}

	public String getFileNameWithoutExtension() {
		int index = imageFile.getName().lastIndexOf('.');
		return imageFile.getName().substring(0, index);
	}
	
	public String getFileNameExtension() {
		int index = imageFile.getName().lastIndexOf('.');
		return imageFile.getName().substring(index+1);
	}
	
	public String getFileName() {
		return imageFile.getName();
	}

	public String toString() {
		return getViewName();
	}
	
	public String getViewName(){
		String str = this._pageVIewIndex +": " + this.name;
		if (this.isSearchable() && this.isTransformationIsNecessary()) {
			str += ("  [**]");
		} else if (this.isSearchable()) {
			str += ("  [*]");
		}
		return str;
	}

	public FirstIDAT getFirstIDAT() {
		return this.firstIDAT;
	}

	public SecondIDAT getSecondIDAT() {
		return this.secondIDAT;
	}

	public ThirdIDAT getThirdIDAT() {
		return this.thirdIDAT;
	}

	public void setFirstIDAT(FirstIDAT fstidat) {
		this.firstIDAT = fstidat;
	}

	public void setSecondIDAT(SecondIDAT sndidat) {
		this.secondIDAT = sndidat;
	}

	public void setThirdIDAT(ThirdIDAT thdidat) {
		this.thirdIDAT = thdidat;
	}

	public boolean hasDscFile() {
		if (this.dscFile == null) {
			return false;
		} else {
			return true;
		}
	}

	public List<Region> getSelectedRegions() {
		List<Region> results = new ArrayList<Region>();
		for (int i = 0; i < this.regions.size(); i++) {
			View view = this.regions.get(i).getView();
			if (view.isSelected()) {
				results.add(this.regions.get(i));
			}
		}
		return results;
	}

	public void clearSelection() {
		for (int i = 0; i < this.regions.size(); i++) {
			View view = this.regions.get(i).getView();
			view.setIsSelected(false);
			view.setIsEmphasized(false);
		}
	}

	public void checkDscFile() {
		String folderPath = DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "/dsc/";
		this.dscFile = new File(folderPath + this.getFileNameWithoutExtension()	+ ".dsc");
		if (!this.dscFile.exists()) {
			this.dscFile = null;
		}
	}
	
	public boolean isSearchable() {
		return this.dscFile != null;
	}

	public File getDscFile() {
		String folderPath = DSC_FOLDER_PATH + this.spreadDirParent.getPath() + "/dsc/";
		this.dscFile = new File(folderPath + this.getFileNameWithoutExtension()
				+ ".dsc");
		if (!this.dscFile.exists()) {
			this.dscFile = null;
			return null;
		}
		return this.dscFile;
	}

	public void removeRegion(Region region) {
		this.regions.remove(region);
	}

	public void release() {
		if (Preference.getInstance().shouldLoadAll()) {
			return;
		}
		if (this.imageIcon == null) {
			return;
		}
		this.imageIcon.getImage().flush();
		this.imageIcon = null;
	}

	public Region getRegionByURI(String uri) {
		for (int i = 0; i < this.regions.size(); i++) {
			if (this.regions.get(i).getURI().equals(uri)){
				return this.regions.get(i);
			}
		}
		return null;
	}

	
	//If resource of uri does not exist, then null is returned.
	public GSResource getResource(String uri) {
		if (this.uri.equals(uri)) {
			return this;
		} else if (firstIDAT.uri.equals(uri)) {
			return firstIDAT;
		} else if (thirdIDAT.uri.equals(uri)) {
			return thirdIDAT;
		} else if (secondIDAT.uri.equals(uri)) {
			return secondIDAT;
		}
		
		if (URISolver.isATextSegmentFromFirstIDAT(uri)) {
			return firstIDAT.getTextSegmentByURI(uri);
		} else if (URISolver.isATextSegmentFromSecondIDAT(uri)) {
			return secondIDAT.getTextSegmentByURI(uri);
		} else if (URISolver.isATextSegmentFromThirdIDAT(uri)) {
			return thirdIDAT.getTextSegmentByURI(uri);
		}
		return this.getRegionByURI(uri);
	}

	public String getFolderPath() {
		return this.spreadDirParent.getPath();
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

	public String getWriter() {
		return writer;
	}

	public void setWriter(String writer) {
		this.writer = writer;
	}

	// ----------------regionの表示に関するメソッド--------------------------------------//
	/*
	 * getProtectedRegions(),getNoRegions(),getAllRegions()メソッドにより、
	 * 適切なregionsをsheetから得ることで表示を変える。
	 * 
	 * 2007/9/20
	 * 
	 * kazuhiro kobayashi 10/5 以下のメソッドを修正
	 */
	/*
	 * シートのshoewRegionの状態に応じて適切なregionsを表示するメソッド
	 */

	public void showRegions() {
		if (currentSheet.getShowRegion() == Sheet.SHOW_PROTECTED_REGIONS) {
			regions = currentSheet.getProtectedRegions();
		} else if (currentSheet.getShowRegion() == Sheet.SHOW_NO_REGIONS) {
			// regions = currentSheet.getNoRegions();
			regions = new ArrayList<Region>();
		} else if (currentSheet.getShowRegion() == Sheet.SHOW_ALL_REGIONS) {
			regions = currentSheet.getAllRegions();
		}
	}

	/*
	 * シートのshowRegionsの状態を変えるめそっど
	 */
	public void changeShowRegionAttribute() {
		if (currentSheet.getShowRegion() == Sheet.SHOW_NO_REGIONS) {
			currentSheet.setShowRegion(Sheet.SHOW_ALL_REGIONS);
		} else if (currentSheet.getShowRegion() == Sheet.SHOW_ALL_REGIONS) {
			currentSheet.setShowRegion(Sheet.SHOW_PROTECTED_REGIONS);
		} else if (currentSheet.getShowRegion() == Sheet.SHOW_PROTECTED_REGIONS) {
			currentSheet.setShowRegion(Sheet.SHOW_NO_REGIONS);
		}
	}

	public void changeShowRegion(int showRegion) {
		currentSheet.setShowRegion(showRegion);
		showRegions();
	}

	// ----------シートの選択に関するメソッド----------------------------------------//

	/*
	 * 新しいシートを作成してリストに加えるメソッド
	 */
	public Sheet makeSheet() {
		List<Region> regions = new ArrayList<Region>();
		Sheet sheet = new Sheet(regions, this);
		if (sheets.size() != 0) {
			setNewSheetName(sheet);
		}
		sheets.add(sheet);
		return sheet;
	}

	/*
	 * シートを指定して移動するメソッド
	 */
	public void _selectSheet(Sheet sheet) {
		// distinguishNormalOrProtected();
		currentSheet = sheet;
		showRegions();
	}

	/*
	 * 次のシートに移動するメソッド
	 */
	public void roundSheet() {
		int i = sheets.indexOf(currentSheet);
		if (i + 1 < sheets.size()) {
			_selectSheet(sheets.get(i + 1));
		} else {
			_selectSheet(sheets.get(0));
		}
	}

	/*
	 * シートの名前
	 */
	public void setNewSheetName(Sheet sheet) {
		String sheetName = (String) JOptionPane
				.showInputDialog("Input sheet name");
		sheet.setSheetName(sheetName);
	}

	/*
	 * 外のクラスからの操作に使う。
	 */
	public List<Sheet> getSheets() {
		return this.sheets;
	}

	public Sheet getCurrentSheet() {
		return this.currentSheet;
	}

	public void setCurrentSheet(Sheet sheet) {
		this.currentSheet = sheet;
	}
	
	
	public static void resetIndex(){
		Spread.pageIndex = 0;
	}


	// ADD shayashi
	public static Integer getCurrentMaxPageIndex(){
		return Spread.pageIndex;
	}
	

	// ADD NorthGrid
	/**
	 * 表示用インデックス設定
	 */
	public void setPageViewIndex(int number){
		_pageVIewIndex = number;
	}
	// ADD NorthGrid
	/**
	 * 表示用インデックス取得
	 */
	public int getPageViewIndex(){
		return _pageVIewIndex;
	}
	public int getPageIndex(){
		return this._pageIndex;
	}
	@Override
	public String createURI() {
		return URICreator.createURI(this);
	}
	@Override
	public String getTypeString() {
		return URICreator.SPREAD;
	}
	public String createOldVersionURI() {
		return GSConstants.URI_HEADER 
			+ spreadDirParent.createOldVersionURI() + getFileNameWithoutExtension(imageFile) + "/";
	}
	public IDAT getIDATByTextSegmentURI(String uri) {
		if (uri.contains(URIObject.FIRST_IDAT) || uri.contains("/transcription/")) {
			return this.firstIDAT;
		} else if (uri.contains(URIObject.SECOND_IDAT) || uri.contains("/annotation/")) {
			return this.secondIDAT;
		} else if (uri.contains(URIObject.THIRD_IDAT) || uri.contains("/translation/")) {
			return this.thirdIDAT;
		}
		return null;
	}
	public List<LineSegmentForEdit> getLinesForEdit() {
		// TODO Auto-generated method stub
		return null;
	}
	

	/**
	 * @ the spread is horizontazl_mode or null when undefined
	 */
	public LineDirection getLineDirection() {
		return lineDirection;
	}
}
