/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.karaf.shell.impl.console.commands.help.wikidoc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

import org.apache.karaf.util.StringEscapeUtils;

/**
 * Parses wiki syntax from a reader and calls a Wikivisitor with the 
 * tokens it finds
 */
public class WikiParser {

	WikiVisitor visitor;
	
	public WikiParser(WikiVisitor visitor) {
		this.visitor = visitor;
	}

	public void parse(Reader reader) throws IOException {
		BufferedReader br = new BufferedReader(reader);
		String line;
		while ((line = br.readLine()) != null) {
			if (!line.startsWith("#")) {
				parse(line);
			}
		}
	}

	public void parse(String line) {
        String unescaped = StringEscapeUtils.unescapeJava(line);
		Tokenizer tokenizer = new Tokenizer(unescaped);
		String token;
		boolean bold = false;
		boolean first = true;
		while ((token = tokenizer.nextToken("\u001B[h*")) != null) {
			if (first) {
				first = false;
				int tabs = 0;
				for (int i = 0; i < token.length() && token.charAt(i) == '\t'; i++) {
					tabs++;
				}
				token = token.substring(tabs);
				for (int i = 0; i < tabs; i++) {
					token = "    " + token;
				}
				int i = 0;
				while (i < token.length() && token.charAt(i) == ' ') {
					i++;
				}
				visitor.startPara(i);
				token = token.substring(i);
			}
            if ("\u001B".equals(token)) {
                parseEsc(tokenizer, token);
            } else if ("[".equals(token)) {
				parseLink(tokenizer);
			} else if ("h".equals(token)) {
				parseHeading(tokenizer);
			} else if ("*".equals(token)) {
				parseEnumeration(tokenizer);
			} else if ("**".equals(token)) {
				bold = !bold;
				visitor.bold(bold);
			} else {
				visitor.text(token);
			}
		}
		if (first) {
			visitor.startPara(0);
		}
		visitor.endPara();
	}

    private void parseEsc(Tokenizer tokenizer, String token) {
        visitor.text(token + tokenizer.nextToken("\u001B[h*") + tokenizer.nextToken("\u001B[]"));
    }
	
	private void parseEnumeration(Tokenizer tokenizer) {
		String text = tokenizer.nextToken("-\n");
		visitor.enumeration(text.trim());
	}

	private void parseHeading(Tokenizer tokenizer) {
		String level = tokenizer.nextToken("123456789");
		if (!level.matches("[123456789]")) {
			visitor.text("h" + level);
			return;
		}
		String dot = tokenizer.nextToken(".\n");
		if (!".".equals(dot)) {
			visitor.text("h" + level + dot);
			return;
		}
		String heading = tokenizer.nextToken("\n");
		if (heading == null) {
			heading = "";
		}
		visitor.heading(Integer.parseInt(level), heading.trim());
	}

	private void parseLink(Tokenizer tokenizer) {
		String token = tokenizer.nextToken("]");
		visitor.link(token, "");
		tokenizer.nextToken("]");
	}

	public static class Tokenizer {

		final String str;
		int pos;

		public Tokenizer(String str) {
			this.str = str;
		}

		public String nextToken(String delim) {
			StringBuilder sb = new StringBuilder();
			boolean escape = false;
			boolean del = false;
			while (pos < str.length()) {
				char c = str.charAt(pos++);
				if (escape) {
					escape = false;
					sb.append(c);
				} else if (c == '\\') {
					if (del) {
						pos--;
						break;
					} else {
						escape = true;
					}
				} else if (delim.indexOf(c) >= 0) {
					if (sb.length() == 0 || del) {
						sb.append(c);
						del = true;
					} else {
						pos--;
						break;
					}
				} else {
					if (del) {
						pos--;
						break;
					}
					sb.append(c);
				}
			}
			return sb.length() > 0 ? sb.toString() : null;
		}

	}

}
