/**
 * Moxkiriya standalone Wiki.
 * Page data structure.
 * 
 * @author Ryuhei Terada
 * See the '<a href="{@docRoot}/copyright.html">Copyright</a>'
 */
package com.wiki.standalone.moxkiriya;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.regex.Pattern;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.version.Version;

import com.wiki.standalone.moxkiriya.util.FileIO;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * Page data structure.
 *
 */
public class PageData {
	/** JCR node */
	private Node node_ = null;

	/** Base version */
	private Version baseVersion_ = null;
	
	/** namespace: Main, File, or Category. */
	private SimpleStringProperty namespace_ = new SimpleStringProperty("");
	
	/** page title */
	private SimpleStringProperty title_ = new SimpleStringProperty("");
	
	/** content */
	private SimpleStringProperty content_ = new SimpleStringProperty("");;

	/** date */
	private SimpleStringProperty date_ = new SimpleStringProperty("");;

	/** check in Page list view */
	private SimpleBooleanProperty check_  = new SimpleBooleanProperty(false);
	
	/** categories */
	private ArrayList<String> categories_ = new ArrayList<String>();
	
	/** file data */
	private FileData fileData_ = null;

	/**
	 * Constructor.
	 */
	public PageData() {
	}

	/**
	 * Constructor.
	 * @throws Exception
	 */
	public PageData(Node node) throws Exception {
		setNode(node);
	}

	/**
	 * Node getter.
	 * @return Node
	 */
	public Node getNode() {
		return node_;
	}

	/**
	 * node setter
	 * @param node
	 * @throws Exception
	 */
	public void setNode(Node node) throws Exception {
		try {
			node_ = node;
	
			namespace_.setValue(node.getProperty(WikiRepository.PROPERTY_NAMESPACE).getString());
			title_.setValue(node.getProperty(WikiRepository.PROPERTY_TITLE).getString());
			content_.setValue(node.getProperty(WikiRepository.PROPERTY_CONTENT).getString());
			
			Value[] categories = node.getProperty(WikiRepository.PROPERTY_category).getValues();
			for(Value category: categories) {
				addCategory(category.getString());			
			}
			
			Node fileNode = node.getNode(WikiRepository.NODE_FILE);
			if(fileNode != null) {
				fileData_ = new FileData(fileNode);
			}
		} catch(PathNotFoundException e) {
			// Ignore exception.
		}
	}

	/**
	 * UUID property getter.
	 * @return StringProperty
	 */
	public StringProperty uuidProperty() {
		SimpleStringProperty property = new SimpleStringProperty();

		try {
			property.setValue(node_.getProperty(Property.JCR_UUID).getString());
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		
		return property;
	}

	/**
	 * Check property getter.
	 * @return BooleanProperty
	 */
	public BooleanProperty checkProperty() {
		return check_;
	}

	/**
	 * Namespace getter.
	 * @return String
	 */
	public String getNamespace() {
		return namespace_.getValue();
	}
	
	/**
	 * Namespace setter.
	 * @param namespace
	 */
	public void setNamespace(String namespace) {
		namespace_.setValue(namespace);
	}

	/**
	 * Title getter.
	 * @return String
	 */
	public String getTitle() {
		return title_.getValue();
	}
	
	/**
	 * Title setter.
	 * @param title
	 */
	public void setTitle(String title) {
		title_.setValue(title);
	}

	/**
	 * titleProperty getter.
	 * @return StringProperty
	 */
	public StringProperty titleProperty() {
		return title_;
	}

	/**
	 * Content getter.
	 * @return String
	 */
	public String getContent() {
		return content_.getValue();
	}
	
	/**
	 * Content setter.
	 * @param title
	 */
	public void setContent(String content) {
		content_.setValue(content);
		categories_ = extractCategoriesFromContent(content);
	}

	/**
	 * Introduction getter.
	 * Introduction is the first part of the page.
	 * The part is extracted until the following conditions are satisfied.
	 * <ul>
	 * <li>Up to the first heading.</li>
	 * <li>255 characters</li>
	 * <li>3 lines</li>
	 * </ul>
	 * @return String
	 * @throws Exception
	 */
	public String getIntroduction() throws Exception {
		BufferedReader buffreader = FileIO.bufferedReader(new StringReader(content_.getValue()));
		String         line;
		StringBuffer   buf        = new StringBuffer("");
		int            lineCount  = 0;
		int            charCount  = 0;
		Pattern        hnPattern  = Pattern.compile("^={2,6}[^=]+={2,6}$");
		
		while((line = buffreader.readLine()) != null) {
			if(hnPattern.matcher(line).matches() == true) {
				break;
			}
			else if(charCount + line.length() > 255) {
				buf.append(line.substring(0, 255 - charCount));
				charCount = 255;
				break;
			}
			else {
				buf.append(line + "\n");
				charCount += line.length() + 1;
				lineCount++;
				
				if(lineCount >= 3) {
					break;
				}
			}
		}
		
		String intro = buf.toString();
		
		return intro.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
	}
	
	/**
	 * contentProperty getter.
	 * @return StringProperty
	 */
	public StringProperty introductionProperty() {
		String introduction = "";
		try {
			introduction = getIntroduction();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return new SimpleStringProperty(introduction);
	}
	
	/**
	 * dateProperty getter.
	 * @return StringProperty
	 */
	public StringProperty dateProperty() {
		String date = "";
		try {
			date                = baseVersion_.getCreated().getTime().toString();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return new SimpleStringProperty(date);
	}

	/**
	 * Base version setter.
	 * @param baseVersion
	 */
	public void setBaseVersion(Version baseVersion) {
		baseVersion_ = baseVersion;
	}
	
	/**
	 * Categories getter.
	 * @return ArrayList<String>
	 */
	public ArrayList<String> getCategories() {
		return categories_;
	}
	
	/**
	 * Categories setter.
	 * @param category
	 */
	public void addCategory(String category) {
		categories_.add(category);
	}
	
	/**
	 * File data getter.
	 * @return FileData
	 */
	public FileData getFileData() {
		return fileData_;
	}
	
	/**
	 * Content setter.
	 * @param title
	 * @throws Exception 
	 */
	public void setFileData(File file) throws Exception {
		fileData_ = new FileData(file);
	}

	/**
	 * content から Categories を抽出する。
	 * @param content
	 * @return　ArrayList<String>
	 */
	private ArrayList<String> extractCategoriesFromContent(String content) {
		ArrayList<String> categories       = new ArrayList<String>();
		char[]            charArrayContent = content.toCharArray();
		int               count            = 0;

		while(count < charArrayContent.length) {
			if(charArrayContent[count] == '<') {
				String sub = content.substring(count);

				if(content.startsWith("<code>")) {
					count += extractCategoriesSkipElem(sub, "<code>", "</code>");
				}
				else if(sub.startsWith("<pre>")) {
					count += extractCategoriesSkipElem(sub, "<pre>", "</pre>");
				}
				else {
					count++;
				}
			}
			else if(charArrayContent[count] == '[') {
				if(content.startsWith("[[Category:", count)) {
					count += "[[Category:".length();
					int startCount = count;

					while(count < charArrayContent.length) {
						count++;
						if(charArrayContent[count] == ']') {
							if(content.startsWith("]]", count) == true) {
								int endCount   = count;
								
								String category = content.substring(startCount, endCount);
								categories.add(category);
								count += "]]".length();
								break;
							}
						}
					} 
				}
				else {
					count++;
				}
			}
			else {
				count++;
			}
		}
		
		return categories;
	}

	/**
	 * Categories抽出中の指定された要素{<pre>, <code>}をスキップする。
	 * @param content
	 * @param startElem
	 * @param endElem
	 * @return int
	 */
	private int extractCategoriesSkipElem(String content, String startElem, String endElem) {
		char[] charArrayContent = content.toCharArray();
		int    count            = 0;

		while(count < content.length()) {
			count++;
			if(charArrayContent[count] == '<') {
				String sub = content.substring(count);
				if(sub.startsWith(endElem) == true) {
					count += endElem.length();
					break;
				}
				else if(sub.startsWith(startElem) == true) {
					count += extractCategoriesSkipElem(sub, startElem, endElem);
				}
			}
		}
		
		return count;
	}

	/**
	 * File data structure.
	 */
	public class FileData {
		/** Node */
		private Node node_ = null;
		
		/** Mime type */
		private String mimeType_;

		/** Data within the file.*/
		private InputStream inputStream_;

		/** Timestamp of the file. */
		private Calendar lastModified_;

		/** character encoding. */
		private String encoding_;
		
		/**
		 * Constructor.
		 * @param file
		 * @throws Exception 
		 */
		public FileData(File file) throws Exception {
			setInputStream(file);
		}

		/**
		 * Constructor.
		 * @param node
		 * @throws Exception 
		 */
		public FileData(Node node) throws Exception {
			setNode(node);
		}

		/**
		 * Node getter.
		 * @return Node
		 */
		public Node getNode() {
			return node_;
		}

		/**
		 * Node setter.
		 * @param node
		 * @throws Exception
		 */
		public void setNode(Node node) throws Exception {
			node_ = node;
			setInputStream(node);
		}
		
		/**
		 * inputstream getter.
		 * @return InputStream
		 */
		public InputStream getInputStream() {
			return inputStream_;
		}

		/**
		 * Inputstream setter.
		 * @param file
		 * @throws Exception 
		 */
		public void setInputStream(File file) throws Exception {
			inputStream_ = new FileInputStream(file);
			mimeType_  = URLConnection.guessContentTypeFromName(file.getAbsolutePath());

			lastModified_ = Calendar.getInstance();
			lastModified_.setTimeInMillis(file.lastModified());

			if(mimeType_.startsWith("text/") == true) {
				encoding_ = "UTF-8";
			}
		}

		/**
		 * Inputstream setter.
		 * @param file
		 * @throws Exception 
		 */
		public void setInputStream(Node node) throws Exception {
			Node nodeResource = node.getNode(Property.JCR_CONTENT);

			inputStream_  = nodeResource.getProperty(Property.JCR_DATA).getBinary().getStream();
			mimeType_     = nodeResource.getProperty(Property.JCR_MIMETYPE).getString();
			lastModified_ = nodeResource.getProperty(Property.JCR_LAST_MODIFIED).getDate();

			if(mimeType_.startsWith("text/") == true) {
				encoding_ = nodeResource.getProperty(Property.JCR_ENCODING).getString();
			}
		}

		/**
		 * Mimetype getter.
		 * @return String
		 */
		public String getMimeType() {
			return mimeType_;
		}

		/**
		 * Last modified getter. 
		 * @return Calendar
		 */
		public Calendar getLastModified() {
			return lastModified_;
		}
	};
}
