// Copyright 2023 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package charset

import (
	"bytes"
	"encoding/binary"
	"strings"
	"unicode/utf8"

	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/simplifiedchinese"
)

// Current implementation meets the requirement of GB18030-2022

// EncodingGB18030Impl is the instance of encodingGB18030
var EncodingGB18030Impl = &encodingGB18030{encodingGBK{encodingBase{enc: customGB18030{}}}}

func init() {
	EncodingGB18030Impl.self = EncodingGB18030Impl
}

// encodingGB18030 is GB18030 encoding.
type encodingGB18030 struct {
	encodingGBK
}

// Name implements Encoding interface.
func (*encodingGB18030) Name() string {
	return CharsetGB18030
}

// Tp implements Encoding interface.
func (*encodingGB18030) Tp() EncodingTp {
	return EncodingTpGB18030
}

// Peek implements Encoding interface.
func (*encodingGB18030) Peek(src []byte) []byte {
	length := len(src)
	if length == 0 {
		return src
	}
	charLen := 4
	if length > 0 && src[0] < 0x80 {
		charLen = 1
	} else if length >= 2 && src[1] >= 0x40 && src[1] <= 0xfe {
		charLen = 2
	}
	if charLen < length {
		return src[:charLen]
	}
	return src
}

func (*encodingGB18030) MbLen(bs string) int {
	if len(bs) < 2 {
		return 0
	}

	if 0x81 <= bs[0] && bs[0] <= 0xfe {
		if (0x40 <= bs[1] && bs[1] <= 0x7e) || (0x80 <= bs[1] && bs[1] <= 0xfe) {
			return 2
		}
		if 0x30 <= bs[1] && bs[1] <= 0x39 && 0x81 <= bs[2] && bs[2] <= 0xfe && 0x30 <= bs[3] && bs[3] <= 0x39 {
			return 4
		}
	}

	return 0
}

// ToUpper implements Encoding interface.
func (*encodingGB18030) ToUpper(d string) string {
	return strings.ToUpperSpecial(GB18030Case, d)
}

// ToLower implements Encoding interface.
func (*encodingGB18030) ToLower(d string) string {
	return strings.ToLowerSpecial(GB18030Case, d)
}

// customGB18030 is a simplifiedchinese.GB18030 wrapper.
type customGB18030 struct{}

// NewCustomGB18030Encoder return a custom GB18030 encoding.
func NewCustomGB18030Encoder() *encoding.Encoder {
	return customGB18030{}.NewEncoder()
}

// NewDecoder returns simplifiedchinese.GB18030.NewDecoder().
func (customGB18030) NewDecoder() *encoding.Decoder {
	return &encoding.Decoder{
		Transformer: customGB18030Decoder{
			gb18030Decoder: simplifiedchinese.GB18030.NewDecoder(),
		},
	}
}

// NewEncoder returns simplifiedchinese.gb18030.
func (customGB18030) NewEncoder() *encoding.Encoder {
	return &encoding.Encoder{
		Transformer: customGB18030Encoder{
			gb18030Encoder: simplifiedchinese.GB18030.NewEncoder(),
		},
	}
}

type customGB18030Decoder struct {
	gb18030Decoder *encoding.Decoder
}

func (c customGB18030Decoder) decodeDoubleBytes(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
	c1, c0 := src[1], src[0]
	var r rune
	offset := int32(c1 - 0xa1)

	switch c0 {

	// see "双字节 1 区" in 《GB+18030-2022.pdf》
	case 0xa2:
		if c1 >= 0xab && c1 <= 0xb0 {
			r = '\ue766' + int32(c1-0xab)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 == 0xe4 {
			r = '\ue76d'
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xef && c1 <= 0xf0 {
			r = '\ue76e' + int32(c1-0xef)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xfd && c1 <= 0xfe {
			r = '\ue770' + int32(c1-0xfd)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	case 0xa4:
		if c1 >= 0xf4 && c1 <= 0xfe {
			r = '\ue772' + int32(c1-0xf4)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	case 0xa5:
		if c1 >= 0xf7 && c1 <= 0xfe {
			r = '\ue77d' + int32(c1-0xf7)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	case 0xa6:
		if c1 >= 0xb9 && c1 <= 0xc0 {
			r = '\ue785' + int32(c1-0xb9)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xd9 && c1 <= 0xdf || c1 >= 0xec && c1 <= 0xed || c1 == 0xf3 {
			switch c1 {
			case 0xd9:
				r = '\ufe10'
			case 0xda:
				r = '\ufe12'
			case 0xdb:
				r = '\ufe11'
			case 0xdc:
				r = '\ufe13'
			case 0xdd:
				r = '\ufe14'
			case 0xde:
				r = '\ufe15'
			case 0xdf:
				r = '\ufe16'
			case 0xec:
				r = '\ufe17'
			case 0xed:
				r = '\ufe18'
			case 0xf3:
				r = '\ufe19'
			}
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xf6 && c1 <= 0xfe {
			r = '\ue797' + int32(c1-0xf6)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	case 0xa7:
		if c1 >= 0xc2 && c1 <= 0xd0 {
			r = '\ue7a0' + int32(c1-0xc2)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xf2 && c1 <= 0xfe {
			r = '\ue7af' + int32(c1-0xf2)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	case 0xa8:
		if c1 == 0xbc {
			r = '\u1e3f'
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xc1 && c1 <= 0xc4 {
			r = '\ue7c9' + int32(c1-0xc1)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xea && c1 <= 0xfe {
			r = '\ue7cd' + int32(c1-0xea)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
		// see "双字节 5 区" in 《GB+18030-2022.pdf》
		if c1 >= 0x96 && c1 <= 0xa0 {
			r = '\ue7bc' + int32(c1-0x96)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	case 0xa9:
		if c1 >= 0xa1 && c1 <= 0xa3 {
			r = '\ue7fe' + int32(c1-0xa1)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0xf0 && c1 <= 0xfe {
			r = '\ue801' + int32(c1-0xf0)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
		// see "双字节 5 区" in 《GB+18030-2022.pdf》
		if c1 == 0x58 {
			r = '\ue7e2'
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 == 0x5b {
			r = '\ue7e3'
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0x5d && c1 <= 0x5f {
			r = '\ue7e4' + int32(c1-0x5d)
			return utf8.EncodeRune(dst[:], r), 2, nil
		} else if c1 >= 0x97 && c1 <= 0xa0 {
			r = '\ue7f4' + int32(c1-0x97)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}

	// see "双字节 2 区" in 《GB+18030-2022.pdf》
	case 0xd7:
		if c1 >= 0xfa && c1 <= 0xfe {
			r = '\ue810' + int32(c1-0xfa)
			return utf8.EncodeRune(dst[:], r), 2, nil
		}

	// see "双字节 4 区" in 《GB+18030-2022.pdf》
	case 0xfe:
		switch c1 {
		case 0x51:
			fallthrough
		case 0x52:
			fallthrough
		case 0x53:
			r = '\ue816' + int32(c1-0x51)
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x59:
			r = '\u9fb4'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x61:
			r = '\u9fb5'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x66:
			r = '\u9fb6'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x67:
			r = '\u9fb7'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x6c:
			r = '\ue831'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x6d:
			r = '\u9fb8'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x76:
			r = '\ue83b'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x7e:
			r = '\u9fb9'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x90:
			r = '\u9fba'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0x91:
			r = '\ue855'
			return utf8.EncodeRune(dst[:], r), 2, nil
		case 0xa0:
			r = '\u9fbb'
			return utf8.EncodeRune(dst[:], r), 2, nil
		}
	}

	if (c0 >= 0xaa && c0 <= 0xaf || c0 >= 0xf8 && c0 <= 0xfe) && (c1 >= 0xa1 && c1 <= 0xfe) {
		switch c0 {
		// see "双字节用户区 1" in 《GB+18030-2022.pdf》
		case 0xaa:
			r = '\ue000'
		case 0xab:
			r = '\ue05e'
		case 0xac:
			r = '\ue0bc'
		case 0xad:
			r = '\ue11a'
		case 0xae:
			r = '\ue178'
		case 0xaf:
			r = '\ue1d6'
		// see "双字节用户区 2" in 《GB+18030-2022.pdf》
		case 0xf8:
			r = '\ue234'
		case 0xf9:
			r = '\ue292'
		case 0xfa:
			r = '\ue2f0'
		case 0xfb:
			r = '\ue34e'
		case 0xfc:
			r = '\ue3ac'
		case 0xfd:
			r = '\ue40a'
		case 0xfe:
			r = '\ue468'
		}
		r += offset
		return utf8.EncodeRune(dst[:], r), 2, nil
	}

	if (c0 >= 0xa1 && c0 <= 0xa7) && (c1 >= 0x40 && c1 <= 0xa0 && c1 != 0x7f) {
		// see "双字节用户区 3" in 《GB+18030-2022.pdf》
		if c1 > 0x7f {
			offset -= 1
		}
		switch c0 {
		case 0xa1:
			r = '\ue4c6'
		case 0xa2:
			r = '\ue526'
		case 0xa3:
			r = '\ue586'
		case 0xa4:
			r = '\ue5e6'
		case 0xa5:
			r = '\ue646'
		case 0xa6:
			r = '\ue6a6'
		case 0xa7:
			r = '\ue706'
		}
		r += offset
		return utf8.EncodeRune(dst[:], r), 2, nil
	}
	return c.gb18030Decoder.Transform(dst, src, atEOF)
}

// Transform special treatment for 0x80,
func (c customGB18030Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
	if len(src) == 0 {
		return 0, 0, nil
	}
	if src[0] == 0x80 {
		return utf8.EncodeRune(dst[:], utf8.RuneError), 1, nil
	}

	if len(src) == 2 {
		return c.decodeDoubleBytes(dst, src, atEOF)
	}

	if len(src) == 4 {
		k := uint32(src[0])<<24 + uint32(src[1])<<16 + uint32(src[2])<<8 + uint32(src[3])
		if r, ok := gb18030ToUnicode[k]; ok {
			return utf8.EncodeRune(dst[:], r), 4, nil
		}
	}

	return c.gb18030Decoder.Transform(dst, src, atEOF)
}

// Reset is same as simplifiedchinese.GB18030.Reset().
func (c customGB18030Decoder) Reset() {
	c.gb18030Decoder.Reset()
}

type customGB18030Encoder struct {
	gb18030Encoder *encoding.Encoder
}

// Transform special treatment for `€`,
func (c customGB18030Encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
	nSrc = len(src)
	if bytes.HasPrefix(src, []byte{0xe2, 0x82, 0xac}) { /* '€' in UTF8 */
		binary.BigEndian.PutUint16(dst, 0xa2e3)
		return 2, 3, nil
	}

	if bytes.HasPrefix(src, []byte{0xe1, 0xb8, 0xbf}) { /* 'ḿ' in UTF8, GB18030-2005 */
		binary.BigEndian.PutUint16(dst, 0xa8bc)
		return 2, 3, nil
	}

	// see "3 GBK" in http://www.fmddlmyy.cn/text24.html
	// or see "附录 D.1" in 《GB+18030-2022.pdf》
	if nSrc == 3 && src[0] == 0xe9 && src[1] == 0xbe && (src[2] >= 0xb4 && src[2] <= 0xbb) {
		switch src[2] {
		case 0xb4: // 龴
			binary.BigEndian.PutUint16(dst, 0xFE59)
		case 0xb5: // 龵
			binary.BigEndian.PutUint16(dst, 0xFE61)
		case 0xb6: // 龶
			binary.BigEndian.PutUint16(dst, 0xFE66)
		case 0xb7: // 龷
			binary.BigEndian.PutUint16(dst, 0xFE67)
		case 0xb8: // 龸
			binary.BigEndian.PutUint16(dst, 0xFE6D)
		case 0xb9: // 龹
			binary.BigEndian.PutUint16(dst, 0xFE7E)
		case 0xba: // 龺
			binary.BigEndian.PutUint16(dst, 0xFE90)
		case 0xbb: // 龻
			binary.BigEndian.PutUint16(dst, 0xFEA0)
		}
		return 2, 3, nil
	}

	// Unicode: E7C7, UTF-8: EE9F87, GB18030: 8135F437
	if bytes.HasPrefix(src, []byte{0xee, 0x9f, 0x87}) {
		binary.BigEndian.PutUint32(dst, 0x8135F437)
		return 4, 3, nil
	}

	var k uint32
	if nSrc == 3 {
		k = uint32(src[0])<<16 + uint32(src[1])<<8 + uint32(src[2])
	} else if nSrc == 4 {
		k = uint32(src[0])<<24 + uint32(src[1])<<16 + uint32(src[2])<<8 + uint32(src[3])
	}
	if v, ok := utf8ToDoubleBytesGB18030[k]; ok {
		binary.BigEndian.PutUint16(dst, v)
		return 2, nSrc, nil
	}
	if v, ok := utf8ToQuadBytesGB18030[k]; ok {
		binary.BigEndian.PutUint32(dst, v)
		return 4, nSrc, nil
	}

	return c.gb18030Encoder.Transform(dst, src, atEOF)
}

// Reset is same as simplifiedchinese.gb18030.
func (c customGB18030Encoder) Reset() {
	c.gb18030Encoder.Reset()
}
