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

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.jcr.Node;
import javax.jcr.Property;

import org.apache.jackrabbit.util.Base64;

import com.wiki.standalone.moxkiriya.PageData;
import com.wiki.standalone.moxkiriya.WikiEngine;
import com.wiki.standalone.moxkiriya.WikiRepository;
import com.wiki.standalone.moxkiriya.util.FileTypeDeterminator;

/**
 * Wiki internal inline parser.
 * Transform [[linkTarget|Title]] to <a href="linkTarget">Title</a>
 */
public class WikiInternalLinkInlineParser extends WikiInlineParserBase {
	/** WIKI開始トークン */
	public static final String WIKI_TOKEN_START = "[[";

	/** WIKI開始トークン */
	public static final String WIKI_TOKEN_END   = "]]";

	/** WIKI記法の正規表現文字列　先頭頭パターン */
	public static final String PATTERN_START = Pattern.quote(WIKI_TOKEN_START)
												+ "[^"
												+ Pattern.quote("[")
												+ "]";

	/** WIKI記法の正規表現文字列　末尾パターン */
	public static final String PATTERN_END = "[^]]"
												+ Pattern.quote(WIKI_TOKEN_END);

	/** アンカー開始タグ */
	private static final String START_TAG = "<a href=";

	/** アンカー終了タグ */
	private static final String END_TAG = "</a>\n";

	/** jcr_uuid 独自属性 */
	public static final String ATTRIBUTE_JCR_UUID = WikiEngine.ATTRIBUTE_JCR_UUID;

	/** img開始タグ */
	private static final String IMAGE_START_TAG = "<img src=";

	/** img終了タグ */
	private static final String IMAGE_END_TAG = "</img>";

	/** ページ名接頭辞 "File:" */
	private static final String PAGENAME_PREFIX_FILE = WikiEngine.NAMESPACE_FILE + ":";

	/** ページ名接頭辞 ":File:" */
	private static final String PAGENAME_PREFIX_COLONFILE = ":" + WikiEngine.NAMESPACE_FILE + ":";

	/** ページ名接頭辞 "Category:" */
	private static final String PAGENAME_PREFIX_CATEGORY = WikiEngine.NAMESPACE_CATEGORY + ":";

	/** ページ名接頭辞 ":Category:" */
	private static final String PAGENAME_PREFIX_COLONCATEGORY = ":" + WikiEngine.NAMESPACE_CATEGORY + ":";

	/** ページタイトルマクロ "#title" */
	private static final String PAGENAME_HASH_TITLE = "#title";

	/** 存在しないファイルのスタイルクラス */
	private static final String STYLE_CLASS_NOEXIST = "class=\"noexist\"";
	
	/** リンク先のページ名 */
	private String pagename_;
	
	/** リンク先ファイルのフルパス */
	private String linkpath_;

	/** リンクモード */
	private enum LinkMode {
		NONE,
		INNERLINK,
		IMAGERENDER,
		ATTACHEDFILELINK
	};
	
	/** リンクモード */
	private LinkMode linkMode;

	/**
	 * テキストノード処理クラス
	 */
	private interface TextNodeProcessor {
		/**
		 * テキストノードを処理する
		 * @param buf
		 * @param textNode
		 */
		void textNodeProcess(StringBuffer buf, String textNode);
	};

	private final LinkedHashMap<LinkMode, TextNodeProcessor> textNodeProcessorMap = new LinkedHashMap<LinkMode, TextNodeProcessor>() {
		private static final long serialVersionUID = 1L;
		{
			put(LinkMode.NONE, new TextNodeProcessor() {
				@Override
				public void textNodeProcess(StringBuffer buf, String textNode) {
					
				}
			});
		}
		{
			put(LinkMode.INNERLINK, new TextNodeProcessor() {
				@Override
				public void textNodeProcess(StringBuffer buf, String textNode) {
					if(linkpath_.isEmpty() != true) {
						if( (textNode.startsWith(PAGENAME_PREFIX_COLONFILE) == true)
						 || (textNode.startsWith(PAGENAME_PREFIX_COLONCATEGORY) == true)) {
							textNode = textNode.replaceFirst(Pattern.quote(":"), "");
						}

						buf.append(textNode);
					}
				}
			});			
		}
		{
			put(LinkMode.IMAGERENDER, new TextNodeProcessor() {
				@Override
				public void textNodeProcess(StringBuffer buf, String textNode) {
					try {
						String       mediaType = null;
						StringBuffer databuf   = new StringBuffer("");

						HashMap<String, PageData> pageDataMap = wikiEngine_.queryPageTitle(linkpath_);
						PageData.FileData fileData = null;
						PageData          pageData = null;

						if(pageDataMap.isEmpty() != true) {
							/*
							 * File名前空間のpageは重複しないため、最初に見つかったnodeを対象にする
							 */
							pageData = pageDataMap.values().iterator().next();
							fileData = pageData.getFileData();
						}

						if(fileData == null) {
							/*
							 * 表示/編集中のページデータを取得する
							 */
							HashMap<String, PageData> nowPageDataMap = wikiEngine_.getPageDataMap();
							PageData nowPageData = nowPageDataMap.values().iterator().next();
							if(nowPageData.getNamespace().equals(WikiRepository.PROPERTY_FILE) == true) {
								String title = nowPageData.getTitle();
								if(linkpath_.equals(WikiRepository.PROPERTY_FILE + ":" + title) == true) {
									/*
									 * parse中のページのtitleとリンク対象のページが同一の場合、
									 * PageData内のFileを設定する。
									 * File:タイトルを新規編集中でAttach file textboxにパス名が入力されているとき用の処理
									 */
									fileData = nowPageData.getFileData();
								}
							}
						}
						
						if(fileData != null) {
							mediaType   = fileData.getMimeType();

							InputStream inputStream = fileData.getInputStream();
							ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

							if(mediaType.startsWith("image/") == true) {
								Base64.encode(inputStream, outputStream);
								databuf.append(outputStream.toString());
							}

							buf.append(IMAGE_START_TAG);
							if(databuf.length() > 0) {
								buf.append("\"data:");
								buf.append(mediaType + ";base64,");
								buf.append(databuf);
								buf.append("\"");
							}
							buf.append(" alt=\"");
							buf.append(textNode);
							buf.append("\"");
							buf.append(" title=\"");
							buf.append(textNode);
							buf.append("\" >");
							buf.append(IMAGE_END_TAG);
						}
						else {
							buf.append(textNode);
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});			
		}
		{
			put(LinkMode.ATTACHEDFILELINK, new TextNodeProcessor() {
				@Override
				public void textNodeProcess(StringBuffer buf, String textNode) {
					String basename = linkpath_.substring(linkpath_.lastIndexOf("\\") + "\\".length());
					buf.append(basename);
				}
			});			
		}
	};
	
	/**
	 * コンストラクタ
	 * @param wikiRepository
	 */
	public WikiInternalLinkInlineParser(WikiEngine wikiEngine) {
		super(wikiEngine);
	}

	/**
	 * lineとWiki記法をマッチングする
	 * @param line
	 * @return マッチング結果
	 */
	public static boolean matches(String line) {
		boolean isMatch = false;
		
		Matcher startMatcher
			= Pattern.compile(WikiInternalLinkInlineParser.PATTERN_START)
					.matcher(line);
		
		if(startMatcher.find() == true) {
			/*
			 * PATTERN_STARTの開始位置を取得
			 */						
			int     startIndex = startMatcher.start();
			
			/*
			 * PATTERN_ENDの開始位置を取得
			 */
			String   subline             = line.substring(startIndex);
			Matcher  endMatcher = Pattern.compile(WikiInternalLinkInlineParser.PATTERN_END)
						.matcher(subline);
			
			if(endMatcher.find() == true) {
				isMatch = true;
			}
		}
		
		return isMatch;
	}

	@Override
	public String[] devideLine(String line) {
		char[]   charArrayLine = line.toCharArray();
		int      startIndex    = -1;
		int      endIndex      = -1;
		boolean  withinElem    = false;
		
		for(int count = 0; count < charArrayLine.length; count++) {
			if(charArrayLine[count] == '>') {
				withinElem = false;
				continue;
			} else if(charArrayLine[count] == '<') {
				withinElem = true;
			}
			
			if(withinElem == true) {
				/*
				 * HTML要素内をスキップ
				 */
				continue;
			}

			if(charArrayLine[count] == '[') {
				String startString = line.substring(count);
				if(startString.startsWith(WIKI_TOKEN_START) == true) {
					startIndex = count;
				}
			}
			else if(charArrayLine[count] == ']') {
				if(startIndex != -1) {
					String endString = line.substring(count);
					if(endString.startsWith(WIKI_TOKEN_END) == true) {
						endIndex = count;
						break;
					}
				}
			}
		}
		
		String split0 = line.substring(0, startIndex);
		String split1 = line.substring(startIndex, endIndex + 2);
		String split2 = line.substring(endIndex + 2);
		
		return new String[] {split0.replaceAll(Pattern.quote("["), "&#91;")
								.replaceAll(Pattern.quote("]"), "&#93;"),
							 split1,
							 split2};
	}	

	@Override
	public String deleteWikiToken(String line) {
		String deleteTop   = line.replaceFirst(Pattern.quote(WIKI_TOKEN_START), "");
		String deleteToken = deleteTop.substring(0, deleteTop.lastIndexOf(WIKI_TOKEN_END));
		String textNode    = deleteToken;

		if(deleteToken.contains("|") == true) {
			pagename_ = deleteToken.substring(0, deleteToken.indexOf("|"));
			textNode  = deleteToken.substring(deleteToken.indexOf("|") + "|".length());

			if(textNode.equals(PAGENAME_HASH_TITLE) == true) {
				textNode = pagename_.substring(pagename_.lastIndexOf(":") + ":".length());
			}
		}
		else {
			pagename_ = deleteToken;
		}
		
		return textNode;
	}

	@Override
	public void startElementProcess(StringBuffer buf) {
		try {
			linkpath_ = buildLinkpathFromPagename(pagename_);

			if(linkpath_.isEmpty() != true) {
				buf.append(START_TAG);
				buf.append("\"");
				buf.append(linkpath_ + "\" ");
	
				HashMap<String, PageData> pageDataMap = wikiEngine_.queryPageTitle(linkpath_);			
				if(pageDataMap.size() == 0) {
					buf.append(STYLE_CLASS_NOEXIST);
				}
				else if(pageDataMap.size() == 1) {
					Set<String> key      = pageDataMap.keySet();
					PageData    pageData = pageDataMap.get(key.iterator().next());
					Node        node     = pageData.getNode();
					buf.append(" " + ATTRIBUTE_JCR_UUID + "=\"");
					buf.append(node.getProperty(Property.JCR_UUID).getString());
					buf.append("\"");
				}
				buf.append(">");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void textNodeProcess(StringBuffer buf, String textNode) {
		textNodeProcessorMap.get(linkMode).textNodeProcess(buf, textNode);
	}
	
	@Override
	public String getStartPattern() {
		return PATTERN_START;
	}

	@Override
	public void endElementProcess(StringBuffer buf) {
		if(linkpath_.isEmpty() != true) {
			buf.append(END_TAG);
		}
		linkMode = LinkMode.NONE;
	}

	@Override
	public String getEndPattern() {
		return PATTERN_END;
	}

	/**
	 * pagenameからLinkパスを構築する
	 * @param pagename
	 * @return Linkパス
	 * @throws Exception
	 */
	private String buildLinkpathFromPagename(String pagename) throws Exception {
		String linkpath = null;

		if(pagename.startsWith(PAGENAME_PREFIX_FILE) == true) {
			if(FileTypeDeterminator.isImageFile(pagename) == true) {
				linkMode = LinkMode.IMAGERENDER;
				linkpath = buildImageLinkpath(pagename);
			}
			else {
				linkMode = LinkMode.ATTACHEDFILELINK;
				linkpath = buildAttachLinkpath(pagename);
			}
		}
		else if(pagename.startsWith(PAGENAME_PREFIX_CATEGORY) == true) {
			linkMode = LinkMode.INNERLINK;
			linkpath = "";
		}
		else {
			linkMode = LinkMode.INNERLINK;
			linkpath = buildInternalLinkpath(pagename);
		}
		
		return linkpath;
	}

	/**
	 * イメージリンクパスを構築する。
	 * @param pagename
	 * @return イメージリンクパス
	 */
	private String buildAttachLinkpath(String pagename) {
		/*
		 * 先頭の":"を削除
		 */
		return pagename.substring(1);
	}

	/**
	 * イメージリンクパスを構築する。
	 * @param pagename
	 * @return イメージリンクパス
	 */
	private String buildImageLinkpath(String pagename) {
		return pagename;
	}

	/**
	 * 内部リンクパスを構築する
	 * @param pagename
	 * @return 内部リンクパス
	 * @throws Exception
	 */
	private String buildInternalLinkpath(String pagename) throws Exception {
		if( (pagename.startsWith(PAGENAME_PREFIX_COLONFILE) == true)
		 || (pagename.startsWith(PAGENAME_PREFIX_COLONCATEGORY) == true)) {
			pagename = pagename.replaceFirst(Pattern.quote(":"), "");
		}
		
		return pagename;
	}
}
