// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package simplifiedchinese

import (
	"errors"
	"unicode/utf8"

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

// HZGB2312 is the HZ-GB2312 encoding.
var HZGB2312 encoding.Encoding = hzGB2312{}

type hzGB2312 struct{}

func (hzGB2312) NewDecoder() transform.Transformer {
	return new(hzGB2312Decoder)
}

func (hzGB2312) NewEncoder() transform.Transformer {
	return new(hzGB2312Encoder)
}

func (hzGB2312) String() string {
	return "HZ-GB2312"
}

var errInvalidHZGB2312 = errors.New("simplifiedchinese: invalid HZ-GB2312 encoding")

const (
	asciiState = iota
	gbState
)

type hzGB2312Decoder int

func (d *hzGB2312Decoder) Reset() {
	*d = asciiState
}

func (d *hzGB2312Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
	r, size := rune(0), 0
loop:
	for ; nSrc < len(src); nSrc += size {
		c0 := src[nSrc]
		if c0 >= utf8.RuneSelf {
			err = errInvalidHZGB2312
			break loop
		}

		if c0 == '~' {
			if nSrc+1 >= len(src) {
				err = transform.ErrShortSrc
				break loop
			}
			size = 2
			switch src[nSrc+1] {
			case '{':
				*d = gbState
				continue
			case '}':
				*d = asciiState
				continue
			case '~':
				if nDst >= len(dst) {
					err = transform.ErrShortDst
					break loop
				}
				dst[nDst] = '~'
				nDst++
				continue
			case '\n':
				continue
			default:
				err = errInvalidHZGB2312
				break loop
			}
		}

		if *d == asciiState {
			r, size = rune(c0), 1
		} else {
			if nSrc+1 >= len(src) {
				err = transform.ErrShortSrc
				break loop
			}
			c1 := src[nSrc+1]
			if c0 < 0x21 || 0x7e <= c0 || c1 < 0x21 || 0x7f <= c1 {
				err = errInvalidHZGB2312
				break loop
			}

			r, size = '\ufffd', 2
			if i := int(c0-0x01)*190 + int(c1+0x3f); i < len(decode) {
				r = rune(decode[i])
				if r == 0 {
					r = '\ufffd'
				}
			}
		}

		if nDst+utf8.RuneLen(r) > len(dst) {
			err = transform.ErrShortDst
			break loop
		}
		nDst += utf8.EncodeRune(dst[nDst:], r)
	}
	if atEOF && err == transform.ErrShortSrc {
		err = errInvalidHZGB2312
	}
	return nDst, nSrc, err
}

type hzGB2312Encoder int

func (d *hzGB2312Encoder) Reset() {
	*d = asciiState
}

func (e *hzGB2312Encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
	r, size := rune(0), 0
	for ; nSrc < len(src); nSrc += size {
		r = rune(src[nSrc])

		// Decode a 1-byte rune.
		if r < utf8.RuneSelf {
			size = 1
			if r == '~' {
				if nDst+2 > len(dst) {
					err = transform.ErrShortDst
					break
				}
				dst[nDst+0] = '~'
				dst[nDst+1] = '~'
				nDst += 2
				continue
			}

		} else {
			// Decode a multi-byte rune.
			r, size = utf8.DecodeRune(src[nSrc:])
			if size == 1 {
				// All valid runes of size 1 (those below utf8.RuneSelf) were
				// handled above. We have invalid UTF-8 or we haven't seen the
				// full character yet.
				if !atEOF && !utf8.FullRune(src[nSrc:]) {
					err = transform.ErrShortSrc
					break
				}
			}

			// func init checks that the switch covers all tables.
			switch {
			case encode0Low <= r && r < encode0High:
				if r = rune(encode0[r-encode0Low]); r != 0 {
					goto writeGB
				}
			case encode1Low <= r && r < encode1High:
				if r = rune(encode1[r-encode1Low]); r != 0 {
					goto writeGB
				}
			case encode2Low <= r && r < encode2High:
				if r = rune(encode2[r-encode2Low]); r != 0 {
					goto writeGB
				}
			case encode3Low <= r && r < encode3High:
				if r = rune(encode3[r-encode3Low]); r != 0 {
					goto writeGB
				}
			case encode4Low <= r && r < encode4High:
				if r = rune(encode4[r-encode4Low]); r != 0 {
					goto writeGB
				}
			}
			r = encoding.ASCIISub
		}

	writeASCII:
		if *e != asciiState {
			if nDst+3 > len(dst) {
				err = transform.ErrShortDst
				break
			}
			*e = asciiState
			dst[nDst+0] = '~'
			dst[nDst+1] = '}'
			nDst += 2
		} else if nDst >= len(dst) {
			err = transform.ErrShortDst
			break
		}
		dst[nDst] = uint8(r)
		nDst++
		continue

	writeGB:
		c0 := uint8(r>>8) - 0x80
		c1 := uint8(r) - 0x80
		if c0 < 0x21 || 0x7e <= c0 || c1 < 0x21 || 0x7f <= c1 {
			r = encoding.ASCIISub
			goto writeASCII
		}
		if *e == asciiState {
			if nDst+4 > len(dst) {
				err = transform.ErrShortDst
				break
			}
			*e = gbState
			dst[nDst+0] = '~'
			dst[nDst+1] = '{'
			nDst += 2
		} else if nDst+2 > len(dst) {
			err = transform.ErrShortDst
			break
		}
		dst[nDst+0] = c0
		dst[nDst+1] = c1
		nDst += 2
		continue
	}
	return nDst, nSrc, err
}
