/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 gnosygnu@gmail.com

XOWA is licensed under the terms of the General Public License (GPL) Version 3,
or alternatively under the terms of the Apache License Version 2.0.

You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.

The terms of each license can be found in the source code repository:

GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx;
import gplx.core.strings.*; import gplx.core.envs.*;
public class Time_span_ {
	public static final    Time_span Zero = new Time_span(0);
	public static final    Time_span Null = new Time_span(-1);
	public static Time_span fracs_(long val)	{return new Time_span(val);}
	public static Time_span seconds_(double seconds)	{
		long fracs = (long)(seconds * Divisors[Idx_Sec]);
		return new Time_span(fracs);
	}
	public static Time_span decimal_(Decimal_adp seconds)	{
		return new Time_span(seconds.To_long_mult_1000());
	}
	public static Time_span units_(int frc, int sec, int min, int hour) {
		int[] units = new int[] {frc, sec, min, hour};
		long fracs = Merge_long(units, Time_span_.Divisors);
		return Time_span_.fracs_(fracs);
	}
	public static Time_span from_(long bgn) {return Time_span_.fracs_(System_.Ticks() - bgn);}
	public static final long parse_null = Long_.Min_value;
	public static Time_span parse(String raw) {
		byte[] bry = Bry_.new_u8(raw);
		long fracs = parse_to_fracs(bry, 0, bry.length, false);
		return fracs == parse_null ? null : Time_span_.fracs_(fracs);
	}
	public static long parse_to_fracs(byte[] raw, int bgn, int end, boolean fail_if_ws) {
		int sign = 1, val_f = 0, val_s = 0, val_m = 0, val_h = 0, colon_pos = 0, unit_val = 0, unit_multiple = 1;
		for (int i = end - 1; i >= bgn; i--) {	// start from end; fracs should be lowest unit
			byte b = raw[i];
			switch (b) {
				case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
				case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
					int unit_digit = Byte_ascii.To_a7_int(b);
					unit_val = (unit_multiple == 1) ? unit_digit : unit_val + (unit_digit * unit_multiple);
					switch (colon_pos) {
						case 0:		val_s = unit_val; break;
						case 1:		val_m = unit_val; break;
						case 2:		val_h = unit_val; break;
						default:	return parse_null;	// only hour:minute:second supported for ':' separator; ':' count should be <= 2
					}
					unit_multiple *= 10;
					break;
				case Byte_ascii.Dot:
					double factor = (double)1000 / (double)unit_multiple;	// factor is necessary to handle non-standard decimals; ex: .1 -> 100; .00199 -> .001
					val_f = (int)((double)val_s * factor);	// move val_s unit_val to val_f; logic is indirect, b/c of differing inputs: "123" means 123 seconds; ".123" means 123 fractionals
					val_s = 0;
					unit_multiple = 1;
					break;
				case Byte_ascii.Colon:
					colon_pos++;
					unit_multiple = 1;
					break;
				case Byte_ascii.Dash:
					if	(i == 0 && unit_val > 0)		// only if first char && unit_val > 0 
						sign = -1;
					break;
				case Byte_ascii.Space: case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr:
					if (fail_if_ws) return parse_null;
					break;
				default:
					return parse_null;				// invalid char; return null;
			}
		}
		return sign * (val_f + (val_s * Divisors[1]) + (val_m * Divisors[2]) + (val_h * Divisors[3]));
	}
	public static String To_str(long frc, String fmt) {
		String_bldr sb = String_bldr_.new_();
		int[] units = Split_long(frc, Divisors);

		if (String_.Eq(fmt, Time_span_.Fmt_Short)) {
			for (int i = Idx_Hour; i > -1; i--) {
				int val = units[i];
				if (val == 0 && i == Idx_Hour) continue;	// skip hour if 0; ex: 01:02, instead of 00:01:02
				if (i == Idx_Frac) continue;	// skip frac b/c fmt is short
				if (sb.Count() > 0)	// sb already has unit; add delimiter
					sb.Add(Sprs[i]);
				if (val < 10)	// zeroPad
					sb.Add("0");
				sb.Add(Int_.To_str(val));
			}
			return sb.To_str_and_clear();
		}
		boolean fmt_fracs = !String_.Eq(fmt, Time_span_.Fmt_NoFractionals);
		boolean fmt_padZeros = String_.Eq(fmt, Time_span_.Fmt_PadZeros);
		if (frc == 0) return fmt_padZeros ? "00:00:00.000" : "0";

		int[] padZerosAry = ZeroPadding;
		boolean first = true;
		String dlm = "";
		int zeros = 0;
		if (frc < 0) sb.Add("-");								// negative sign
		for (int i = Idx_Hour; i > -1; i--) {						// NOTE: "> Idx_Frac" b/c frc will be handled below
			int val = units[i];
			if (i == Idx_Frac										// only write fracs...
				&& !(val == 0 && fmt_padZeros)						// ... if val == 0 && fmt is PadZeros
				&& !(val != 0 && fmt_fracs)							// ... or val != 0 && fmt is PadZeros or Default
				) continue;
			if (first && val == 0 && !fmt_padZeros) continue;		// if first and val == 0, don't full pad (avoid "00:")
			zeros = first && !fmt_padZeros ? 1 : padZerosAry[i];	// if first, don't zero pad (avoid "01")
			dlm = first ? "" : Sprs[i];						// if first, don't use dlm (avoid ":01")
			sb.Add(dlm);
			sb.Add(Int_.To_str_pad_bgn_zero(val, zeros));
			first = false;
		}
		return sb.To_str();
	}
	@gplx.Internal protected static int[] Split_long(long fracs, int[] divisors) {
		int divLength = Array_.Len(divisors);
		int[] rv = new int[divLength];
		long cur = Math_.Abs(fracs);
		for (int i = divLength - 1; i > -1; i--) {
			int divisor = divisors[i];
			long factor = cur / divisor;
			rv[i] = (int)factor;
			cur -= (factor * divisor);
		}
		return rv;
	}
	@gplx.Internal protected static long Merge_long(int[] vals, int[] divisors) {
		long rv = 0; int valLength = Array_.Len(vals);
		for (int i = 0; i < valLength; i++) {
			rv += vals[i] * divisors[i];
		}
		return rv;
	}
	public static final    String Fmt_PadZeros = "00:00:00.000";	// u,h00:m00:s00.f000
	public static final    String Fmt_Short = "short";				// u,h##:m#0:s00;
	public static final    String Fmt_Default = "0.000";			// v,#.000
	public static final    String Fmt_NoFractionals = "0";			// v,#
	@gplx.Internal protected static final    int[] Divisors =  {
												   1,			//1 fracs
												   1000,		//1,000 fracs in a second
												   60000,		//60,000 fracs in a minute (60 seconds * 1,000)
												   3600000,	//3,600,000 fracs in an hour (60 minutes * 60,000)
	};
	public static final    String MajorDelimiter = ":";
	public static final    int 
	  Idx_Frac = 0
	, Idx_Sec = 1
	, Idx_Min = 2
	, Idx_Hour = 3;
	static int[] ZeroPadding	= {3, 2, 2, 2,};
	static String[] Sprs	= {".", MajorDelimiter, MajorDelimiter, "",};
	public static Time_span cast(Object arg) {try {return (Time_span)arg;} catch(Exception exc) {throw Err_.new_type_mismatch_w_exc(exc, Time_span.class, arg);}}
	public static final double Ratio_f_to_s = 1000;
}
