package gui;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class EditingPane extends Composite {

	private static final String TIMESTAMP_DATA = "timestamp";
	private static final int TIMESTAMP_FONT_STYLE = SWT.BOLD;
	private static final Color TIMESTAPM_FONT_COLOR = Display.getCurrent()
			.getSystemColor(SWT.COLOR_BLUE);
	private static final Cursor ARROW_CURSOR = new Cursor(Display.getCurrent(),
			SWT.CURSOR_ARROW);
	private static final Cursor CARRET_CURSOR = new Cursor(
			Display.getCurrent(), SWT.CURSOR_IBEAM);

	private StyledText text;
	private boolean changed = false;

	PmTrans evidenceBucket;

	public EditingPane(Composite parent, PmTrans pm) {
		super(parent, SWT.NONE);
		setLayout(new GridLayout(1, false));

		evidenceBucket = pm;

		// Create the text pane
		text = new StyledText(this, SWT.V_SCROLL | SWT.BORDER);
		text.setEditable(true);
		updateFont();

		GridData gd = new GridData();
		gd.grabExcessHorizontalSpace = true;
		gd.horizontalAlignment = SWT.FILL;
		gd.grabExcessVerticalSpace = true;
		gd.verticalAlignment = SWT.FILL;

		text.setWordWrap(true);
		text.setLayoutData(gd);

		// Event handling for autosave and save before closing
		text.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				if (!changed)
					changed = true;
			}
		});

		// Timestamp selection and deleting handling
		text.addCaretListener(new CaretListener() {
			@Override
			public void caretMoved(CaretEvent event) {
				int currentOffset = text.getSelection().x;
				int newOffset = event.caretOffset;
				if (currentOffset == newOffset)
					return;
				StyleRange[] timestamps = text.getStyleRanges();
				if (text.getSelection().x == text.getSelection().y)
					for (int i = 0; i < timestamps.length; i++) {
						StyleRange tsp = timestamps[i];
						if (tsp.data.toString().startsWith(TIMESTAMP_DATA)
								&& newOffset >= tsp.start
								&& newOffset < (tsp.start + tsp.length))
							text.setSelection(currentOffset > newOffset ? tsp.start
									: tsp.start + tsp.length);
					}
			}
		});
		text.addModifyListener(new ModifyListener() {
			@Override
			public void modifyText(ModifyEvent e) {
				StyleRange[] timestamps = text.getStyleRanges();
				for (int i = 0; i < timestamps.length; i++) {
					StyleRange tsp = timestamps[i];
					if (tsp.data.toString().startsWith(TIMESTAMP_DATA)) {
						int originalLenght = Integer.parseInt(tsp.data
								.toString().replace(TIMESTAMP_DATA, ""));
						if (originalLenght != tsp.length)
							text.replaceTextRange(tsp.start, tsp.length, "");
					}
				}
			}
		});
		text.addMouseListener(new MouseListener() {

			@Override
			public void mouseUp(MouseEvent e) {
				try {
					int clickOffset = text.getOffsetAtLocation(new Point(e.x,
							e.y));
					for (int i = 0; i < text.getStyleRanges().length; i++) {
						StyleRange range = text.getStyleRanges()[i];
						if (range.data.toString().startsWith(TIMESTAMP_DATA)
								&& range.start < clickOffset
								&& range.start + range.length > clickOffset) {
							String[] time = text.getTextRange(range.start,
									range.length).split(":");
							evidenceBucket.goTo(Integer.parseInt(time[0]) * 60
									+ Double.parseDouble(time[1]));
						}
					}
				} catch (IllegalArgumentException e2) {
					// ignore
				}

			}

			@Override
			public void mouseDown(MouseEvent e) {

			}

			@Override
			public void mouseDoubleClick(MouseEvent e) {

			}
		});
		text.addMouseMoveListener(new MouseMoveListener() {

			@Override
			public void mouseMove(MouseEvent e) {
				try {
					int mouseOffset = text.getOffsetAtLocation(new Point(e.x,
							e.y));
					if (!text.getCursor().equals(ARROW_CURSOR))
						for (int i = 0; i < text.getStyleRanges().length; i++) {
							StyleRange range = text.getStyleRanges()[i];
							if (range.data.toString()
									.startsWith(TIMESTAMP_DATA)
									&& range.start < mouseOffset
									&& range.start + range.length > mouseOffset)
								text.setCursor(ARROW_CURSOR);
						}
					else {
						boolean noTimestamp = true;
						for (int i = 0; i < text.getStyleRanges().length; i++) {
							StyleRange range = text.getStyleRanges()[i];
							if (range.data.toString()
									.startsWith(TIMESTAMP_DATA)
									&& range.start < mouseOffset
									&& range.start + range.length > mouseOffset)
								noTimestamp = false;
						}
						if (noTimestamp)
							text.setCursor(CARRET_CURSOR);
					}

				} catch (IllegalArgumentException e2) {
					if (text.getCursor().equals(ARROW_CURSOR))
						text.setCursor(CARRET_CURSOR);
				}
			}
		});
	}

	public Control getVisualization() {
		return text;
	}

	public boolean isChanged() {
		return changed;
	}

	public void insertTimestamp(double secs) {
		int minutes = ((int) secs) / 60;
		int seconds = ((int) secs) % 60;
		int milis = (int) ((secs % 1) * 1000);
		String timestamp = String.format("%02d:%02d.%04d", minutes, seconds,
				milis);
		text.insert(timestamp);
		printTimestamp(text.getSelection().x, timestamp.length());
		text.setSelection(text.getSelection().x + timestamp.length());
	}

	public void printTimestamp(int start, int lenght) {
		StyleRange styleRange = new StyleRange();
		styleRange.start = start;
		styleRange.length = lenght;
		styleRange.fontStyle = TIMESTAMP_FONT_STYLE;
		styleRange.foreground = TIMESTAPM_FONT_COLOR;
		styleRange.data = TIMESTAMP_DATA + lenght;
		text.setStyleRange(styleRange);
	}

	public void loadTranscription(File transcriptionFile)
			throws ParserConfigurationException, SAXException, IOException {
		text.setText("");

		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document doc = dBuilder.parse(transcriptionFile);

		// Text
		text.setText(doc.getElementsByTagName("text").item(0).getTextContent());

		// Timestamps
		NodeList nList = doc.getElementsByTagName("timeStamp");
		for (int temp = 0; temp < nList.getLength(); temp++) {
			Element timestamp = (Element) nList.item(temp);
			printTimestamp(Integer.parseInt(timestamp.getAttribute("start")),
					Integer.parseInt(timestamp.getAttribute("lenght")));
		}

		changed = false;
	}

	public void importText(File f) throws IOException {
		text.setText("");
		BufferedReader br = new BufferedReader(new FileReader(f));
		String line = br.readLine();
		while (line != null) {
			text.append(line + System.getProperty("line.separator"));
			line = br.readLine();
		}
		br.close();
		changed = false;
	}

	public void saveTranscription(File transcriptionFile)
			throws ParserConfigurationException, TransformerException {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
		Document doc = docBuilder.newDocument();
		Element rootElement = doc.createElement("transcription");
		doc.appendChild(rootElement);
		// Text Element
		Element textE = doc.createElement("text");
		textE.appendChild(doc.createTextNode(text.getText()));
		rootElement.appendChild(textE);

		// Timestamps
		Element timeStamps = doc.createElement("time_stamps");
		rootElement.appendChild(timeStamps);
		for (int i = 0; i < text.getStyleRanges().length; i++) {
			StyleRange range = text.getStyleRanges()[i];
			Element timeStampE = doc.createElement("timeStamp");
			Attr start = doc.createAttribute("start");
			Attr lenght = doc.createAttribute("lenght");
			start.setValue("" + range.start);
			lenght.setValue("" + range.length);
			timeStampE.setAttributeNode(start);
			timeStampE.setAttributeNode(lenght);
			timeStamps.appendChild(timeStampE);
		}

		// Save the file
		TransformerFactory transformerFactory = TransformerFactory
				.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMSource source = new DOMSource(doc);
		StreamResult result = new StreamResult(transcriptionFile);

		transformer.transform(source, result);
		changed = false;
	}

	public void exportText(File exportFile) throws IOException {
		BufferedWriter bw = new BufferedWriter(new FileWriter(exportFile));
		bw.write(text.getText());
		bw.close();
		changed = false;
	}

	public void clear() {
		text.setText("");
		changed = false;
	}

	public void cut() {
		text.cut();
	}

	public void paste() {
		text.paste();
	}

	public void copy() {
		text.copy();
	}

	public void updateFont() {
		text.setFont(new Font(Display.getCurrent(), Config.getInstance()
				.getString(Config.FONT), Config.getInstance().getInt(
				Config.FONT_SIZE), SWT.NORMAL));
		text.setBackground(Config.getInstance().getColor(
				Config.BACKGROUND_COLOR));
		text.setForeground(Config.getInstance().getColor(Config.FONT_COLOR));
	}

	public void zoomIn() {
		int currentSize = Config.getInstance().getInt(Config.FONT_SIZE);
		Config.getInstance().setValue(Config.FONT_SIZE,
				currentSize > 32 ? currentSize : currentSize + 1);
		updateFont();
	}

	public void zoomOut() {
		int currentSize = Config.getInstance().getInt(Config.FONT_SIZE);
		Config.getInstance().setValue(Config.FONT_SIZE,
				currentSize <= 6 ? currentSize : currentSize - 1);
		updateFont();
	}

	public void setFontName(String fName) {
		Config.getInstance().setValue(Config.FONT, fName);
		updateFont();
	}

	public FindReplaceDialog getFindReplaceDialog() {
		return new FindReplaceDialog(getShell(), text);
	}

	public void changeBackgroundColor() {
		ColorDialog cd = new ColorDialog(getShell());
		cd.setRGB(text.getBackground().getRGB());
		cd.setText("Choose a color");

		RGB newColor = cd.open();
		if (newColor != null)
			Config.getInstance().setValue(Config.BACKGROUND_COLOR,
					new Color(Display.getCurrent(), newColor));
		updateFont();
	}

	public void changeFontColor() {
		ColorDialog cd = new ColorDialog(getShell());
		cd.setRGB(text.getBackground().getRGB());
		cd.setText("Choose a color");

		RGB newColor = cd.open();
		if (newColor != null)
			Config.getInstance().setValue(Config.FONT_COLOR,
					new Color(Display.getCurrent(), newColor));
		updateFont();
	}

	public boolean quickFindPrevious(String keyWord) {
		int index = text.getText().substring(0, text.getSelection().x)
				.lastIndexOf(keyWord);
		if (index == -1)
			return false;
		text.setSelection(index, index + keyWord.length());
		return true;
	}

	public boolean quickFindNext(String searchString) {
		if (searchString.isEmpty())
			return false;
		int index = text.getText().indexOf(searchString,
				text.getSelection().x + 1);
		if (index == -1)
			return false;
		text.setSelection(index, index + searchString.length());
		return true;
	}

	public void selectAll() {
		text.selectAll();
	}
}