package com.ftinc.si.assist.test.web;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ftinc.si.assist.test.Tool;

public class DayForTest {
	protected static String[] weekday_name = {"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"};
	protected static String[] weekday_jname = {"日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"};

	private static ArrayList<String> s_national_holidays = null;
	private static ArrayList<String> s_holidays = null;

	//holidaysの標準パラメータをセット
	public static void initDayForTest() {
		//書式：["yyyy/mm/dd",...] 但し、月日の文字列の最初は0ではないこと。
		File t_holidayfile = Tool._libfile("$LIBDIR\\holidays_*.txt");
		if (t_holidayfile != null && t_holidayfile.exists()) {
			FileReader t_r;
			try {
				t_r = new FileReader(t_holidayfile);
				String t_json = ""; //$NON-NLS-1$
				BufferedReader t_buf = new BufferedReader(t_r);
				String t_line;
				while ((t_line = t_buf.readLine()) != null) {
					t_json += t_line;
				}
				t_buf.close();
				t_r.close();
				if (t_json != null && t_json.length() > 0) {
					@SuppressWarnings("unchecked")
					HashMap<String, ArrayList<String>> _map = (HashMap<String, ArrayList<String>>)Tool.getObjectfromJSON(HashMap.class, t_json);
					if (_map != null) {
						s_national_holidays = _map.get("nationals");
						s_holidays = _map.get("holidays");
					}
				}
			} catch (IOException e) {
				Tool.logIfDebug(e,  e.getMessage());
			}
		}
	}

	//定休日の配列を返す。サブクラスで上書き可能。
	protected static ArrayList<String> fixedDayOff() {
		if (s_holidays == null) {
			initDayForTest();
		}

		ArrayList<String> t_list = s_holidays;
		if (t_list == null) {
			t_list = new ArrayList<String>();
		}
		return t_list;
	}

	//祝日をyyyy/mm/ddの書式でリストとして返す
	//※未定義なら大域変数で指定されるWebAPIから情報を取得する。
	//  holidays_request ： webapiのリクエストを作成するためのmap。
	//  holidays_pattern : responseから休日を切り出す正規表現。
	protected static ArrayList<String> nationalHolidays() {
		if (s_national_holidays == null) {
			initDayForTest();
		}

		ArrayList<String> t_list = s_national_holidays;
		if (t_list == null) {
			t_list = new ArrayList<String>();
		}
		return t_list;
	}

	//パターンの箇所にvalを文字列化して埋め込む。
	protected static String replace(Pattern pat, String str, int val) {
		Matcher t_m = pat.matcher(str);
		if (t_m.find()) {
			return t_m.replaceFirst(Integer.toString(val));
		}
		return str;
	}

	//fmtの例: 西暦yyyy年mm月dd日
	protected static ArrayList<Integer> lengthList(String fmt) {
		ArrayList<Integer> t_list = new ArrayList<Integer>();
		Pattern t_pat = Pattern.compile("^.*?[^ymd]*?(y+)[^ymd]*?.*?$");
		Matcher t_m =t_pat.matcher(fmt);
		if (t_m.find()) {
			t_list.add(t_m.group(1).length());
		} else {
			t_list.add(0);
		}
		t_pat = Pattern.compile("^.*?[^ymd]*?(m+)[^ymd]*?.*?$");
		t_m =t_pat.matcher(fmt);
		if (t_m.find()) {
			t_list.add(t_m.group(1).length());
		} else {
			t_list.add(0);
		}
		t_pat = Pattern.compile("^.*?[^ymd]*?(d+)[^ymd]*?.*?$");
		t_m =t_pat.matcher(fmt);
		if (t_m.find()) {
			t_list.add(t_m.group(1).length());
		} else {
			t_list.add(0);
		}
		return t_list;
	}

	protected static String toDateString(String template, ArrayList<Integer> lenlist, int year, int month, int day) {
		String str_y = String.format("%04d", year);
		String str_m = String.format("%02d", month);
		String str_d = String.format("%02d", day);

		if (lenlist.get(0) > 0) {
			template = template.replaceFirst("y+", str_y.substring(str_y.length() - lenlist.get(0), str_y.length()));
		}
		if (lenlist.get(1) > 0) {
			template = template.replaceFirst("m+", str_m.substring(str_m.length() - lenlist.get(1), str_m.length()));
		}
		if (lenlist.get(2) > 0) {
			template = template.replaceFirst("d+", str_d.substring(str_d.length() - lenlist.get(2), str_d.length()));
		}
		return template;
	}

	//★★以下、strs[0]は書式。y,m,dを置き換える。
	public static String today(String[] strs) {
		String day_template = strs[0]; //例：　yyyy年mm月dd日
		ArrayList<Integer> f_list = lengthList(day_template);

		Calendar t_clndr = Calendar.getInstance();

		int t_year = t_clndr.get(Calendar.YEAR);
		int t_month = t_clndr.get(Calendar.MONTH) + 1;
		int t_day = t_clndr.get(Calendar.DATE);

		return toDateString(day_template, f_list, t_year, t_month, t_day);
	}


	//本日以降の最初の営業日を返す。書式strs[0]は年情報がない場合も考慮している。
	public static String nextBusinessDay(String[] strs) {
		String day_template = strs[0]; //例：　yyyy年mm月dd日
		ArrayList<Integer> f_list = lengthList(day_template);

		Calendar t_clndr = Calendar.getInstance();

		for (int i = 0; i < 31; i++) {
			String x_year = Integer.toString(t_clndr.get(Calendar.YEAR));
			String x_month = Integer.toString(t_clndr.get(Calendar.MONTH) + 1);
			String x_day = Integer.toString(t_clndr.get(Calendar.DATE));
			int w_day = t_clndr.get(Calendar.DAY_OF_WEEK) - 1;

			if (!fixedDayOff().contains(weekday_name[w_day])) {
				//この日が定休でないこと
				if (!nationalHolidays().contains(x_year + "/" + x_month + "/" + x_day)) {
					//この日が祝日（企業によっては営業することあり、カスタマイズ前提である）でないこと
					return toDateString(day_template, f_list, t_clndr.get(Calendar.YEAR), t_clndr.get(Calendar.MONTH) + 1, t_clndr.get(Calendar.DATE));
				}
			}

			t_clndr.add(Calendar.DATE, 1);
		}
		return null;
	}

	//本日以降の最初の指定曜日（str[1]）を返す。書式strs[0]は年情報がない場合も考慮している。
	public static String nextWeekday(String[] strs) {
		if (strs.length < 1) {
			Tool.logIfDebug(null, "ERROR: unexpected # of arguments @nextWeekday");
			return "ERROR: unexpected # of arguments @nextWeekday";
		}
		String day_template = strs[0]; //例：　yyyy年mm月dd日
		ArrayList<Integer> f_list = lengthList(day_template);

		Calendar t_clndr = Calendar.getInstance();
		for (int i = 0; i < 31; i++) {
			int w_day = t_clndr.get(Calendar.DAY_OF_WEEK) - 1;

			if (strs[1].toLowerCase().equals(weekday_name[w_day]) || strs[1].equals(weekday_jname[w_day])) {
				return toDateString(day_template, f_list, t_clndr.get(Calendar.YEAR), t_clndr.get(Calendar.MONTH) + 1, t_clndr.get(Calendar.DATE));
			}

			t_clndr.add(Calendar.DATE, 1);
		}
		return null;
	}

	//本日以降の最初の休日（定休ではない）を返す。書式strs[0]は年情報がない場合も考慮している。
	//※　定休は「次回の曜日」で取得可能。
	public static String nextHoliday(String[] strs) {
		String day_template = strs[0]; //例：　yyyy年mm月dd日
		ArrayList<Integer> f_list = lengthList(day_template);

		Calendar t_clndr = Calendar.getInstance();

		for (int i = 0; i < 31; i++) {
			String x_year = Integer.toString(t_clndr.get(Calendar.YEAR));
			String x_month = Integer.toString(t_clndr.get(Calendar.MONTH) + 1);
			String x_day = Integer.toString(t_clndr.get(Calendar.DATE));

			if (nationalHolidays().contains(x_year + "/" + x_month + "/" + x_day)) {
				//この日が祝日（企業によっては営業することあり、カスタマイズ前提である）でないこと
				return toDateString(day_template, f_list, t_clndr.get(Calendar.YEAR), t_clndr.get(Calendar.MONTH) + 1, t_clndr.get(Calendar.DATE));
			}
			t_clndr.add(Calendar.DATE, 1);
		}
		return null;
	}

	//strs[1]以降で指定された日以降の最初の営業日を返す。書式strs[0]は年情報がない場合も考慮している。
	public static String nextBusinessDayAfter(String[] strs) {
		//strs[0]:月日書式
		//strs[1]:書式に従った指定日
		return lookupBusinessDayAround(strs, 1);
	}

	//strs[1]以降で指定された日以前の最初の営業日を返す。書式strs[0]は年情報がない場合も考慮している。
	public static String nextBusinessDayBefore(String[] strs) {
		return lookupBusinessDayAround(strs, -1);
	}

	private static Integer[] getDateFromString(String template, String str_date) {
		String reg_y = "";
		Pattern pat_y = Pattern.compile("([^ymd]*)y+([^ymd]*)");
		Matcher t_m = pat_y.matcher(template);
		if (t_m.find()) {
			if (t_m.group(1).length() > 0 || t_m.group(2).length() > 0) {
				if (t_m.group(1).length() == 0) {
					reg_y = "^";
				} else {
					reg_y = Pattern.quote(t_m.group(1));
				}
				reg_y += "(\\d+)";

				if (t_m.group(2).length() == 0) {
					reg_y += "$";
				} else {
					reg_y += Pattern.quote(t_m.group(2));
				}
			}
		}
		String reg_m = "";
		Pattern pat_m = Pattern.compile("([^ymd]*)m+([^ymd]*)");
		t_m = pat_m.matcher(template);
		if (t_m.find()) {
			if (t_m.group(1).length() > 0 || t_m.group(2).length() > 0) {
				if (t_m.group(1).length() == 0) {
					reg_m = "^";
				} else {
					reg_m = Pattern.quote(t_m.group(1));
				}
				reg_m += "(\\d+)";

				if (t_m.group(2).length() == 0) {
					reg_m += "$";
				} else {
					reg_m += Pattern.quote(t_m.group(2));
				}
			}
		}
		String reg_d = "";
		Pattern pat_d = Pattern.compile("([^dym]*)d+([^ymd]*)");
		t_m = pat_d.matcher(template);
		if (t_m.find()) {
			if (t_m.group(1).length() > 0 || t_m.group(2).length() > 0) {
				if (t_m.group(1).length() == 0) {
					reg_d = "^";
				} else {
					reg_d = Pattern.quote(t_m.group(1));
				}
				reg_d += "(\\d+)";

				if (t_m.group(2).length() == 0) {
					reg_d += "$";
				} else {
					reg_d += Pattern.quote(t_m.group(2));
				}
			}
		}

		Calendar t_clndr = Calendar.getInstance();
		Integer x_year = t_clndr.get(Calendar.YEAR);
		Integer x_month = t_clndr.get(Calendar.MONTH) + 1;
		Integer x_day = t_clndr.get(Calendar.DATE);

		String str_year = x_year.toString();
		String str_month = x_month.toString();
		String str_day = x_day.toString();

		t_clndr.set(x_year, x_month - 1, x_day);

		int status = 2;
		Pattern t_pat = Pattern.compile(reg_y);
		t_m = t_pat.matcher(str_date);
		if (t_m.find()) {
			str_year = t_m.group(1);
		} else {
			status = 1;
		}
		t_pat = Pattern.compile(reg_m);
		t_m = t_pat.matcher(str_date);
		if (t_m.find()) {
			str_month = t_m.group(1);
		} else {
			status = 0;
		}
		t_pat = Pattern.compile(reg_d);
		t_m = t_pat.matcher(str_date);
		if (t_m.find()) {
			str_day = t_m.group(1);
		}

		x_day = Integer.valueOf(str_day);
		x_month = Integer.valueOf(str_month);
		x_year = Integer.valueOf(str_year);

		Calendar t_clndr2 = Calendar.getInstance();
		t_clndr2.set(x_year, x_month - 1, x_day);
		if (t_clndr2.before(t_clndr)) {
			if (status == 1) {
				//年指定なし
				x_year++;
			} else if (status == 0) {
				//年月指定なし
				t_clndr2.add(Calendar.MONTH, 1);;

				x_year = t_clndr.get(Calendar.YEAR);
				x_month = t_clndr.get(Calendar.MONTH) + 1;
				x_day = t_clndr.get(Calendar.DATE);
			}
		}

		return new Integer[] {x_year, x_month, x_day};
	}

	public static String lookupBusinessDayAround(String[] strs, int n) {
		//strs[0]:月日書式
		//strs[1]:書式に従った指定日
		String day_template = strs[0]; //例：　yyyy年mm月dd日
		ArrayList<Integer> f_list = lengthList(day_template);

		Integer[] t_date = getDateFromString(day_template, strs[1]);
		if (t_date == null) {
			return null;
		}

		Integer x_year = t_date[0];
		Integer x_month = t_date[1];
		Integer x_day = t_date[2];

		Calendar t_clndr = Calendar.getInstance();
		t_clndr.set(x_year, x_month - 1, x_day);
		for (int i = 0; i < 31; i++) {
			String t_year = Integer.toString(t_clndr.get(Calendar.YEAR));
			String t_month = Integer.toString(t_clndr.get(Calendar.MONTH) + 1);
			String t_day = Integer.toString(t_clndr.get(Calendar.DATE));
			int w_day = t_clndr.get(Calendar.DAY_OF_WEEK) - 1;

			if (!fixedDayOff().contains(weekday_name[w_day])) {
				//この日が定休でないこと
				if (!nationalHolidays().contains(t_year + "/" + t_month + "/" + t_day)) {
					//この日が祝日（企業によっては営業することあり、カスタマイズ前提である）でないこと
					return toDateString(day_template, f_list, t_clndr.get(Calendar.YEAR), t_clndr.get(Calendar.MONTH) + 1, t_clndr.get(Calendar.DATE));
				}
			}

			t_clndr.add(Calendar.DATE, n);
		}
		return null;
	}

}
