package goja

import (
	
)

type date struct {
	year, month, day     int
	hour, min, sec, msec int
	timeZoneOffset       int // time zone offset in minutes
	isLocal              bool
}

func skip( string,  byte) (string, bool) {
	if len() > 0 && [0] ==  {
		return [1:], true
	}
	return , false
}

func skipSpaces( string) string {
	for len() > 0 && [0] == ' ' {
		 = [1:]
	}
	return 
}

func skipUntil( string,  string) string {
	for len() > 0 && !strings.ContainsRune(, rune([0])) {
		 = [1:]
	}
	return 
}

func match( string,  string) (string, bool) {
	if len() < len() {
		return , false
	}
	for  := 0;  < len(); ++ {
		 := []
		 := []
		if  !=  {
			// switch to lower-case; 'a'-'A' is known to be a single bit
			 |= 'a' - 'A'
			if  !=  ||  < 'a' ||  > 'z' {
				return , false
			}
		}
	}
	return [len():], true
}

func getDigits( string, ,  int) (int, string, bool) {
	var ,  int
	for  < len() &&  <  && [] >= '0' && [] <= '9' {
		 = *10 + int([]-'0')
		++
	}
	if  <  {
		return 0, , false
	}
	return , [:], true
}

func getMilliseconds( string) (int, string) {
	,  := 100, 0
	if len() > 0 && ([0] == '.' || [0] == ',') {
		const  = 1
		 := 
		for  < len() && - < 9 && [] >= '0' && [] <= '9' {
			 += int([]-'0') * 
			 /= 10
			++
		}
		if  >  {
			// only consume the separator if digits are present
			return , [:]
		}
	}
	return 0, 
}

// [+-]HH:mm or [+-]HHmm or Z
func getTimeZoneOffset( string,  bool) (int, string, bool) {
	if len() == 0 {
		return 0, , false
	}
	 := [0]
	if  == '+' ||  == '-' {
		var , ,  int
		var  bool
		 := [1:]
		 := len()
		if , ,  = getDigits(, 1, 9); ! {
			return 0, , false
		}
		 -= len()
		if  &&  != 2 &&  != 4 {
			return 0, , false
		}
		for  > 4 {
			 -= 2
			 /= 100
		}
		if  > 2 {
			 =  % 100
			 =  / 100
		} else if ,  = skip(, ':');  {
			if , ,  = getDigits(, 2, 2); ! {
				return 0, , false
			}
		}
		if  > 23 ||  > 59 {
			return 0, , false
		}
		 = *60 + 
		if  == '-' {
			 = -
		}
		return , , true
	} else if  == 'Z' {
		return 0, [1:], true
	}
	return 0, , false
}

var tzAbbrs = []struct {
	nameLower string
	offset    int
}{
	{"gmt", 0},        // Greenwich Mean Time
	{"utc", 0},        // Coordinated Universal Time
	{"ut", 0},         // Universal Time
	{"z", 0},          // Zulu Time
	{"edt", -4 * 60},  // Eastern Daylight Time
	{"est", -5 * 60},  // Eastern Standard Time
	{"cdt", -5 * 60},  // Central Daylight Time
	{"cst", -6 * 60},  // Central Standard Time
	{"mdt", -6 * 60},  // Mountain Daylight Time
	{"mst", -7 * 60},  // Mountain Standard Time
	{"pdt", -7 * 60},  // Pacific Daylight Time
	{"pst", -8 * 60},  // Pacific Standard Time
	{"wet", +0 * 60},  // Western European Time
	{"west", +1 * 60}, // Western European Summer Time
	{"cet", +1 * 60},  // Central European Time
	{"cest", +2 * 60}, // Central European Summer Time
	{"eet", +2 * 60},  // Eastern European Time
	{"eest", +3 * 60}, // Eastern European Summer Time
}

func getTimeZoneAbbr( string) (int, string, bool) {
	for ,  := range tzAbbrs {
		if ,  := match(, .nameLower);  {
			return .offset, , true
		}
	}
	return 0, , false
}

var monthNamesLower = []string{
	"jan",
	"feb",
	"mar",
	"apr",
	"may",
	"jun",
	"jul",
	"aug",
	"sep",
	"oct",
	"nov",
	"dec",
}

func getMonth( string) (int, string, bool) {
	for ,  := range monthNamesLower {
		if ,  := match(, );  {
			return  + 1, , true
		}
	}
	return 0, , false
}

func parseDateISOString( string) (date, bool) {
	if len() == 0 {
		return date{}, false
	}
	var  = date{month: 1, day: 1}
	var  bool

	// year is either yyyy digits or [+-]yyyyyy
	 := [0]
	if  == '-' ||  == '+' {
		 = [1:]
		if .year, ,  = getDigits(, 6, 6); ! {
			return date{}, false
		}
		if  == '-' {
			if .year == 0 {
				// reject -000000
				return date{}, false
			}
			.year = -.year
		}
	} else if .year, ,  = getDigits(, 4, 4); ! {
		return date{}, false
	}
	if ,  = skip(, '-');  {
		if .month, ,  = getDigits(, 2, 2); ! || .month < 1 {
			return date{}, false
		}
		if ,  = skip(, '-');  {
			if .day, ,  = getDigits(, 2, 2); ! || .day < 1 {
				return date{}, false
			}
		}
	}
	if ,  = skip(, 'T');  {
		if .hour, ,  = getDigits(, 2, 2); ! {
			return date{}, false
		}
		if ,  = skip(, ':'); ! {
			return date{}, false
		}
		if .min, ,  = getDigits(, 2, 2); ! {
			return date{}, false
		}
		if ,  = skip(, ':');  {
			if .sec, ,  = getDigits(, 2, 2); ! {
				return date{}, false
			}
			.msec,  = getMilliseconds()
		}
		.isLocal = true
	}
	// parse the time zone offset if present
	if len() > 0 {
		if .timeZoneOffset, ,  = getTimeZoneOffset(, true); ! {
			return date{}, false
		}
		.isLocal = false
	}
	// error if extraneous characters
	return , len() == 0
}

func parseDateOtherString( string) (date, bool) {
	var  = date{
		year:    2001,
		month:   1,
		day:     1,
		isLocal: true,
	}
	var  [3]int
	var  int
	var , , ,  bool
	for {
		 = skipSpaces()
		if len() == 0 {
			break
		}
		 := [0]
		,  := len(), 0
		if  == '+' ||  == '-' {
			if  {
				if , ,  = getTimeZoneOffset(, false);  {
					.timeZoneOffset = 
					.isLocal = false
				}
			}
			if ! || ! {
				 = [1:]
				if , ,  = getDigits(, 1, 9);  {
					.year = 
					if  == '-' {
						if .year == 0 {
							return date{}, false
						}
						.year = -.year
					}
					 = true
				}
			}
		} else if , ,  = getDigits(, 1, 9);  {
			if ,  = skip(, ':');  {
				// time part
				.hour = 
				if .min, ,  = getDigits(, 1, 2); ! {
					return date{}, false
				}
				if ,  = skip(, ':');  {
					if .sec, ,  = getDigits(, 1, 2); ! {
						return date{}, false
					}
					.msec,  = getMilliseconds()
				}
				 = true
				if  := skipSpaces(); len() > 0 {
					if ,  = match(, "pm");  {
						if .hour < 12 {
							.hour += 12
						}
						 = 
						continue
					} else if ,  = match(, "am");  {
						if .hour == 12 {
							.hour = 0
						}
						 = 
						continue
					}
				}
			} else if -len() > 2 {
				.year = 
				 = true
			} else if  < 1 ||  > 31 {
				.year = 
				if  < 100 {
					.year += 1900
				}
				if  < 50 {
					.year += 100
				}
				 = true
			} else {
				if  == 3 {
					return date{}, false
				}
				[] = 
				++
			}
		} else if , ,  = getMonth();  {
			.month = 
			 = true
			 = skipUntil(, "0123456789 -/(")
		} else if , ,  = getTimeZoneAbbr();  {
			.timeZoneOffset = 
			if len() > 0 {
				if  := [0]; ( >= 'a' &&  <= 'z') || ( >= 'A' &&  <= 'Z') {
					return date{}, false
				}
			}
			.isLocal = false
			continue
		} else if  == '(' {
			// skip parenthesized phrase
			 := 1
			 = [1:]
			for len() > 0 &&  != 0 {
				if [0] == '(' {
					++
				} else if [0] == ')' {
					--
				}
				 = [1:]
			}
			if  > 0 {
				return date{}, false
			}
		} else if  == ')' {
			return date{}, false
		} else {
			if  ||  ||  ||  > 0 {
				return date{}, false
			}
			// skip a word
			 = skipUntil(, " -/(")
		}
		for len() > 0 && strings.ContainsRune("-/.,", rune([0])) {
			 = [1:]
		}
	}
	 := 
	if  {
		++
	}
	if  {
		++
	}
	if  > 3 {
		return date{}, false
	}

	switch  {
	case 0:
		if ! {
			return date{}, false
		}
	case 1:
		if  {
			.day = [0]
		} else {
			.month = [0]
		}
	case 2:
		if  {
			.month = [0]
			.day = [1]
		} else if  {
			.year = [1]
			if [1] < 100 {
				.year += 1900
			}
			if [1] < 50 {
				.year += 100
			}
			.day = [0]
		} else {
			.month = [0]
			.day = [1]
		}
	case 3:
		.year = [2]
		if [2] < 100 {
			.year += 1900
		}
		if [2] < 50 {
			.year += 100
		}
		.month = [0]
		.day = [1]
	default:
		return date{}, false
	}
	return , .month > 0 && .day > 0
}