package jsonschema

import (
	"bytes"
	"fmt"
	"log"
	"math/big"
	"strings"
)

func init() {
	log.SetFlags(log.Ltime | log.Lshortfile)
}

var (
	trueLiteral  = []byte("true")
	falseLiteral = []byte("false")
	nullLiteral  = []byte("null")
)

var regexpExtraSpaceChars = string('\ufeff') + string('\u000b') + string('\u00a0') + string('\u2029') + string('\u2003')

var regexpControlChars = map[string]string{
	`\c@`: `\000`,
	`\cA`: `\001`,
	`\cB`: `\002`,
	`\cC`: `\003`,
	`\cD`: `\004`,
	`\cE`: `\005`,
	`\cF`: `\006`,
	`\cG`: `\007`,
	`\cH`: `\008`,
	`\cI`: `\009`,
	`\cJ`: `\00A`,
	`\cK`: `\00B`,
	`\cL`: `\00C`,
	`\cM`: `\00D`,
	`\cN`: `\00E`,
	`\cO`: `\00F`,
	`\cP`: `\010`,
	`\cQ`: `\011`,
	`\cR`: `\012`,
	`\cS`: `\013`,
	`\cT`: `\014`,
	`\cU`: `\015`,
	`\cV`: `\016`,
	`\cW`: `\017`,
	`\cX`: `\018`,
	`\cY`: `\019`,
	`\cZ`: `\01A`,
	`\c[`: `\01B`,
	`\c\`: `\01C`,
	`\c]`: `\01D`,
	`\c^`: `\01E`,
	`\c_`: `\01F`,
}

func addError(err, errs error) error {
	if err == nil {
		return errs
	}
	if errs == nil {
		return err
	}

	return fmt.Errorf("%w\n%s", errs, err.Error())
}

func convertRegexp(re string) string {
	re = strings.ReplaceAll(re, `\w`, `\pL`)
	// re = strings.ReplaceAll(re, `\w`, `[0-9A-Za-z_]`)
	// re = strings.ReplaceAll(re, `\W`, `[^0-9A-Za-z_]`)
	re = strings.ReplaceAll(re, `\d`, `\pN`)
	// re = strings.ReplaceAll(re, `\d`, `[0-9]`)
	// re = strings.ReplaceAll(re, `\D`, `[^0-9]`)
	re = strings.ReplaceAll(re, `\s`, `[\s`+regexpExtraSpaceChars+`]`)
	re = strings.ReplaceAll(re, `\S`, `[^\s`+regexpExtraSpaceChars+`]`)

	// Replace control escape characters with their unicod equivalent
	for cc, unicode := range regexpControlChars {
		re = strings.ReplaceAll(re, cc, unicode)
		re = strings.ReplaceAll(re, strings.ToLower(cc), unicode)
	}

	return re
}

func isInteger(val []byte) bool {
	floatVal, ok := new(big.Float).SetString(string(val))
	if !ok {
		return false
	}

	return floatVal.IsInt()
}

// This should more or less match what jsonparser detects.
// Unfortunately jsonparser doesn't expose it's detect type function.
func DetectJSONType(data []byte) ValueType {
	// Empty
	if len(data) == 0 {
		return Unknown
	}

	// Object
	if data[0] == '{' && data[len(data)-1] == '}' {
		return Object
	}

	// Array
	if data[0] == '[' && data[len(data)-1] == ']' {
		return Array
	}

	// String
	if data[0] == '"' && data[len(data)-1] == '"' {
		return String
	}

	// Number
	isInt := true
	isNum := false
numberLoop:
	for idx, c := range data {
		switch c {
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			// Do nothing
		case '-', '+':
			if idx != 0 {
				isInt = false
				isNum = false
				break numberLoop
			}
			// Do nothing
		case 'e':
			if idx != 1 {
				isInt = false
				isNum = false
				break numberLoop
			}
			// Do nothing
		case '.':
			isInt = false
			isNum = true
		default:
			isInt = false
			isNum = false
			break numberLoop
		}
	}
	if isNum {
		if isInteger(data) {
			isInt = true
			isNum = false
		} else {
			return Number
		}
	}
	if isInt {
		return Integer
	}

	// Null
	if bytes.Equal(data, nullLiteral) {
		return Null
	}

	// Boolean
	if bytes.Equal(data, trueLiteral) || bytes.Equal(data, falseLiteral) {
		return Boolean
	}

	// Assume it's a string
	// TODO: Ensure this is a valid assumption
	return String
}
