/**
 * Moxkiriya standalone Wiki.
 * Wiki engine.
 * 
 * @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.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.ResourceBundle;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Value;
import javax.jcr.version.Version;
import javax.jcr.version.VersionIterator;

import com.wiki.standalone.moxkiriya.parser.blockparser.WikiBodyBlockParser;
import com.wiki.standalone.moxkiriya.util.FileIO;

/**
 * Wiki engine.
 *
 */
public class WikiEngine {
	/** Wiki Root directory */
	public static final String WIKIROOT_DIRECTORY = "wikiroot";

	/** Main page directory */
	public static final String MAINPAGE_TITLE = "MainPage";

	/** template file HTML<head>section. */
	public static final String TEMPLATE_HTMLHEADER_FILE = "templateHtmlHead.txt";

	/** fixedstylesheetファイル名 */
	public static final String FIXED_STYLESHEET_FILENAME = "fixedstylesheet.css";

	/** user stylesheetファイル名 */
	public static final String USER_STYLESHEET_FILENAME = "user.css";

	/** defaultstylesheetファイル名 */
	public static final String DEFAULT_STYLESHEET_FILENAME = "defaultstylesheet.css";


	/** jcr_uuid 独自属性 */
	public static final String ATTRIBUTE_JCR_UUID = "data-jcr_uuid";

	/** namespace "Cagegory" */
	public static final String NAMESPACE_CATEGORY = WikiRepository.PROPERTY_CATEGORY;

	/** namespace "File" */
	public static final String NAMESPACE_FILE = WikiRepository.PROPERTY_FILE;
	
	/** Wiki Repository. */
	protected WikiRepository wikiRepository_;

	/** Page data map. */
	private HashMap<String, PageData> pageDataMap_ = null;

	/** Wiki history manager. */
	private WikiHistory wikiHistory_ = null;

	/**
	 * Constructor.
	 */
	public WikiEngine() {
		wikiHistory_ = new WikiHistory();
	}

	/**
	 * Wiki repository getter.
	 * return wikiRepository
	 */
	public WikiRepository getWikiRepository() {
		return wikiRepository_;
	}

	/**
	 * Wiki repository setter.
	 * @param wikiRepository
	 */
	public void setWikiRepository(WikiRepository wikiRepository) {
		wikiRepository_ = wikiRepository;
	}

	/**
	 * 
	 * @param wikirootPath
	 * @param mainPageData
	 * @throws Exception 
	 */
	public void buildWikiRepository(File wikirootPath) throws Exception {
		wikiRepository_   = new WikiRepository(wikirootPath);

		PageData mainPageData = new PageData();
		mainPageData.setNamespace(WikiRepository.PROPERTY_MAIN);
		mainPageData.setTitle(MAINPAGE_TITLE);

		wikiRepository_.buildWikiRepository(mainPageData);		
	}

	/**
	 * PageData map setter.
	 * @param map
	 */
	public void setPageDataMap(HashMap<String, PageData> map) {
		pageDataMap_ = map;
	}
	
	/**
	 * PageData map setter.
	 * @param pageData
	 * @throws Exception 
	 */
	public void setPageDataMap(PageData pageData) throws Exception {
		String uuid = pageData.getNode().getProperty(Property.JCR_UUID).getString();
		pageDataMap_ = queryPageUUID(uuid);
	}
	
	/**
	 * PageData map setter.
	 * @param pageData
	 * @throws Exception 
	 */
	public void setPageDataMap(String title) throws Exception {
		pageDataMap_ = queryPageTitle(title);
	}

	/**
	 * PageData map getter.
	 * return HashMap<String, PageData>
	 */
	public HashMap<String, PageData> getPageDataMap() {
		return pageDataMap_;
	}

	/**
	 * NamespaceList getter.
	 * @return ArrayList<String>
	 * @throws Exception
	 */
	public ArrayList<String> getNamespaceList() throws Exception {
		ArrayList<String> namespaceList = new ArrayList<String>();
		Value[]           namespaces = wikiRepository_.getNamespaceList();

		for(Value value: namespaces) {
			namespaceList.add(value.getString());
		}

		return namespaceList;
	}

	/**
	 * Checkout.
	 * @throws Exception 
	 */
	public void checkout() throws Exception {
		PageData currentPageData = null;

		if(pageDataMap_.size() == 1) {
			currentPageData = pageDataMap_.values().iterator().next();
		}
		
		if(currentPageData != null) {
			wikiRepository_.checkout(currentPageData);
		}
	}

	/**
	 * Checkin.
	 * @throws Exception 
	 */
	public PageData checkin(PageData pageData) throws Exception {
		return wikiRepository_.checkin(pageData);
	}
	
	/**
	 * Cancel checkout.
	 * @throws Exception 
	 */
	public void cancelCheckout() throws Exception {
		PageData currentPageData = null;

		if(pageDataMap_.size() == 1) {
			currentPageData = pageDataMap_.values().iterator().next();
		}
		
		if(currentPageData != null) {
			wikiRepository_.cancelCheckout(currentPageData);
		}
	}
	
	/**
	 * Wikiシンタックスの文字列をHTML形式に変換する。
	 * @return HTMLコンテンツ
	 * @throws Exception 
	 */
	public String parse() throws Exception {
		PageData pageData = null;
		String   bodyHtml = "";

		if(pageDataMap_.size() == 1) {
			pageData = pageDataMap_.values().iterator().next();
			wikiHistory_.add(pageData);
			bodyHtml = markupContents(pageData);
		}
		else {
			bodyHtml = markupContentsList();
		}

		/*
		 * Header部の構築
		 */
		String         headerHtml = buildHtmlHeader();
		StringBuffer   buf        = new StringBuffer();

		buf.append(headerHtml);
		buf.append(bodyHtml);
		buf.append("</html>");

		return buf.toString();
	}

	/**
	 * Wikiシンタックスの文字列をHTML形式に変換する。
	 * @param pageData
	 * @return HTMLコンテンツ
	 * @throws Exception 
	 */
	public String parse(PageData pageData) throws Exception {
		String bodyHtml = markupContents(pageData);

		/*
		 * Header部の構築
		 */
		String         headerHtml = buildHtmlHeader();
		StringBuffer   buf        = new StringBuffer();

		buf.append(headerHtml);
		buf.append(bodyHtml);
		buf.append("</html>");

		return buf.toString();
	}

	/**
	 * Wikiシンタックスの文字列をHTML形式に変換する。
	 * @return HTMLコンテンツ
	 * @throws Exception 
	 */
	public String markupContents(PageData pageData) throws Exception {
		WikiBodyBlockParser bodyBlockParser = new WikiBodyBlockParser(this);
		String              bodyHtml        = "";

		/*
		 * Contentsのparse & Body部の構築.
		 */
		bodyBlockParser.parsePageTitle(pageData.getTitle());
		BufferedReader buffreader = FileIO.bufferedReader(new StringReader(pageData.getContent()));

		bodyHtml = bodyBlockParser.parse(buffreader)
				+ buildCategoryList(pageData);

		buffreader.close();

		return bodyHtml;
	}

	/**
	 * PageリストをHTML形式で生成する。
	 * @return HTMLコンテンツ
	 * @throws Exception 
	 */
	public String markupContentsList() throws Exception {
		StringBuffer   bodybuf = new StringBuffer();
		ResourceBundle resources_ = ResourceBundle.getBundle("com.wiki.standalone.moxkiriya.resources.moxkiriya");

		bodybuf.append("<h1>" + resources_.getString("key.Search.Result") + "</h1>\n");
		if(pageDataMap_.size() > 1) {
			bodybuf.append("<div>\n");
			bodybuf.append(resources_.getString("key.Message.found.pages") + "\n");
			bodybuf.append("</div>\n");
	
			for(String uuid: pageDataMap_.keySet()) {
				PageData pageData = pageDataMap_.get(uuid);
				
				bodybuf.append("<h3>\n");
				bodybuf.append("<a href="
						+ "\"" + pageData.getTitle() + "\" "
						+ ATTRIBUTE_JCR_UUID + "="
						+ pageData.getNode().getProperty(Property.JCR_UUID).getString()
						+ ">");
				bodybuf.append(pageData.getTitle());
				bodybuf.append("</a>\n");
				bodybuf.append("</h3>\n");
				bodybuf.append("<div>\n");
				bodybuf.append(pageData.getIntroduction());
				bodybuf.append("</div>\n");
			}
		}
		else {
			/*
			 * No page was found.
			 */
			bodybuf.append("<div>\n");
			bodybuf.append(resources_.getString("key.search.result.message.nopage") + "\n");
			bodybuf.append("</div>\n");				
		}
		
		return bodybuf.toString();
	}
	
	/**
	 * HTMLヘッダ部を構築する。
	 * @return HTMLヘッダ部
	 * @throws IOException
	 */
	public static String buildHtmlHeader() throws IOException {
		StringBuffer   stringBuf      = new StringBuffer();
		SettingManager settingMgr     = SettingManager.getInstance();
		File           headerTemplate = new File(settingMgr.getAbsolutePath(TEMPLATE_HTMLHEADER_FILE));
		InputStream  in        = new FileInputStream(headerTemplate);
		byte         byteBuf[] = new byte[255];
		int          size;

		while((size = in.read(byteBuf)) != -1) {
			stringBuf.append(new String(byteBuf, 0, size));
		}
		in.close();
		File           stylesheetFile;

		stylesheetFile = new File(settingMgr.getAbsolutePath(DEFAULT_STYLESHEET_FILENAME));		
		stringBuf.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\""
				+ stylesheetFile.toURI().toString()
				+ "\">\n");
		
		stylesheetFile = new File(settingMgr.getAbsolutePath(USER_STYLESHEET_FILENAME));		
		stringBuf.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\""
						+ stylesheetFile.toURI().toString()
						+ "\">\n");

		stylesheetFile = new File(settingMgr.getAbsolutePath(FIXED_STYLESHEET_FILENAME));
		stringBuf.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\""
					+ stylesheetFile.toURI().toString()
					+ "\">\n");

		stringBuf.append("<script>\n"
					+ "function scrollTo(elementId) {\n"
					+ "\tdocument.getElementById(elementId).scrollIntoView()\n"
					+ "}\n"
					+ "</script>");
		
		stringBuf.append("</head>\n");
		
		return stringBuf.toString();
	}

	private String buildCategoryList(PageData pageData) throws Exception {
		StringBuffer      buf = new StringBuffer("");
		ArrayList<String> categoryies = pageData.getCategories();

		for(String category: categoryies) {
			if(category.isEmpty() != true) {
				HashMap<String, PageData> categoryPageMap = queryPageTitle(category, NAMESPACE_CATEGORY);
				
				if(categoryPageMap.size() == 1) {
					PageData categoryPage = categoryPageMap.values().iterator().next();
					Node     node         = categoryPage.getNode();
					
					buf.append("<a href=" + "\"" + category + "\" ");
					buf.append(ATTRIBUTE_JCR_UUID + "=\"");
					buf.append(node.getProperty(Property.JCR_UUID).getString());
					buf.append("\">");
					buf.append(category);
					buf.append("</a> ");
				}
			}
		}
		
		String startElem = "";
		String endElem   = "";
		
		if(buf.length() > 0) {
			startElem = "<div class=\"categorylist\"> Categories: \n";
			endElem   = "</div>";
		}
		
		return startElem + buf.toString() + endElem;				
	}
	
	
	/**
	 * Execute query node by namespace.
	 * @param namespace
	 * @return HashMap<String, PageData>
	 * @throws Exception
	 */
	public HashMap<String, PageData> queryPageNamespace(String namespace) throws Exception {
		return wikiRepository_.queryPageNamespace(namespace);
	}

	/**
	 * Execute query node by UUID.
	 * @param uuid
	 * @return HashMap<String, PageData>
	 * @throws Exception
	 */
	public HashMap<String, PageData> queryPageUUID(String uuid) throws Exception {
		return wikiRepository_.queryPageUUID(uuid);
	}

	/**
	 * Execute query node by pageTitle.
	 * @param pageTitle
	 * @param namespace
	 * @return HashMap<String, PageData>
	 * @throws Exception
	 */
	public HashMap<String, PageData> queryPageTitle(String pageTitle, String namespace) throws Exception {
		return wikiRepository_.queryPageTitle(pageTitle, namespace);
	}

	/**
	 * Execute query node by pageTitle.
	 * @param pageTitle
	 * @return HashMap<String, PageData>
	 * @throws Exception
	 */
	public HashMap<String, PageData> queryPageTitle(String pageTitle) throws Exception {
		return wikiRepository_.queryPageTitle(pageTitle);
	}

	/**
	 * Execute query full text search.
	 * @param searchKey
	 * @return HashMap<String, PageData>
	 * @throws Exception
	 */
	public HashMap<String, PageData> queryPageFullTextSearch(String searchKey) throws Exception {
		return wikiRepository_.queryPageFullTextSearch(searchKey);
	}

	/**
	 * Test namespace is contains namespaceList.
	 * @param namespace
	 * @return boolean
	 * @throws Exception
	 */
	public boolean isContainsNamespaceList(String namespace) throws Exception {
		return wikiRepository_.isContainsNamespaceList(namespace);
	}

	/**
	 * Delete page node.
	 * @param pagedata
	 * @throws Exception 
	 */
	public void deletePage(PageData pagedata) throws Exception {
		wikiRepository_.deletePage(pagedata);
	}
	
	/**
	 * Refresh session.
	 * @throws Exception 
	 */
	public void refreshSession() throws Exception {
		wikiRepository_.refreshSession();
		if(pageDataMap_ != null) {
			if(pageDataMap_.size() == 1) {
				PageData pageData = pageDataMap_.values().iterator().next();
				String   uuid     = pageData.getNode().getProperty(Property.JCR_UUID).getString();
				
				pageDataMap_ = wikiRepository_.queryPageUUID(uuid);
			}
		}
	}

	/**
	 * Is there an older node on a history list.
	 * @return boolean
	 */
	public boolean canBack() {
		return wikiHistory_.canBack();
	}

	/**
	 * Is there an newer node on a history list.
	 * @return boolean
	 */
	public boolean canForward() {
		return wikiHistory_.canForward();
	} 

	/**
	 * Execute history back.
	 * @throws Exception 
	 */
	public String back() throws Exception {
		PageData pageData = wikiHistory_.back();
		setPageDataMap(pageData);
		return parse(pageData);
	}

	/**
	 * Execute history forward.
	 * @return PageData
	 * @throws Exception 
	 */
	public String forward() throws Exception {
		PageData pageData = wikiHistory_.forward();
		setPageDataMap(pageData);
		return parse(pageData);
	}

	/**
	 * History clear.
	 */
	public void clearHistory() {
		wikiHistory_.clear();
	}
	
	/**
	 * VersionHistory getter.
	 * @return VersionIterator
	 * @throws Exception 
	 */
	public VersionIterator getVersionHistory() throws Exception {
		PageData pageData = pageDataMap_.values().iterator().next();
		String   uuid     = pageData.getNode().getProperty(Property.JCR_UUID).getString();

		return wikiRepository_.getVersionHistory(uuid);
	}

	/**
	 * Base version getter.
	 * @return Version
	 * @throws Exception
	 */
	public Version getBaseVersion() throws Exception {
		PageData pageData = pageDataMap_.values().iterator().next();

		return wikiRepository_.getBaseVersion(pageData.getNode());
	}
	
	/**
	 * Restore version.
	 * @param version
	 * @throws Exception 
	 */
	public void restoreVersion(Version version) throws Exception {
		PageData pageData = pageDataMap_.values().iterator().next();
		wikiRepository_.restoreVersion(version, pageData.getNode());
	}

	/**
	 * Import system vies.
	 * @param outputFile
	 * @throws Exception
	 */
	public void importSystemView(File outputFile) throws Exception {
		wikiRepository_.importSystemView(outputFile);
	}
	
	/**
	 * Export system view.
	 * @param outputFile
	 * @throws Exception 
	 */
	public void exportSystemView(File outputFile) throws Exception {
		wikiRepository_.exportSystemView(outputFile);
	}
	
	/**
	 * Session closer.
	 */
	public void closeSession() {
		wikiRepository_.closeSession();
	}
}
