package convutil

import (
	"errors"
	"regexp"
	"tinyrdm/backend/types"
	strutil "tinyrdm/backend/utils/string"
)

type DataConvert interface {
	Encode(string) (string, bool)
	Decode(string) (string, bool)
}

var (
	jsonConv    JsonConvert
	yamlConv    YamlConvert
	xmlConv     XmlConvert
	base64Conv  Base64Convert
	binaryConv  BinaryConvert
	hexConv     HexConvert
	gzipConv    GZipConvert
	deflateConv DeflateConvert
	zstdConv    ZStdConvert
	brotliConv  BrotliConvert
	msgpackConv MsgpackConvert
)

// ConvertTo convert string to specified type
// @param decodeType empty string indicates automatic detection
// @param formatType empty string indicates automatic detection
// @param custom decoder if any
func ConvertTo(str, decodeType, formatType string, customDecoder []CmdConvert) (value, resultDecode, resultFormat string) {
	if len(str) <= 0 {
		// empty content
		if len(formatType) <= 0 {
			resultFormat = types.FORMAT_RAW
		} else {
			resultFormat = formatType
		}
		if len(decodeType) <= 0 {
			resultDecode = types.DECODE_NONE
		} else {
			resultDecode = decodeType
		}
		return
	}

	// decode first
	value, resultDecode = decodeWith(str, decodeType, customDecoder)
	// then format content
	if len(formatType) <= 0 {
		value, resultFormat = autoViewAs(value)
	} else {
		value, resultFormat = viewAs(value, formatType)
	}
	return
}

func decodeWith(str, decodeType string, customDecoder []CmdConvert) (value, resultDecode string) {
	if len(decodeType) > 0 {
		switch decodeType {
		case types.DECODE_NONE:
			value = str

		case types.DECODE_BASE64:
			if base64Str, ok := base64Conv.Decode(str); ok {
				value = base64Str
			} else {
				value = str
			}

		case types.DECODE_GZIP:
			if gzipStr, ok := gzipConv.Decode(str); ok {
				value = gzipStr
			} else {
				value = str
			}

		case types.DECODE_DEFLATE:
			if falteStr, ok := deflateConv.Decode(str); ok {
				value = falteStr
			} else {
				value = str
			}

		case types.DECODE_ZSTD:
			if zstdStr, ok := zstdConv.Decode(str); ok {
				value = zstdStr
			} else {
				value = str
			}

		case types.DECODE_BROTLI:
			if brotliStr, ok := brotliConv.Decode(str); ok {
				value = brotliStr
			} else {
				value = str
			}

		case types.DECODE_MSGPACK:
			if msgpackStr, ok := msgpackConv.Decode(str); ok {
				value = msgpackStr
			} else {
				value = str
			}

		default:
			for _, decoder := range customDecoder {
				if decoder.Name == decodeType {
					if decodedStr, ok := decoder.Decode(str); ok {
						value = decodedStr
					} else {
						value = str
					}
					break
				}
			}
		}

		resultDecode = decodeType
		return
	}

	value, resultDecode = autoDecode(str, customDecoder)
	return
}

// attempt try possible decode method
// if no decode is possible, it will return the origin string value and "none" decode type
func autoDecode(str string, customDecoder []CmdConvert) (value, resultDecode string) {
	if len(str) > 0 {
		// pure digit content may incorrect regard as some encoded type, skip decode
		if match, _ := regexp.MatchString(`^\d+$`, str); !match {
			var ok bool
			if len(str)%4 == 0 && len(str) >= 12 && !strutil.IsSameChar(str) {
				if value, ok = base64Conv.Decode(str); ok {
					resultDecode = types.DECODE_BASE64
					return
				}
			}

			if value, ok = gzipConv.Decode(str); ok {
				resultDecode = types.DECODE_GZIP
				return
			}

			// FIXME: skip decompress with deflate due to incorrect format checking
			//if value, ok = decodeDeflate(str); ok {
			//	resultDecode = types.DECODE_DEFLATE
			//	return
			//}

			if value, ok = zstdConv.Decode(str); ok {
				resultDecode = types.DECODE_ZSTD
				return
			}

			// FIXME: skip decompress with brotli due to incorrect format checking
			//if value, ok = decodeBrotli(str); ok {
			//	resultDecode = types.DECODE_BROTLI
			//	return
			//}

			if value, ok = msgpackConv.Decode(str); ok {
				resultDecode = types.DECODE_MSGPACK
				return
			}

			// try decode with custom decoder
			for _, decoder := range customDecoder {
				if decoder.Auto {
					if value, ok = decoder.Decode(str); ok {
						resultDecode = decoder.Name
						return
					}
				}
			}
		}
	}

	value = str
	resultDecode = types.DECODE_NONE
	return
}

func viewAs(str, formatType string) (value, resultFormat string) {
	if len(formatType) > 0 {
		switch formatType {
		default:
			fallthrough
		case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML:
			value = str

		case types.FORMAT_JSON:
			if jsonStr, ok := jsonConv.Decode(str); ok {
				value = jsonStr
			} else {
				value = str
			}

		case types.FORMAT_HEX:
			if hexStr, ok := hexConv.Decode(str); ok {
				value = hexStr
			} else {
				value = str
			}

		case types.FORMAT_BINARY:
			if binStr, ok := binaryConv.Decode(str); ok {
				value = binStr
			} else {
				value = str
			}
		}
		resultFormat = formatType
		return
	}
	return
}

// attempt automatic convert to possible types
// if no conversion is possible, it will return the origin string value and "plain text" type
func autoViewAs(str string) (value, resultFormat string) {
	if len(str) > 0 {
		var ok bool
		if value, ok = jsonConv.Decode(str); ok {
			resultFormat = types.FORMAT_JSON
			return
		}

		if value, ok = yamlConv.Decode(str); ok {
			resultFormat = types.FORMAT_YAML
			return
		}

		if value, ok = xmlConv.Decode(str); ok {
			resultFormat = types.FORMAT_XML
			return
		}

		if strutil.ContainsBinary(str) {
			if value, ok = hexConv.Decode(str); ok {
				resultFormat = types.FORMAT_HEX
				return
			}
		}
	}

	value = str
	resultFormat = types.FORMAT_RAW
	return
}

func SaveAs(str, format, decode string, customDecoder []CmdConvert) (value string, err error) {
	value = str
	switch format {
	case types.FORMAT_JSON:
		if jsonStr, ok := jsonConv.Encode(str); ok {
			value = jsonStr
		} else {
			err = errors.New("invalid json data")
			return
		}

	case types.FORMAT_HEX:
		if hexStr, ok := hexConv.Encode(str); ok {
			value = hexStr
		} else {
			err = errors.New("invalid hex data")
			return
		}

	case types.FORMAT_BINARY:
		if binStr, ok := binaryConv.Encode(str); ok {
			value = binStr
		} else {
			err = errors.New("invalid binary data")
			return
		}
	}

	switch decode {
	case types.DECODE_NONE:
		return

	case types.DECODE_BASE64:
		value, _ = base64Conv.Encode(value)
		return

	case types.DECODE_GZIP:
		if gzipStr, ok := gzipConv.Encode(str); ok {
			value = gzipStr
		} else {
			err = errors.New("fail to build gzip")
		}
		return

	case types.DECODE_DEFLATE:
		if deflateStr, ok := deflateConv.Encode(str); ok {
			value = deflateStr
		} else {
			err = errors.New("fail to build deflate")
		}
		return

	case types.DECODE_ZSTD:
		if zstdStr, ok := zstdConv.Encode(str); ok {
			value = zstdStr
		} else {
			err = errors.New("fail to build zstd")
		}
		return

	case types.DECODE_BROTLI:
		if brotliStr, ok := brotliConv.Encode(str); ok {
			value = brotliStr
		} else {
			err = errors.New("fail to build brotli")
		}
		return

	case types.DECODE_MSGPACK:
		if msgpackStr, ok := msgpackConv.Encode(str); ok {
			value = msgpackStr
		} else {
			err = errors.New("fail to build msgpack")
		}
		return

	default:
		for _, decoder := range customDecoder {
			if decoder.Name == decode {
				if encodedStr, ok := decoder.Encode(str); ok {
					value = encodedStr
				} else {
					value = str
				}
				return
			}
		}
	}
	return str, nil
}
