package json

import (
	"unicode/utf8"
)

// htmlSafeSet holds the value true if the ASCII character with the given
// array position can be safely represented inside a JSON string, embedded
// inside of HTML <script> tags, without any additional escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), the backslash character ("\"), HTML opening and closing
// tags ("<" and ">"), and the ampersand ("&").
var htmlSafeSet = [256]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,
	0x80:     false,
	0x81:     false,
	0x82:     false,
	0x83:     false,
	0x84:     false,
	0x85:     false,
	0x86:     false,
	0x87:     false,
	0x88:     false,
	0x89:     false,
	0x8a:     false,
	0x8b:     false,
	0x8c:     false,
	0x8d:     false,
	0x8e:     false,
	0x8f:     false,
	0x90:     false,
	0x91:     false,
	0x92:     false,
	0x93:     false,
	0x94:     false,
	0x95:     false,
	0x96:     false,
	0x97:     false,
	0x98:     false,
	0x99:     false,
	0x9a:     false,
	0x9b:     false,
	0x9c:     false,
	0x9d:     false,
	0x9e:     false,
	0x9f:     false,
	0xa0:     false,
	0xa1:     false,
	0xa2:     false,
	0xa3:     false,
	0xa4:     false,
	0xa5:     false,
	0xa6:     false,
	0xa7:     false,
	0xa8:     false,
	0xa9:     false,
	0xaa:     false,
	0xab:     false,
	0xac:     false,
	0xad:     false,
	0xae:     false,
	0xaf:     false,
	0xb0:     false,
	0xb1:     false,
	0xb2:     false,
	0xb3:     false,
	0xb4:     false,
	0xb5:     false,
	0xb6:     false,
	0xb7:     false,
	0xb8:     false,
	0xb9:     false,
	0xba:     false,
	0xbb:     false,
	0xbc:     false,
	0xbd:     false,
	0xbe:     false,
	0xbf:     false,
	0xc0:     false,
	0xc1:     false,
	0xc2:     false,
	0xc3:     false,
	0xc4:     false,
	0xc5:     false,
	0xc6:     false,
	0xc7:     false,
	0xc8:     false,
	0xc9:     false,
	0xca:     false,
	0xcb:     false,
	0xcc:     false,
	0xcd:     false,
	0xce:     false,
	0xcf:     false,
	0xd0:     false,
	0xd1:     false,
	0xd2:     false,
	0xd3:     false,
	0xd4:     false,
	0xd5:     false,
	0xd6:     false,
	0xd7:     false,
	0xd8:     false,
	0xd9:     false,
	0xda:     false,
	0xdb:     false,
	0xdc:     false,
	0xdd:     false,
	0xde:     false,
	0xdf:     false,
	0xe0:     false,
	0xe1:     false,
	0xe2:     false,
	0xe3:     false,
	0xe4:     false,
	0xe5:     false,
	0xe6:     false,
	0xe7:     false,
	0xe8:     false,
	0xe9:     false,
	0xea:     false,
	0xeb:     false,
	0xec:     false,
	0xed:     false,
	0xee:     false,
	0xef:     false,
	0xf0:     false,
	0xf1:     false,
	0xf2:     false,
	0xf3:     false,
	0xf4:     false,
	0xf5:     false,
	0xf6:     false,
	0xf7:     false,
	0xf8:     false,
	0xf9:     false,
	0xfa:     false,
	0xfb:     false,
	0xfc:     false,
	0xfd:     false,
	0xfe:     false,
	0xff:     false,
}

// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
var safeSet = [utf8.RuneSelf]bool{
	' ':      true,
	'!':      true,
	'"':      false,
	'#':      true,
	'$':      true,
	'%':      true,
	'&':      true,
	'\'':     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,
	'<':      true,
	'=':      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,
	'\\':     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,
}

var hex = "0123456789abcdef"

func (e *Encoder) encodeEscapedString(s string) {
	valLen := len(s)
	// write string, the fast path, without utf8 and escape support
	i := 0
	for ; i < valLen; i++ {
		if !htmlSafeSet[s[i]] {
			break
		}
	}
	e.buf = append(e.buf, '"')
	if i == valLen {
		e.buf = append(e.buf, s...)
		e.buf = append(e.buf, '"')
		return
	}
	e.buf = append(e.buf, s[:i]...)
	e.writeStringSlowPathWithHTMLEscaped(i, s, valLen)
}

func (e *Encoder) writeStringSlowPathWithHTMLEscaped(i int, s string, valLen int) {
	start := i
	// for the remaining parts, we process them char by char
	for i < valLen {
		if b := s[i]; b < utf8.RuneSelf {
			if htmlSafeSet[b] {
				i++
				continue
			}
			if start < i {
				e.buf = append(e.buf, s[start:i]...)
			}
			switch b {
			case '\\', '"':
				e.buf = append(e.buf, '\\', b)
			case '\n':
				e.buf = append(e.buf, '\\', 'n')
			case '\r':
				e.buf = append(e.buf, '\\', 'r')
			case '\t':
				e.buf = append(e.buf, '\\', '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.
				e.buf = append(e.buf, `\u00`...)
				e.buf = append(e.buf, hex[b>>4], hex[b&0xF])
			}
			i++
			start = i
			continue
		}
		c, size := utf8.DecodeRuneInString(s[i:])
		if c == utf8.RuneError && size == 1 {
			if start < i {
				e.buf = append(e.buf, s[start:i]...)
			}
			e.buf = append(e.buf, `\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 {
				e.buf = append(e.buf, s[start:i]...)
			}
			e.buf = append(e.buf, `\u202`...)
			e.buf = append(e.buf, hex[c&0xF])
			i += size
			start = i
			continue
		}
		i += size
	}
	if start < len(s) {
		e.buf = append(e.buf, s[start:]...)
	}
	e.buf = append(e.buf, '"')
}

func (e *Encoder) encodeNoEscapedString(s string) {
	valLen := len(s)

	// write string, the fast path, without utf8 and escape support
	i := 0
	for ; i < valLen; i++ {
		c := s[i]
		if c <= 31 || c == '"' || c == '\\' {
			break
		}
	}
	e.buf = append(e.buf, '"')
	if i == valLen {
		e.buf = append(e.buf, s...)
		e.buf = append(e.buf, '"')
		return
	}
	e.buf = append(e.buf, s[:i]...)
	e.writeStringSlowPath(i, s, valLen)
}

func (e *Encoder) writeStringSlowPath(i int, s string, valLen int) {
	start := i
	// for the remaining parts, we process them char by char
	for i < valLen {
		if b := s[i]; b < utf8.RuneSelf {
			if safeSet[b] {
				i++
				continue
			}
			if start < i {
				e.buf = append(e.buf, s[start:i]...)
			}
			switch b {
			case '\\', '"':
				e.buf = append(e.buf, '\\', b)
			case '\n':
				e.buf = append(e.buf, '\\', 'n')
			case '\r':
				e.buf = append(e.buf, '\\', 'r')
			case '\t':
				e.buf = append(e.buf, '\\', '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.
				e.buf = append(e.buf, []byte(`\u00`)...)
				e.buf = append(e.buf, hex[b>>4], hex[b&0xF])
			}
			i++
			start = i
			continue
		}
		i++
		continue
	}
	if start < len(s) {
		e.buf = append(e.buf, s[start:]...)
	}
	e.buf = append(e.buf, '"')
}