/*
 * Copyright 2024 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * MIT License
 *
 * Copyright (c) 2016 json-iterator
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * The source code of this file is written based on json-iterator,
 * all modifications are Copyright 2021 CloudWeGo Authors.
 */

package utils

import (
	"errors"
	"fmt"
	"strings"
	"unicode/utf16"
	"unicode/utf8"
	"unsafe"
)

// const of json keyword char
const (
	EmptyJSON  = "{}"
	Comma      = ','
	Colon      = ':'
	DQuotation = '"'
	LeftBrace  = '{'
	RightBrace = '}'
)

const (
	t1 = 0x00 // 0000 0000
	tx = 0x80 // 1000 0000
	t2 = 0xC0 // 1100 0000
	t3 = 0xE0 // 1110 0000
	t4 = 0xF0 // 1111 0000
	t5 = 0xF8 // 1111 1000

	maskx = 0x3F // 0011 1111

	rune1Max = 1<<7 - 1
	rune2Max = 1<<11 - 1
	rune3Max = 1<<16 - 1

	surrogateMin = 0xD800
	surrogateMax = 0xDFFF

	maxRune   = '\U0010FFFF' // Maximum valid Unicode code point.
	runeError = '\uFFFD'     // the "error" Rune or "Unicode replacement character"

	hex = "0123456789abcdef"
)

// Map2JSONStr transform map[string]string to json str, perf is better than use json lib directly
func _Map2JSONStr(mapInfo map[string]string) (str string, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("Map2JSONStr panic: %+v", r)
		}
	}()
	size := len(mapInfo)
	if mapInfo == nil || size == 0 {
		return EmptyJSON, nil
	}
	// calculate actual byte size that avoid allocate mem multi times
	idx := 0
	byteSize := 2
	for k, v := range mapInfo {
		byteSize += len(k) + len(v) + 5
		if idx++; idx < size {
			byteSize++
		}
	}
	var strBuilder strings.Builder
	strBuilder.Grow(byteSize)
	strBuilder.WriteByte(LeftBrace)
	idx = 0
	for k, v := range mapInfo {
		wrapStrWithQuotation(k, &strBuilder)
		strBuilder.WriteByte(Colon)
		wrapStrWithQuotation(v, &strBuilder)
		if idx++; idx < size {
			strBuilder.WriteByte(Comma)
		}
	}
	strBuilder.WriteByte(RightBrace)
	return strBuilder.String(), nil
}

// JSONStr2Map transform json str to map[string]string, perf is better than use json lib directly
func _JSONStr2Map(jsonStr string) (mapInfo map[string]string, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("JSONStr2Map panic: %+v", r)
		}
	}()
	data := []byte(jsonStr)
	size := len(data)
	lastIdx := size - 1
	idx := 0
	var c byte
	if c, idx, err = nextToken(data, idx, lastIdx); err != nil {
		return
	}
	var isNull bool
	if idx, isNull = checkNull(c, data, idx, lastIdx); isNull {
		return
	}
	if c != LeftBrace {
		err = fmt.Errorf("json str is invalid")
		return
	}
	if ch, _, _ := nextToken(data, idx, lastIdx); ch == RightBrace {
		return
	}

	mapInfo = make(map[string]string)
	for ; c == Comma || c == LeftBrace; c, idx, err = nextToken(data, idx, lastIdx) {
		if err != nil {
			err = fmt.Errorf("json str is invalid")
			return
		}
		var key, val string
		if key, idx, err = readString(data, idx, lastIdx); err != nil {
			return
		}
		if c, idx, err = nextToken(data, idx, lastIdx); c != ':' || err != nil {
			err = fmt.Errorf("json str is invalid, expect ':' after object field, but found %s", string(c))
			return
		}
		if val, idx, err = readString(data, idx, lastIdx); err != nil {
			return
		}
		mapInfo[key] = val
	}
	return mapInfo, err
}

func readString(buf []byte, idx, lastIdx int) (string, int, error) {
	var err error
	var c byte
	var isNull bool
	if c, idx, err = nextToken(buf, idx, lastIdx); err != nil {
		return "", idx, err
	}
	var str []byte
	if c == '"' {
		start := idx
		noESC := true
		for idx <= lastIdx {
			if c, idx, err = readByte(buf, idx, lastIdx); err != nil {
				return "", idx, err
			}
			switch c {
			case '"':
				if start < idx-1 {
					if noESC {
						str = buf[start : idx-1]
					} else {
						str = append(str, buf[start:idx-1]...)
					}
				}
				return *(*string)(unsafe.Pointer(&str)), idx, nil
			case '\\':
				if start < idx-1 {
					if noESC {
						str = buf[start : idx-1]
					} else {
						str = append(str, buf[start:idx-1]...)
					}
				}
				if c, idx, err = readByte(buf, idx, lastIdx); err != nil {
					return "", idx, err
				}
				if str, idx, err = readEscapedChar(c, buf, idx, str, lastIdx); err != nil {
					return "", 0, err
				}
				start = idx
				noESC = false
			}
		}
	} else if idx, isNull = checkNull(c, buf, idx, lastIdx); isNull {
		return "", idx, nil
	}
	err = fmt.Errorf("json str is invalid, expects '\"' or n, but found %s", string(c))
	return *(*string)(unsafe.Pointer(&str)), idx, err
}

func readByte(buf []byte, idx, lastIdx int) (byte, int, error) {
	if lastIdx < idx {
		return 0, -1, fmt.Errorf("readByte no more data")
	}
	c := buf[idx]
	idx++
	return c, idx, nil
}

func nextToken(buf []byte, idx, lastIdx int) (byte, int, error) {
	if lastIdx < idx {
		return 0, -1, errors.New("nextToken no more data")
	}
	var c byte
	for idx <= lastIdx {
		c = buf[idx]
		idx++
		switch c {
		case ' ', '\n', '\t', '\r':
			continue
		}
		return c, idx, nil
	}
	return c, idx, nil
}

func checkNull(c byte, data []byte, idx, lastIdx int) (int, bool) {
	if c == 'n' {
		ch, idx, _ := readByte(data, idx, lastIdx)
		if ch != 'u' {
			idx--
			return idx, false
		}
		ch, idx, _ = readByte(data, idx, lastIdx)
		if ch != 'l' {
			idx--
			return idx, false
		}
		ch, idx, _ = readByte(data, idx, lastIdx)
		if ch != 'l' {
			idx--
			return idx, false
		}
		return idx, true
	}
	return idx, false
}

func readU4(buf []byte, idx, lastIdx int) (rune, int, error) {
	var err error
	var ret rune
	for i := 0; i < 4; i++ {
		var c byte
		if c, idx, err = readByte(buf, idx, lastIdx); err != nil {
			return ret, idx, err
		}
		if c >= '0' && c <= '9' {
			ret = ret*16 + rune(c-'0')
		} else if c >= 'a' && c <= 'f' {
			ret = ret*16 + rune(c-'a'+10)
		} else if c >= 'A' && c <= 'F' {
			ret = ret*16 + rune(c-'A'+10)
		} else {
			return ret, idx, fmt.Errorf("unicode invalid: expects 0~9 or a~f, but found %v", string([]byte{c}))
		}
	}
	return ret, idx, nil
}

// refer to json-iterator/go/iter_str readEscapedChar
func readEscapedChar(c byte, buf []byte, idx int, str []byte, lastIdx int) ([]byte, int, error) {
	var err error
	switch c {
	case 'u':
		var r rune
		if r, idx, err = readU4(buf, idx, lastIdx); err != nil {
			return str, idx, err
		}
		// 是否是扩展字符
		if utf16.IsSurrogate(r) {
			if c, idx, err = readByte(buf, idx, lastIdx); err != nil {
				return str, idx, err
			}
			if c != '\\' {
				idx--
				str = appendRune(str, r)
				return str, idx, nil
			}
			if c, idx, err = readByte(buf, idx, lastIdx); err != nil {
				return str, idx, err
			}
			if c != 'u' {
				str = appendRune(str, r)
				return readEscapedChar(c, buf, idx, str, lastIdx)
			}
			var r2 rune
			if r2, idx, err = readU4(buf, idx, lastIdx); err != nil {
				return str, idx, err
			}
			combined := utf16.DecodeRune(r, r2)
			if combined == '\uFFFD' {
				str = appendRune(str, r)
				str = appendRune(str, r2)
			} else {
				str = appendRune(str, combined)
			}
		} else {
			str = appendRune(str, r)
		}
	case '"':
		str = append(str, '"')
	case '\\':
		str = append(str, '\\')
	case '/':
		str = append(str, '/')
	case 'b':
		str = append(str, '\b')
	case 'f':
		str = append(str, '\f')
	case 'n':
		str = append(str, '\n')
	case 'r':
		str = append(str, '\r')
	case 't':
		str = append(str, '\t')
	default:
		return str, idx, errors.New("invalid escape char after \\")
	}
	return str, idx, nil
}

// refer to json-iterator/go/stream_str writeStringSlowPath
func wrapStrWithQuotation(s string, strBuilder *strings.Builder) {
	strBuilder.WriteByte(DQuotation)
	valLen := len(s)
	i := 0
	start := i
	for i < valLen {
		c := s[i]
		if c < utf8.RuneSelf && htmlSafeSet[c] {
			i++
			continue
		} else {
			if b := s[i]; b < utf8.RuneSelf {
				if start < i {
					strBuilder.WriteString(s[start:i])
				}
				switch b {
				case '\\', '"':
					strBuilder.WriteByte('\\')
					strBuilder.WriteByte(b)
				case '\n':
					strBuilder.WriteByte('\\')
					strBuilder.WriteByte('n')
				case '\r':
					strBuilder.WriteByte('\\')
					strBuilder.WriteByte('r')
				case '\t':
					strBuilder.WriteByte('\\')
					strBuilder.WriteByte('t')
				default:
					// This encodes bytes < 0x20 except for \t, \n and \r.
					// If escapeHTML is set, it also escapes <, >, and &
					// because they can lead to security holes when
					// user-controlled strings are rendered into JSON
					// and served to some browsers.
					strBuilder.WriteString(`\u00`)
					strBuilder.WriteByte(hex[b>>4])
					strBuilder.WriteByte(hex[b&0xF])
				}
				i++
				start = i
				continue
			}
			c, size := utf8.DecodeRuneInString(s[i:])
			if c == utf8.RuneError && size == 1 {
				if start < i {
					strBuilder.WriteString(s[start:i])
				}
				strBuilder.WriteString(`\ufffd`)
				i++
				start = i
				continue
			}
			// U+2028 is LINE SEPARATOR.
			// U+2029 is PARAGRAPH SEPARATOR.
			// They are both technically valid characters in JSON strings,
			// but don't work in JSONP, which has to be evaluated as JavaScript,
			// and can lead to security holes there. It is valid JSON to
			// escape them, so we do so unconditionally.
			// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
			if c == '\u2028' || c == '\u2029' {
				if start < i {
					strBuilder.WriteString(s[start:i])
				}
				strBuilder.WriteString(`\u202`)
				strBuilder.WriteByte(hex[c&0xF])
				i += size
				start = i
				continue
			}
			i += size
		}
	}
	if start < valLen {
		strBuilder.WriteString(s[start:])
	}
	strBuilder.WriteByte(DQuotation)
}

// refer to json-iterator/go/iter_str appendRune
func appendRune(p []byte, r rune) []byte {
	// Negative values are erroneous. Making it unsigned addresses the problem.
	switch i := uint32(r); {
	case i <= rune1Max:
		p = append(p, byte(r))
		return p
	case i <= rune2Max:
		p = append(p, t2|byte(r>>6))
		p = append(p, tx|byte(r)&maskx)
		return p
	case i > maxRune, surrogateMin <= i && i <= surrogateMax:
		r = runeError
		fallthrough
	case i <= rune3Max:
		p = append(p, t3|byte(r>>12))
		p = append(p, tx|byte(r>>6)&maskx)
		p = append(p, tx|byte(r)&maskx)
		return p
	default:
		p = append(p, t4|byte(r>>18))
		p = append(p, tx|byte(r>>12)&maskx)
		p = append(p, tx|byte(r>>6)&maskx)
		p = append(p, tx|byte(r)&maskx)
		return p
	}
}

var htmlSafeSet = [utf8.RuneSelf]bool{
	' ':      true,
	'!':      true,
	'"':      false,
	'#':      true,
	'$':      true,
	'%':      true,
	'&':      false,
	'\'':     true,
	'(':      true,
	')':      true,
	'*':      true,
	'+':      true,
	',':      true,
	'-':      true,
	'.':      true,
	'/':      true,
	'0':      true,
	'1':      true,
	'2':      true,
	'3':      true,
	'4':      true,
	'5':      true,
	'6':      true,
	'7':      true,
	'8':      true,
	'9':      true,
	':':      true,
	';':      true,
	'<':      false,
	'=':      true,
	'>':      false,
	'?':      true,
	'@':      true,
	'A':      true,
	'B':      true,
	'C':      true,
	'D':      true,
	'E':      true,
	'F':      true,
	'G':      true,
	'H':      true,
	'I':      true,
	'J':      true,
	'K':      true,
	'L':      true,
	'M':      true,
	'N':      true,
	'O':      true,
	'P':      true,
	'Q':      true,
	'R':      true,
	'S':      true,
	'T':      true,
	'U':      true,
	'V':      true,
	'W':      true,
	'X':      true,
	'Y':      true,
	'Z':      true,
	'[':      true,
	'\\':     false,
	']':      true,
	'^':      true,
	'_':      true,
	'`':      true,
	'a':      true,
	'b':      true,
	'c':      true,
	'd':      true,
	'e':      true,
	'f':      true,
	'g':      true,
	'h':      true,
	'i':      true,
	'j':      true,
	'k':      true,
	'l':      true,
	'm':      true,
	'n':      true,
	'o':      true,
	'p':      true,
	'q':      true,
	'r':      true,
	's':      true,
	't':      true,
	'u':      true,
	'v':      true,
	'w':      true,
	'x':      true,
	'y':      true,
	'z':      true,
	'{':      true,
	'|':      true,
	'}':      true,
	'~':      true,
	'\u007f': true,
}
