/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed 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 net.morilib.nina;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.morilib.automata.DFA;
import net.morilib.util.CharCodes;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/13
 */
public final class Nina {

	/**
	 * 
	 */
	public static final String VERSION = "0.4.8";

	//
	private static final Pattern FANM = Pattern.compile(
			"(.*/)?([^/.]+)(\\..*)?");
	private static final Pattern CDLM = Pattern.compile("-+");

	//
	private Nina() {}

	//
	static Object prendClasse(String s) {
		return prendClasse("", s);
	}

	//
	static Object prendClasse(String d, String n) {
		String s = n;
		Class<?> c;

		try {
			if(s.indexOf('.') < 0)  s = d + "." + n;
			c = Class.forName(s);
			return c.newInstance();
		} catch(ClassNotFoundException e) {
			throw new NinaException(e, "reflecterror");
		} catch(InstantiationException e) {
			throw new NinaException(e, "reflecterror");
		} catch(IllegalAccessException e) {
			throw new NinaException(e, "reflecterror");
		}
	}

	//
	@SuppressWarnings({ "rawtypes", "unchecked" })
	static void apelle(Object o, String s, Object... os) {
		Class[] a = new Class[os.length];
		Class c = o.getClass();
		Method m;

		try {
			for(int i = 0; i < os.length; i++) {
				a[i] = os[i].getClass();
			}
			m = c.getMethod(s, a);
			m.invoke(o, os);
		} catch(SecurityException e) {
			throw new NinaException(e, "reflecterror");
		} catch(NoSuchMethodException e) {
			throw new NinaException(e, "reflecterror");
		} catch(IllegalArgumentException e) {
			throw new NinaException(e, "reflecterror");
		} catch(IllegalAccessException e) {
			throw new NinaException(e, "reflecterror");
		} catch(InvocationTargetException e) {
			throw new NinaException(e, "reflecterror");
		}
	}

	//
	@SuppressWarnings({ "rawtypes", "unchecked" })
	static void apelleInt(Object o, String s, int n) {
		Class c = o.getClass();
		Method m;

		try {
			m = c.getMethod(s, Integer.TYPE);
			m.invoke(o, n);
		} catch(SecurityException e) {
			throw new NinaException(e, "reflecterror");
		} catch(NoSuchMethodException e) {
			throw new NinaException(e, "reflecterror");
		} catch(IllegalArgumentException e) {
			throw new NinaException(e, "reflecterror");
		} catch(IllegalAccessException e) {
			throw new NinaException(e, "reflecterror");
		} catch(InvocationTargetException e) {
			throw new NinaException(e, "reflecterror");
		}
	}

	static int prendCharcode(String charset, int c) {
		int x;

		try {
			x = CharCodes.unicodeToOtherCode((char)c, charset);
			return x;
		} catch (UnsupportedEncodingException e) {
			throw new NinaException(e, "unsupportedcharset",
					charset);
		}
	}

	static String getAutomatonName(String s) {
		if(s.equalsIgnoreCase("full")) {
			return "DFABuilder";
		} else if(s.equalsIgnoreCase("NFA")) {
			return "NFABuilder";
		} else if(s.equalsIgnoreCase("Markov")) {
			return "MarkovBuilder";
		} else if(s.endsWith("Builder")) {
			return s;
		} else {
			throw new NinaException("subautomatonnotfound", s);
		}
	}

	//
	static int len(char c) {
		Character.UnicodeBlock b;

		b = Character.UnicodeBlock.of(c);
		if(b == null) {
			return 1;
		} else if(c >= 0xff01 && c <= 0xff60) {
			return 2;
		} else if(c >= 0xffe0 && c <= 0xffe6) {
			return 2;
		} else if(b.equals(Character.UnicodeBlock.CJK_COMPATIBILITY) ||
				b.equals(Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS) ||
				b.equals(Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS) ||
				b.equals(Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT) ||
				b.equals(Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT) ||
//				b.equals(Character.UnicodeBlock.CJK_STROKES) ||
				b.equals(Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION) ||
				b.equals(Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) ||
				b.equals(Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) ||
				b.equals(Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B) ||
				b.equals(Character.UnicodeBlock.KATAKANA) ||
				b.equals(Character.UnicodeBlock.HIRAGANA) ||
				b.equals(Character.UnicodeBlock.HANGUL_SYLLABLES)) {
			return 2;
		} else {
			return 1;
		}
	}

	static int len(String s) {
		int r = 0;

		for(int i = 0; i < s.length(); i++) {
			r = r + len(s.charAt(i));
		}
		return r;
	}

	static int[] toiarray(String s) {
		int[] a;
		int m;

		a = new int[len(s)];
		for(int i = 0, j = 0; j < s.length(); i += m, j++) {
			m = len(s.charAt(j));
			a[i] = s.charAt(j);
			for(int k = i + 1; k < i + m; k++) {
				a[k] = Quadro.EQ_TO_LEFT;
			}
		}
		return a;
	}

	//
	static InputStream findLib(String n, List<String> libs) {
		File f;

		if((f = new File(n)).isFile()) {
			try {
				return new FileInputStream(f);
			} catch(FileNotFoundException e) {
				throw new RuntimeException(e);
			}
		} else {
			for(String s : libs) {
				f = new File(new File(s), n);
				if(f.isFile()) {
					try {
						return new FileInputStream(f);
					} catch(FileNotFoundException e) {
						throw new RuntimeException(e);
					}
				}
			}
			return null;
		}
	}

	private static final Pattern PTN1 = Pattern.compile(
			"#header[ \t]+(.*)[ \t]+(.*)([ \t]+(.*))?");

	static String[] readHeader(String fn,
			List<String> l) throws IOException {
		String[] a = new String[3];
		BufferedReader r = null;
		InputStream i;
		Matcher m;
		String s;

		try {
			if((i = findLib(fn, l)) == null) {
				throw new NinaException("filenotfound", fn);
			}
			r = new BufferedReader(new InputStreamReader(i));
			s = r.readLine();
			if(s != null && (m = PTN1.matcher(s)).matches()) {
				a[0] = m.group(1);
				a[1] = m.group(2);
				a[2] = m.group(4);
			}
			return a;
		} finally {
			if(r != null)  r.close();
		}
	}

	static List<int[]> readImport(String fn, List<String> lb,
			String enc) throws IOException {
		BufferedReader r = null;
		boolean b = false;
		List<int[]> l;
		Reader d;
		String s;

		try {
			if(enc != null) {
				try {
					d = new InputStreamReader(findLib(fn, lb), enc);
				} catch(UnsupportedEncodingException e) {
					d = new InputStreamReader(findLib(fn, lb));
				}
			} else {
				d = new InputStreamReader(findLib(fn, lb));
			}
			r = new BufferedReader(d);
			l = new ArrayList<int[]>();
			while((s = r.readLine()) != null) {
				if(b) {
					b = !CDLM.matcher(s).matches();
				} else if(!(b = CDLM.matcher(s).matches())){
					l.add(toiarray(s));
				}
			}
			return l;
		} finally {
			if(r != null)  r.close();
		}
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public static String getFAName(NinaSubautomata b, String s) {
		Matcher m;

		if(b != null && NinaSubautomata.MAIN.equals(s)) {
			return b.getMainname();
		} else if(!(m = FANM.matcher(s)).matches()) {
			throw new RuntimeException();
		} else {
			return m.group(2);
		}
	}

	/**
	 * 
	 * @param s
	 * @param prms
	 * @param sub
	 * @return
	 */
	public static NinaAction compile(String name, String s,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons) {
		return NinaParser.compile(Quadro.read(name, s, l), null, null,
				prms, sub, cons, cons, 0);
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @param libs
	 * @param prms
	 * @param sub
	 * @return
	 * @throws IOException
	 */
	public static NinaAction compile(String name, Reader rd,
			List<String> libs, Map<String, String> prms,
			NinaSubautomata sub,
			NinaConsole cons) throws IOException {
		return NinaParser.compile(Quadro.read(name, rd, libs), null,
				libs, prms, sub, cons, cons, 0);
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @param prms
	 * @param sub
	 * @return
	 * @throws IOException
	 */
	public static NinaAction compile(String name, Reader rd,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons) throws IOException {
		return NinaParser.compile(Quadro.read(name, rd, l), null, null,
				prms, sub, cons, cons, 0);
	}

	/**
	 * 
	 * @param name
	 * @param prms
	 * @param sub
	 * @return
	 * @throws IOException
	 */
	public static NinaAction compileResource(String name,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons) throws IOException {
		return NinaParser.compile(Quadro.readResource(name, l),
				null, null, prms, sub, cons, cons, 0);
	}

	/**
	 * 
	 * @param s
	 * @param prms
	 * @param sub
	 * @param pause
	 * @return
	 */
	public static NinaAction trace(String name, String s,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons, boolean pause) {
		return NinaParser.compile(Quadro.read(name, s, l), null, null,
				prms, sub, cons, cons,
				NinaParser._PRINT | (pause ? NinaParser._PAUSE : 0));
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @param prms
	 * @param sub
	 * @param pause
	 * @return
	 * @throws IOException
	 */
	public static NinaAction trace(String name, Reader rd,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons,
			boolean pause) throws IOException {
		return NinaParser.compile(Quadro.read(name, rd, l), null, null,
				prms, sub, cons, cons,
				NinaParser._PRINT | (pause ? NinaParser._PAUSE : 0));
	}

	/**
	 * 
	 * @param name
	 * @param prms
	 * @param sub
	 * @param pause
	 * @return
	 * @throws IOException
	 */
	public static NinaAction traceResource(String name,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons,
			boolean pause) throws IOException {
		return NinaParser.compile(Quadro.readResource(name, l),
				null, null, prms, sub, cons, cons,
				NinaParser._PRINT | (pause ? NinaParser._PAUSE : 0));
	}

	//
	@SuppressWarnings("unchecked")
	private static NinaPattern cst(Object o) {
		if(o instanceof NinaNFA) {
			return new NinaNFAPattern((NinaNFA)o);
		} else if(o instanceof DFA) {
			return new NinaDFAPattern((DFA<Object, Object, Void>)o);
		} else {
			throw new ClassCastException();
		}
	}

	/**
	 * 
	 * @param name
	 * @param s
	 * @param prms
	 * @param sub
	 * @return
	 */
	public static NinaPattern pattern(String name, String s,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons) {
		return cst(compile(name, s, prms, sub, l, cons).getMachine());
	}

	/**
	 * 
	 * @param name
	 * @param s
	 * @param prms
	 * @param sub
	 * @return
	 */
	public static NinaPattern pattern(String name, String s,
			Map<String, String> prms, NinaSubautomata sub,
			NinaConsole cons) {
		return cst(compile(name, s, prms, sub,
				Collections.<String>emptyList(), cons).getMachine());
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @param prms
	 * @param sub
	 * @return
	 * @throws IOException
	 */
	public static NinaPattern pattern(String name, Reader rd,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons) throws IOException {
		return cst(compile(name, rd, prms, sub, l, cons));
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @param prms
	 * @param sub
	 * @param cons
	 * @return
	 * @throws IOException
	 */
	public static NinaPattern pattern(String name, Reader rd,
			Map<String, String> prms, NinaSubautomata sub,
			NinaConsole cons) throws IOException {
		return cst(compile(name, rd, prms, sub,
				Collections.<String>emptyList(), cons));
	}

	/**
	 * 
	 * @param name
	 * @param prms
	 * @param sub
	 * @return
	 * @throws IOException
	 */
	public static NinaPattern patternResource(String name,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons) throws IOException {
		return cst(compileResource(
				name, prms, sub, l, cons).getMachine());
	}

	/**
	 * 
	 * @param s
	 * @param prms
	 * @param sub
	 * @param z
	 * @return
	 */
	public static NinaPattern patternTrace(String name, String s,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons, boolean z) {
		return cst(trace(name, s, prms, sub, l, cons, z));
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @param prms
	 * @param sub
	 * @param z
	 * @return
	 * @throws IOException
	 */
	public static NinaPattern patternTrace(String name, Reader rd,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons,
			boolean z) throws IOException {
		return cst(trace(name, rd, prms, sub, l, cons, z));
	}

	/**
	 * 
	 * @param name
	 * @param prms
	 * @param sub
	 * @param z
	 * @return
	 * @throws IOException
	 */
	public static NinaPattern patternResourceTrace(String name,
			Map<String, String> prms, NinaSubautomata sub,
			List<String> l, NinaConsole cons,
			boolean z) throws IOException {
		return cst(traceResource(name, prms, sub, l, cons, z));
	}

}
