/*
 * 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.unix.misc;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class ParseDate {

	private static final Pattern TIM_01 =
		Pattern.compile("([0-9]+):([0-9]+)(:([0-9]+))?");

	private static final Pattern PTN_01 =
		Pattern.compile(
				"([0-9][0-9][0-9][0-9]+)[\\-/.年년]" +
				"([0-9]+)[\\-/.月월]([0-9]+)[日일]?" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTN_02 =
		Pattern.compile(
				"([0-9][0-9]?)/([0-9][0-9]?)/([0-9]+)" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTN_03 =
		Pattern.compile(
				"([0-9][0-9]?)\\.([0-9][0-9]?)\\.([0-9]+)" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTN_04 =
		Pattern.compile(
				"([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");

	private static final Pattern PTNJA01 =
		Pattern.compile(
				"(明治?|大正?|昭和?|平成?|[MTSH])([0-9]+)[\\-年]" +
				"([0-9]+)[\\-月]([0-9]+)日?" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNJA02 =
		Pattern.compile(
				"(明治?|大正?|昭和?|平成?|[MTSH])" +
				"([0-9][0-9])([0-9][0-9])([0-9][0-9])" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");

	private static final Pattern PTNCJK2 =
		Pattern.compile("([0-9]+)[月월]([0-9]+)[日일]");

	private static final Pattern PTNEN01 =
		Pattern.compile(
				"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)" +
				" +([0-9]+)( +|, *)([0-9]+)" +
				"( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNEN02 =
		Pattern.compile(
				"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)" +
				"[a-z]* +([0-9]+)");
	private static final Pattern PTNEN03 =
		Pattern.compile(
				"(([0-9]+) +years?)? *(([0-9]+) +months?)? *" +
				"(([0-9]+) +days?)? *( +ago)?");

	private static final Pattern PTNFR01 =
		Pattern.compile(
				"([0-9]+) +" +
				"(janv\\.|f[ée]vr\\.|mars|avr\\.|mai|juin|" +
				"juil\\.|ao[ûu]t|sept\\.|oct\\.|nov\\.|d[ée]c\\.)" +
				" +([0-9]+)( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNFR02 =
		Pattern.compile(
				"([0-9]+) +" +
				"(janv\\.|janvier|" +
				"f[ée]vr\\.|f[ée]vrier|" +
				"mars|" +
				"avr\\.|avril|" +
				"mai|" +
				"juin|" +
				"juil\\.|juilet|" +
				"ao[ûu]t|" +
				"sept\\.|septembre|" +
				"oct\\.|octobre|" +
				"nov\\.|novembre|" +
				"d[ée]c\\.|d[ée]cembre)");

	private static final Pattern PTNIT01 =
		Pattern.compile(
				"([0-9]+)-" +
				"(gen|feb|mar|apr|mag|giu|lug|ago|set|ott|nov|dic)" +
				"-([0-9]+)( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNIT02 =
		Pattern.compile(
				"([0-9]+) +" +
				"(gen|feb|mar|apr|mag|giu|lug|ago|set|ott|nov|dic)");

	private static final Pattern PTNES01 =
		Pattern.compile(
				"([0-9]+)-" +
				"(ene|feb|mar|abr|may|jun|jul|ago|sep|oct|nov|dic)" +
				"-([0-9]+)( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNES02 =
		Pattern.compile(
				"([0-9]+) +de +" +
				"(ene|feb|mar|abr|may|jun|jul|ago|sep|oct|nov|dic)");

	private static final Pattern PTNPT01 =
		Pattern.compile(
				"([0-9]+)/" +
				"(Jan|Fev|Mar|Abr|Mai|Jun|Jul|Ago|Set|Out|Nov|Dez)" +
				"/([0-9]+)( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNPT02 =
		Pattern.compile(
				"([0-9]+) +de +" +
				"(jan|fev|mar|abr|mai|jun|jul|ago|set|out|nov|dez)");

	private static final Pattern PTNNL01 =
		Pattern.compile(
				"([0-9]+)-" +
				"(jan|feb|mrt|apr|mei|jun|jul|aug|sep|okt|nov|dec)" +
				"-([0-9]+)( +([0-9]+:[0-9]+(:[0-9]+)?))?");
	private static final Pattern PTNNL02 =
		Pattern.compile(
				"([0-9]+) +" +
				"(jan|feb|mrt|apr|mei|jun|jul|aug|sep|okt|nov|dec)");

	private static final Pattern PTNDE02 =
		Pattern.compile(
				"([0-9]+) +" +
				"(Jan|Feb|M[aä]r|Apr|Mai|Jun|" +
				"Jul|Aug|Sep|Okt|Nov|Dez)");

	private static final Pattern PTNRU02 =
		Pattern.compile(
				"([0-9]+) +" +
				"(Янв|Фев|Мар|Апр|Май|Июн|" +
				"Июн|Авг|Сен|Окт|Ноя|Дек)");

	private static final Pattern PTN1 =
		Pattern.compile(
				"([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])" +
				"((([0-9][0-9])?[0-9][0-9])?)(\\.([0-9][0-9])?)?");

	private static final String[][] MONTHS = new String[][] {
		{
			"Jan",                                   // en
			"janv.", "janv.", "janvier", "janvier",  // fr
			"gen",                                   // it
			"ene", "Ene",                            // es
			"Jan", "jan",                            // pt
			"jan", "Januari",                        // nl
			"Jan", "Jan",                            // de
			"Янв",                                // ru
		},
		{
			"Feb",
			"fevr.", "févr.", "fevrier", "février",
			"feb",
			"feb", "Feb",
			"Fev", "fev",
			"feb", "Februari",
			"Feb", "Feb",
			"Фев",
		},
		{
			"Mar",
			"mars",  "mars", "mars",  "mars",
			"mar",
			"mar", "Mar",
			"Mar", "mar",
			"mrt", "Maart",
			"Mar", "Mär",
			"Мар",
		},
		{
			"Apr",
			"avr.",  "avr.", "avril", "avril",
			"apr",
			"abr", "Abr",
			"Abr", "abr",
			"apr", "April",
			"Apr", "Apr",
			"Апр",
		},
		{
			"May",
			"mai",   "mai", "mai",   "mai",
			"mag",
			"may", "May",
			"Mai", "mai",
			"mei", "Mei",
			"Mai", "Mai",
			"Май",
		},
		{
			"Jun",
			"juin",  "juin", "juin",  "juin",
			"giu",
			"jun", "Jun",
			"Jun", "jun",
			"jun", "Juni",
			"Jun", "Jun",
			"Июн",
		},
		{
			"Jul",
			"juil.", "juil.", "juilet", "juilet",
			"lug",
			"jul", "Jul",
			"Jul", "jul",
			"jul", "Juli",
			"Jul", "Jul",
			"Июл",
		},
		{
			"Aug",
			"aout",  "août", "aout",  "août",
			"ago",
			"ago", "Ago",
			"Ago", "ago",
			"aug", "Augustus",
			"Aug", "Aug",
			"Авг",
		},
		{
			"Sep",
			"sept.", "sept.", "septembre", "septembre",
			"set",
			"sep", "Sep",
			"Set", "set",
			"sep", "September",
			"Sep", "Sep",
			"Сен",
		},
		{
			"Oct",
			"oct.",  "oct.", "octobre",  "octobre",
			"ott",
			"oct", "Oct",
			"Out", "out",
			"okt", "Oktober",
			"Okt", "Okt",
			"Окт",
		},
		{
			"Nov",
			"nov.",  "nov.", "novembre", "novembre",
			"nov",
			"nov", "Nov",
			"Nov", "nov",
			"nov", "November",
			"Nov", "Nov",
			"Ноя",
		},
		{
			"Dec",
			"dec.",  "déc.", "decembre", "décembre",
			"dic",
			"dic", "Dic",
			"Dez", "dez",
			"dec", "December",
			"Dez", "Dez",
			"Дек",
		},
	};

	private static int getMonth(String s) {
		if(s == null)  throw new NullPointerException();
		for(int i = 0; i < MONTHS.length; i++) {
			for(int j = 0; j < MONTHS[i].length; j++) {
				if(s.startsWith(MONTHS[i][j]))  return i + 1;
			}
		}
		throw new IllegalArgumentException();
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long parseTime(String str) {
		Matcher m;
		long ho, mi, se;

		if(!(m = TIM_01.matcher(str)).matches())  return null;
		ho = Long.parseLong(m.group(1));
		mi = Long.parseLong(m.group(2));
		se = m.group(3) != null ? Long.parseLong(m.group(4)) : 0;
		if(ho > 23)  throw new IllegalArgumentException();
		if(mi > 59)  throw new IllegalArgumentException();
		if(se > 60)  throw new IllegalArgumentException();
		return (((ho * 60l) + mi) * 60l + se) * 1000l;
	}

	//
	private static Long fromMD(Pattern p, String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if(!(m = p.matcher(str)).lookingAt())  return null;
		cl = new GregorianCalendar();
		ye = cl.get(Calendar.YEAR);
		mo = getMonth(m.group(1));
		da = Integer.parseInt(m.group(2));
		ti = 0;
		cl = new GregorianCalendar(ye, mo - 1, da);
		return cl.getTimeInMillis() + ti;
	}

	//
	private static Long fromDM(Pattern p, String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if(!(m = p.matcher(str)).lookingAt())  return null;
		cl = new GregorianCalendar();
		ye = cl.get(Calendar.YEAR);
		mo = getMonth(m.group(2));
		da = Integer.parseInt(m.group(1));
		ti = 0;
		cl = new GregorianCalendar(ye, mo - 1, da);
		return cl.getTimeInMillis() + ti;
	}

	//
	private static Long fromMDCJK(String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if(!(m = PTNCJK2.matcher(str)).lookingAt())  return null;
		cl = new GregorianCalendar();
		ye = cl.get(Calendar.YEAR);
		mo = Integer.parseInt(m.group(1));
		da = Integer.parseInt(m.group(2));
		ti = 0;
		cl = new GregorianCalendar(ye, mo - 1, da);
		return cl.getTimeInMillis() + ti;
	}

	//
	private static Long fromDf(java.util.Date t, Pattern p,
			String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;

		if(!(m = p.matcher(str)).matches())  return null;
		if(m.group(2) == null &&
				m.group(4) == null &&
				m.group(6) == null) {
			return null;
		}

		cl = new GregorianCalendar();
		cl.setTime(t);
		ye = m.group(2) != null ? Integer.parseInt(m.group(2)) : 0;
		mo = m.group(4) != null ? Integer.parseInt(m.group(4)) : 0;
		da = m.group(6) != null ? Integer.parseInt(m.group(6)) : 0;
		if(m.group(7) == null || m.group(7).equals("")) {
			cl.add(Calendar.YEAR, ye);
			cl.add(Calendar.MONTH, mo);
			cl.add(Calendar.DAY_OF_YEAR, da);
		} else {
			cl.add(Calendar.YEAR, -ye);
			cl.add(Calendar.MONTH, -mo);
			cl.add(Calendar.DAY_OF_YEAR, -da);
		}
		return cl.getTimeInMillis();
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static long from(java.util.Date time, String str) {
		Long r;

		if((r = fromStandard(str))               != null ||
				(r = fromJapanese(str))          != null ||
				(r = fromEnglish(str))           != null ||
				(r = fromFrench(str))            != null ||
				(r = fromItaly(str))             != null ||
				(r = fromSpanish(str))           != null ||
				(r = fromPortuguese(str))        != null ||
				(r = fromDutch(str))             != null ||
				(r = fromMDCJK(str))             != null ||
				(r = fromMD(PTNEN02, str))       != null ||
				(r = fromDM(PTNFR02, str))       != null ||
				(r = fromDM(PTNIT02, str))       != null ||
				(r = fromDM(PTNES02, str))       != null ||
				(r = fromDM(PTNPT02, str))       != null ||
				(r = fromDM(PTNNL02, str))       != null ||
				(r = fromDM(PTNDE02, str))       != null ||
				(r = fromDM(PTNRU02, str))       != null ||
				(r = fromDf(time, PTNEN03, str)) != null) {
			return r.longValue();
		} else {
			throw new IllegalArgumentException();
		}
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromStandard(String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if((m = PTN_01.matcher(str)).matches()) {
			ye = Integer.parseInt(m.group(1));
			mo = Integer.parseInt(m.group(2));
			da = Integer.parseInt(m.group(3));
			ti = m.group(4) != null ? parseTime(m.group(5)) : 0;
		} else if((m = PTN_02.matcher(str)).matches()) {
			ye = Integer.parseInt(m.group(3));
			mo = Integer.parseInt(m.group(1));
			da = Integer.parseInt(m.group(2));
			ti = m.group(4) != null ? parseTime(m.group(5)) : 0;
		} else if((m = PTN_03.matcher(str)).matches()) {
			ye = Integer.parseInt(m.group(3));
			mo = Integer.parseInt(m.group(2));
			da = Integer.parseInt(m.group(1));
			ti = m.group(4) != null ? parseTime(m.group(5)) : 0;
		} else if((m = PTN_04.matcher(str)).matches()) {
			ye = Integer.parseInt(m.group(1));
			mo = Integer.parseInt(m.group(2));
			da = Integer.parseInt(m.group(3));
			ti = m.group(4) != null ? parseTime(m.group(5)) : 0;
		} else {
			return null;
		}
		cl = new GregorianCalendar(ye, mo - 1, da);
		return cl.getTimeInMillis() + ti;
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromEnglish(String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if(!(m = PTNEN01.matcher(str)).matches())  return null;
		ye = Integer.parseInt(m.group(4));
		mo = getMonth(m.group(1));
		da = Integer.parseInt(m.group(2));
		ti = m.group(5) != null ? parseTime(m.group(6)) : 0;
		cl = new GregorianCalendar(ye, mo - 1, da);
		return cl.getTimeInMillis() + ti;
	}

	//
	private static int getGengoEpoch(String str) {
		if(str.equals("M") || str.startsWith("明")) {
			return 1868;
		} else if(str.equals("T") || str.startsWith("大")) {
			return 1912;
		} else if(str.equals("S") || str.startsWith("昭")) {
			return 1926;
		} else if(str.equals("H") || str.startsWith("平")) {
			return 1989;
		} else {
			throw new IllegalArgumentException();
		}
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromJapanese(String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if((m = PTNJA01.matcher(str)).matches() ||
				(m = PTNJA02.matcher(str)).matches()) {
			ye = getGengoEpoch(m.group(1)) - 1;
			ye = ye + Integer.parseInt(m.group(2));
			mo = Integer.parseInt(m.group(3));
			da = Integer.parseInt(m.group(4));
			ti = m.group(5) != null ? parseTime(m.group(6)) : 0;
			cl = new GregorianCalendar(ye, mo - 1, da);
			return cl.getTimeInMillis() + ti;
		} else {
			return null;
		}
	}

	//
	private static Long fromDMY(Pattern p, String str) {
		GregorianCalendar cl;
		int ye, mo, da;
		Matcher m;
		long ti;

		if(!(m = p.matcher(str)).matches())  return null;
		ye = Integer.parseInt(m.group(3));
		mo = getMonth(m.group(2));
		da = Integer.parseInt(m.group(1));
		ti = m.group(4) != null ? parseTime(m.group(5)) : 0;
		cl = new GregorianCalendar(ye, mo - 1, da);
		return cl.getTimeInMillis() + ti;
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromFrench(String str) {
		return fromDMY(PTNFR01, str);
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromItaly(String str) {
		return fromDMY(PTNIT01, str);
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromSpanish(String str) {
		return fromDMY(PTNES01, str);
	}

	/**
	 *  
	 * @param str
	 * @return
	 */
	public static Long fromPortuguese(String str) {
		return fromDMY(PTNPT01, str);
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static Long fromDutch(String str) {
		return fromDMY(PTNNL01, str);
	}

	/**
	 * 
	 * @param str
	 * @return
	 */
	public static java.util.Date parseMMDD(String str) {
		int mo, da, ho, mi, se, ye;
		GregorianCalendar cl;
		Matcher m;

		if((m = PTN1.matcher(str)).matches()) {
			mo = Integer.parseInt(m.group(1)) - 1;
			da = Integer.parseInt(m.group(2));
			ho = Integer.parseInt(m.group(3));
			mi = Integer.parseInt(m.group(4));
			if(m.group(5) == null || m.group(5).equals("")) {
				cl = new GregorianCalendar();
				ye = cl.get(Calendar.YEAR);
			} else if(m.group(5).length() == 2) {
				cl = new GregorianCalendar();
				ye = cl.get(Calendar.YEAR) / 100;
				ye = ye * 100 + Integer.parseInt(m.group(5));
			} else {
				ye = Integer.parseInt(m.group(5));
			}
			se = m.group(9) != null ?
					Integer.parseInt(m.group(9)) : 0;
			cl = new GregorianCalendar(ye, mo, da, ho, mi, se);
			return cl.getTime();
		} else {
			return null;
		}
	}

}
