package xsd

import (
	"encoding/base64"
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"net/url"
	"regexp"
	"strings"
	"time"

	"git.pyer.club/kingecg/goonvif/onvif/xsd/iso8601"
)

/*
	TODO: XML SOURCE: https://www.w3.org/2001/05/datatypes.xsd
*/

// AnyType alias for string
type AnyType string

// AnySimpleType alias for string
type AnySimpleType string

/***********************************************************
  					Non-derived types
***********************************************************/

/*
The string datatype represents character strings in XML.
The ·value space· of string is the set of finite-length sequences of characters.
String has the following constraining facets:
• length
• minLength
• maxLength
• pattern
• enumeration
• whiteSpace

More info: https://www.w3.org/TR/xmlschema-2/#string

//TODO: valid/invalid character declaration and process restrictions
*/
type String string

/*
Construct an instance of xsd String type
*/
func (tp String) NewString(data string) String {
	return String(data)
}

/*
Boolean has the ·value space· required to support the mathematical concept of binary-valued logic: {true, false}.
Boolean has the following ·constraining facets·:
• pattern
• whiteSpace

More info: https://www.w3.org/TR/xmlschema-2/#boolean

//TODO: process restrictions
*/
type Boolean bool

/*
Construct an instance of xsd Boolean type
*/
func (tp Boolean) NewBool(data bool) Boolean {
	return Boolean(data)
}

/*
Float is patterned after the IEEE single-precision 32-bit floating point type
Float has the following ·constraining facets·:
• pattern
• enumeration
• whiteSpace
• maxInclusive
• maxExclusive
• minInclusive
• minExclusive

More info: https://www.w3.org/TR/xmlschema-2/#float

//TODO: process restrictions
*/
type Float float32

/*
Construct an instance of xsd Float type
*/
func (tp Float) NewFloat(data float32) Float {
	return Float(data)
}

/*
The double datatype is patterned after the IEEE double-precision 64-bit floating point type
Double has the following ·constraining facets·:
• pattern
• enumeration
• whiteSpace
• maxInclusive
• maxExclusive
• minInclusive
• minExclusive

More info: https://www.w3.org/TR/xmlschema-2/#double

//TODO: process restrictions
*/
type Double float64

/*
Construct an instance of xsd Double type
*/
func (tp Double) NewDouble(data float64) Double {
	return Double(data)
}

/*
The type decimal represents a decimal number of arbitrary precision.
Schema processors vary in the number of significant digits they support,
but a conforming processor must support a minimum of 18 significant digits.
The format of xsd:decimal is a sequence of digits optionally preceded by a sign ("+" or "-")
and optionally containing a period. The value may start or end with a period.
If the fractional part is 0 then the period and trailing zeros may be omitted.
Leading and trailing zeros are permitted, but they are not considered significant.
That is, the decimal values 3.0 and 3.0000 are considered equal.

Source: http://www.datypic.com/sc/xsd/t-xsd_decimal.html

Decimal has the following ·constraining facets·:
• totalDigits
• fractionDigits
• pattern
• whiteSpace
• enumeration
• maxInclusive
• maxExclusive
• minInclusive
• minExclusive

More info: https://www.w3.org/TR/xmlschema-2/#decimal

//TODO: process restrictions, valid/invalid characters(commas are not permitted; the decimal separator must be a period)
*/
type Decimal string

/*
Construct an instance of xsd Decimal type
*/
func (tp Decimal) NewDecimal(data string) Decimal {
	return Decimal(data)
}

/*
	Duration is the [ISO 8601] extended format PnYn MnDTnH nMnS,
	where nY represents the number of years, nM the number of months,
	nD the number of days, 'T' is the date/time separator,
	nH the number of hours, nM the number of minutes and nS the number of seconds.
	The number of seconds can include decimal digits to arbitrary precision.
	PnYnMnDTnHnMnS

	Duration has the following ·constraining facets·:

	• pattern
	• enumeration
	• whiteSpace
	• maxInclusive
	• maxExclusive
	• minInclusive
	• minExclusive

	More info: https://www.w3.org/TR/xmlschema-2/#duration

	TODO: process restrictions
	TODO: Look at time.Duration go type
*/

// Duration alias for AnySimpleType
type Duration AnySimpleType

/*
Construct an instance of xsd duration type
*/
func (tp Duration) NewDateTime(years, months, days, hours, minutes, seconds string) Duration {
	i, err := iso8601.NewDuration(
		years,
		months,
		days,
		hours,
		minutes,
		seconds,
	)

	if err != nil {
		log.Fatalln(err)
	}

	//fmt.Println(i.ISO8601Duration())

	return Duration(i.ISO8601Duration())
}

/*
DateTime values may be viewed as objects with integer-valued year, month, day, hour
and minute properties, a decimal-valued second property, and a boolean timezoned property.

The ·lexical space· of dateTime consists of finite-length sequences of characters of the form:

	'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?,

DateTime has the following ·constraining facets·:

• pattern
• enumeration
• whiteSpace
• maxInclusive
• maxExclusive
• minInclusive
• minExclusive

More info: https://www.w3.org/TR/xmlschema-2/#dateTime

TODO: decide good type for time with proper format
TODO: process restrictions
*/
type DateTime AnySimpleType

/*
Construct an instance of xsd dateTime type
*/
func (tp DateTime) NewDateTime(time time.Time) DateTime {
	return DateTime(time.Format("2002-10-10T12:00:00-05:00"))
}

/*
Time represents an instant of time that recurs every day.
The ·value space· of time is the space of time of day values
as defined in § 5.3 of [ISO 8601]. Specifically, it is a set
of zero-duration daily time instances.

Time has the following ·constraining facets·:

• pattern
• enumeration
• whiteSpace
• maxInclusive
• maxExclusive
• minInclusive
• minExclusive

More info: https://www.w3.org/TR/xmlschema-2/#time

TODO: process restrictions
*/
type Time AnySimpleType

/*
Construct an instance of xsd time type
*/
func (tp DateTime) NewTime(time time.Time) DateTime {
	return DateTime(time.Format("15:04:05"))
}

/*
The ·value space· of date consists of top-open intervals of
exactly one day in length on the timelines of dateTime, beginning
on the beginning moment of each day (in each timezone),
i.e. '00:00:00', up to but not including '24:00:00'
(which is identical with '00:00:00' of the next day).
For nontimezoned values, the top-open intervals disjointly
cover the nontimezoned timeline, one per day. For timezoned
values, the intervals begin at every minute and therefore overlap.
*/
type Date AnySimpleType

/*
Construct an instance of xsd date type
*/
func (tp Date) NewDate(time time.Time) Date {
	return Date(time.Format("2004-04-12-05:00"))
}

/*
The type xsd:gYearMonth represents a specific month of a specific
year. The letter g signifies "Gregorian." The format of
xsd:gYearMonth is CCYY-MM. No left truncation is allowed on
either part. To represents years later than 9999, additional
digits can be added to the left of the year value.
To represent years before 0001, a preceding minus sign ("-")
is permitted.

Source: http://www.datypic.com/sc/xsd/t-xsd_gYearMonth.html

More info: https://www.w3.org/TR/xmlschema-2/#gYearMonth
*/
type GYearMonth AnySimpleType

/*
Construct an instance of xsd GYearMonth type
*/
func (tp GYearMonth) NewGYearMonth(time time.Time) GYearMonth {
	return GYearMonth(fmt.Sprintf("", time.Year(), "-", time.Month()))
	//return GYearMonth(time.Format("2004-04-05:00"))
}

/*
The type xsd:gYear represents a specific calendar year.
The letter g signifies "Gregorian." The format of xsd:gYear
is CCYY. No left truncation is allowed. To represent years
later than 9999, additional digits can be added to the left
of the year value. To represent years before 0001, a preceding
minus sign ("-") is allowed.

Source: http://www.datypic.com/sc/xsd/t-xsd_gYear.html

More info: https://www.w3.org/TR/xmlschema-2/#gYear
*/
type GYear AnySimpleType

/*
Construct an instance of xsd GYear type
*/
func (tp GYear) NewGYear(time time.Time) GYear {
	return GYear(fmt.Sprintf("", time.Year()))
	//return GYearMonth(time.Format("2004-04-05:00"))
}

/*
The type xsd:gMonthDay represents a specific day that recurs
every year. The letter g signifies "Gregorian." xsd:gMonthDay
can be used to say, for example, that your birthday is on the
14th of April every year. The format of xsd:gMonthDay is --MM-DD.

Source: http://www.datypic.com/sc/xsd/t-xsd_gMonthDay.html

More info: https://www.w3.org/TR/xmlschema-2/#gMonthDay
*/
type GMonthDay AnySimpleType

/*
Construct an instance of xsd GMonthDay type
*/
func (tp GMonthDay) NewGMonthDay(time time.Time) GMonthDay {
	return GMonthDay(fmt.Sprintf("--", time.Month(), "-", time.Day()))
}

/*
The type xsd:gDay represents a day that recurs every month.
The letter g signifies "Gregorian." xsd:gDay can be used to say,
for example, that checks are paid on the 5th of each month.
To represent a duration of days, use the duration type instead.
The format of gDay is ---DD.

Source: http://www.datypic.com/sc/xsd/t-xsd_gDay.html

More info: https://www.w3.org/TR/xmlschema-2/#gDay
*/
type GDay AnySimpleType

/*
Construct an instance of xsd GDay type
*/
func (tp GDay) NewGDay(time time.Time) GDay {
	return GDay(fmt.Sprintf("---", time.Day()))
}

/*
The type xsd:gMonth represents a specific month that recurs
every year. The letter g signifies "Gregorian." xsd:gMonth
can be used to indicate, for example, that fiscal year-end
processing occurs in September of every year. To represent
a duration of months, use the duration type instead. The format
of xsd:gMonth is --MM.

Source: http://www.datypic.com/sc/xsd/t-xsd_gMonth.html

More info: https://www.w3.org/TR/xmlschema-2/#gMonth
*/
type GMonth AnySimpleType

func (tp GMonth) NewGMonth(time time.Time) GMonth {
	return GMonth(fmt.Sprintf("--", time.Month()))
}

/*
The xsd:hexBinary type represents binary data as a sequence
of binary octets. It uses hexadecimal encoding, where each
binary octet is a two-character hexadecimal number.
Lowercase and uppercase letters A through F are permitted.
For example, 0FB8 and 0fb8 are two equal xsd:hexBinary
representations consisting of two octets.

Source: http://www.datypic.com/sc/xsd/t-xsd_hexBinary.html

More info: https://www.w3.org/TR/xmlschema-2/#hexBinary
*/
type HexBinary AnySimpleType

func (tp HexBinary) NewHexBinary(data []byte) HexBinary {
	return HexBinary(hex.EncodeToString(data))
}

/*
base64Binary represents Base64-encoded arbitrary binary data.
The ·value space· of base64Binary is the set of finite-length sequences of binary octets.
For base64Binary data the entire binary stream is encoded using the Base64 Alphabet in [RFC 2045].

base64Binary has the following ·constraining facets·:
• length
• minLength
• maxLength
• pattern
• enumeration
• whiteSpace

More info: https://www.w3.org/TR/xmlschema-2/#base64Binary
*/
type Base64Binary AnySimpleType

func (tp Base64Binary) NewBase64Binary(data []byte) Base64Binary {
	return Base64Binary(base64.StdEncoding.EncodeToString(data))
}

/*
anyURI represents a Uniform Resource Identifier Reference (URI).
An anyURI value can be absolute or relative, and may have an optional
fragment identifier (i.e., it may be a URI Reference).
This type should be used to specify the intention that the
value fulfills the role of a URI as defined by [RFC 2396], as amended by [RFC 2732].

anyURI has the following ·constraining facets·:
• length
• minLength
• maxLength
• pattern
• enumeration
• whiteSpace

More info: https://www.w3.org/TR/xmlschema-2/#anyURI
*/
type AnyURI AnySimpleType

func (tp AnyURI) NewAnyURI(data url.URL) AnyURI {
	return AnyURI(data.String())
}

/*
QName represents XML qualified names. The ·value space· of QName is the set of tuples
{namespace name, local part}, where namespace name is an anyURI and local part is an NCName.
The ·lexical space· of QName is the set of strings that ·match· the QName production of [Namespaces in XML].

QName has the following ·constraining facets·:
• length
• minLength
• maxLength
• pattern
• enumeration
• whiteSpace

More info: https://www.w3.org/TR/xmlschema-2/#QName
*/
type QName AnySimpleType

func (tp QName) NewQName(prefix, local string) QName {
	var result string
	if len(prefix) == 0 {
		result += local
	} else {
		result = prefix + ":" + local
	}
	return QName(result)
}

//TODO: NOTATION datatype
//type NOTATION AnySimpleType

/*
   Derived types
*/

type NormalizedString String

// TODO: check normalization
func (tp NormalizedString) NewNormalizedString(data string) (NormalizedString, error) {
	if strings.ContainsAny(data, "\r\n\t<>&") {
		return NormalizedString(""), errors.New("String " + data + "  contains forbidden symbols")
	}
	return NormalizedString(data), nil
}

type Token NormalizedString

func (tp Token) NewToken(data NormalizedString) (Token, error) {
	trailing_leading_whitespaces := regexp.MustCompile(`^[\s\p{Zs}]+|[\s\p{Zs}]+$`)
	multiple_whitespaces := regexp.MustCompile(`[\s\p{Zs}]{2,}`)
	//Removing trailing and leading whitespaces and multiple spaces
	/*final := re_leadclose_whtsp.ReplaceAllString(data, "")
	final = re_inside_whtsp.ReplaceAllString(final, " ")*/
	if strings.ContainsAny(string(data), "\r\n\t<>&") || trailing_leading_whitespaces.MatchString(string(data)) || multiple_whitespaces.MatchString(string(data)) {
		return Token(""), errors.New("String " + string(data) + "  contains forbidden symbols or whitespaces")
	}

	return Token(data), nil
}

type Language Token

func (tp Language) NewLanguage(data Token) (Language, error) {
	//Pattern was given from https://www.w3.org/2001/05/datatypes.xsd
	rgxp := regexp.MustCompile(`([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*`)
	if rgxp.MatchString(string(data)) {
		return Language(""), errors.New("String does not match pattern ([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*")
	}
	return Language(data), nil
}

type NMTOKEN Token

// TODO: check for valid symbols: https://www.w3.org/TR/xml/#NT-Nmtoken
func (tp NMTOKEN) NewNMTOKEN(data string) NMTOKEN {
	return NMTOKEN(data)
}

type NMTOKENS []NMTOKEN

func (tp NMTOKENS) NewNMTOKENS(data []NMTOKEN) NMTOKENS {
	result := make(NMTOKENS, len(data))
	for i, j := range data {
		result[i] = j
	}
	return result
}

type Name Token

// TODO: implements https://www.w3.org/TR/xml/#NT-Name
func (tp Name) NewName(data Token) Name {
	return Name(data)
}

type NCName Name

// TODO: https://www.w3.org/TR/REC-xml/#NT-Name and https://www.w3.org/TR/xml-names/#NT-NCName
func (tp NCName) NewNCName(data Name) NCName {
	return NCName(data)
}

// TODO: improve next types to correspond to XMLSchema
type ID NCName

func (tp ID) NewID(data NCName) ID {
	return ID(data)
}

type IDREF NCName

func (tp IDREF) NewIDREF(data NCName) IDREF {
	return IDREF(data)
}

type IDREFS []IDREF

func (tp IDREFS) NewIDREFS(data []IDREF) IDREFS {
	result := make(IDREFS, len(data))
	for i, j := range data {
		result[i] = j
	}
	return result
}

type ENTITY NCName

func (tp ENTITY) NewENTITY(data NCName) ENTITY {
	return ENTITY(data)
}

type ENTITIES []ENTITY

func (tp ENTITIES) NewENTITIES(data []ENTITY) ENTITIES {
	result := make(ENTITIES, len(data))
	for i, j := range data {
		result[i] = j
	}
	return result
}

type Integer int64

func (tp Integer) NewInteger(data int64) Integer {
	return Integer(data)
}

type NonPositiveInteger int64

func (tp NonPositiveInteger) NewNonPositiveInteger(data int64) (NonPositiveInteger, error) {
	if data > 0 {
		return 0, errors.New("Value must be less or equal to 0")
	}
	return NonPositiveInteger(data), nil
}

type NegativeInteger int64

func (tp NegativeInteger) NewNegativeInteger(data int64) (NegativeInteger, error) {
	if data >= 0 {
		return 0, errors.New("Value must be less than 0")
	}
	return NegativeInteger(data), nil
}

type Long int64

func (tp Long) NewLong(data int64) Long {
	return Long(data)
}

type Int int32

func (tp Int) NewInt(data int32) Int {
	return Int(data)
}

type Short int16

func (tp Short) NewShort(data int16) Short {
	return Short(data)
}

type Byte int8

func (tp Byte) NewByte(data int8) Byte {
	return Byte(data)
}

type NonNegativeInteger int64

func (tp NonNegativeInteger) NewNonNegativeInteger(data int64) (NonNegativeInteger, error) {
	if data > 0 {
		return 0, errors.New("Value must be more or equal to 0")
	}
	return NonNegativeInteger(data), nil
}

type UnsignedLong uint64

func (tp UnsignedLong) NewUnsignedLong(data uint64) UnsignedLong {
	return UnsignedLong(data)
}

type UnsignedInt uint32

func (tp UnsignedInt) NewUnsignedInt(data uint32) UnsignedInt {
	return UnsignedInt(data)
}

type UnsignedShort uint16

func (tp UnsignedShort) NewUnsignedShort(data uint16) UnsignedShort {
	return UnsignedShort(data)
}

type UnsignedByte uint8

func (tp UnsignedByte) NewUnsignedByte(data uint8) UnsignedByte {
	return UnsignedByte(data)
}

type PositiveInteger int64

func (tp PositiveInteger) NewPositiveInteger(data int64) (PositiveInteger, error) {
	if data >= 0 {
		return 0, errors.New("Value must be more than 0")
	}
	return PositiveInteger(data), nil
}
