// +build windows

package readline

import (
	"bufio"
	"io"
	"strconv"
	"strings"
	"sync"
	"unicode/utf8"
	"unsafe"
)

const (
	_                = uint16(0)
	COLOR_FBLUE      = 0x0001
	COLOR_FGREEN     = 0x0002
	COLOR_FRED       = 0x0004
	COLOR_FINTENSITY = 0x0008

	COLOR_BBLUE      = 0x0010
	COLOR_BGREEN     = 0x0020
	COLOR_BRED       = 0x0040
	COLOR_BINTENSITY = 0x0080

	COMMON_LVB_UNDERSCORE = 0x8000
)

var ColorTableFg = []word{
	0,                                       // 30: Black
	COLOR_FRED,                              // 31: Red
	COLOR_FGREEN,                            // 32: Green
	COLOR_FRED | COLOR_FGREEN,               // 33: Yellow
	COLOR_FBLUE,                             // 34: Blue
	COLOR_FRED | COLOR_FBLUE,                // 35: Magenta
	COLOR_FGREEN | COLOR_FBLUE,              // 36: Cyan
	COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White
}

var ColorTableBg = []word{
	0,                                       // 40: Black
	COLOR_BRED,                              // 41: Red
	COLOR_BGREEN,                            // 42: Green
	COLOR_BRED | COLOR_BGREEN,               // 43: Yellow
	COLOR_BBLUE,                             // 44: Blue
	COLOR_BRED | COLOR_BBLUE,                // 45: Magenta
	COLOR_BGREEN | COLOR_BBLUE,              // 46: Cyan
	COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White
}

type ANSIWriter struct {
	target io.Writer
	wg     sync.WaitGroup
	ctx    *ANSIWriterCtx
	sync.Mutex
}

func NewANSIWriter(w io.Writer) *ANSIWriter {
	a := &ANSIWriter{
		target: w,
		ctx:    NewANSIWriterCtx(w),
	}
	return a
}

func (a *ANSIWriter) Close() error {
	a.wg.Wait()
	return nil
}

type ANSIWriterCtx struct {
	isEsc     bool
	isEscSeq  bool
	arg       []string
	target    *bufio.Writer
	wantFlush bool
}

func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {
	return &ANSIWriterCtx{
		target: bufio.NewWriter(target),
	}
}

func (a *ANSIWriterCtx) Flush() {
	a.target.Flush()
}

func (a *ANSIWriterCtx) process(r rune) bool {
	if a.wantFlush {
		if r == 0 || r == CharEsc {
			a.wantFlush = false
			a.target.Flush()
		}
	}
	if a.isEscSeq {
		a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)
		return true
	}

	switch r {
	case CharEsc:
		a.isEsc = true
	case '[':
		if a.isEsc {
			a.arg = nil
			a.isEscSeq = true
			a.isEsc = false
			break
		}
		fallthrough
	default:
		a.target.WriteRune(r)
		a.wantFlush = true
	}
	return true
}

func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
	arg := *argptr
	var err error

	if r >= 'A' && r <= 'D' {
		count := short(GetInt(arg, 1))
		info, err := GetConsoleScreenBufferInfo()
		if err != nil {
			return false
		}
		switch r {
		case 'A': // up
			info.dwCursorPosition.y -= count
		case 'B': // down
			info.dwCursorPosition.y += count
		case 'C': // right
			info.dwCursorPosition.x += count
		case 'D': // left
			info.dwCursorPosition.x -= count
		}
		SetConsoleCursorPosition(&info.dwCursorPosition)
		return false
	}

	switch r {
	case 'J':
		killLines()
	case 'K':
		eraseLine()
	case 'm':
		color := word(0)
		for _, item := range arg {
			var c int
			c, err = strconv.Atoi(item)
			if err != nil {
				w.WriteString("[" + strings.Join(arg, ";") + "m")
				break
			}
			if c >= 30 && c < 40 {
				color ^= COLOR_FINTENSITY
				color |= ColorTableFg[c-30]
			} else if c >= 40 && c < 50 {
				color ^= COLOR_BINTENSITY
				color |= ColorTableBg[c-40]
			} else if c == 4 {
				color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
			} else { // unknown code treat as reset
				color = ColorTableFg[7]
			}
		}
		if err != nil {
			break
		}
		kernel.SetConsoleTextAttribute(stdout, uintptr(color))
	case '\007': // set title
	case ';':
		if len(arg) == 0 || arg[len(arg)-1] != "" {
			arg = append(arg, "")
			*argptr = arg
		}
		return true
	default:
		if len(arg) == 0 {
			arg = append(arg, "")
		}
		arg[len(arg)-1] += string(r)
		*argptr = arg
		return true
	}
	*argptr = nil
	return false
}

func (a *ANSIWriter) Write(b []byte) (int, error) {
	a.Lock()
	defer a.Unlock()

	off := 0
	for len(b) > off {
		r, size := utf8.DecodeRune(b[off:])
		if size == 0 {
			return off, io.ErrShortWrite
		}
		off += size
		a.ctx.process(r)
	}
	a.ctx.Flush()
	return off, nil
}

func killLines() error {
	sbi, err := GetConsoleScreenBufferInfo()
	if err != nil {
		return err
	}

	size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x
	size += sbi.dwCursorPosition.x

	var written int
	kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),
		uintptr(size),
		sbi.dwCursorPosition.ptr(),
		uintptr(unsafe.Pointer(&written)),
	)
	return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
		uintptr(size),
		sbi.dwCursorPosition.ptr(),
		uintptr(unsafe.Pointer(&written)),
	)
}

func eraseLine() error {
	sbi, err := GetConsoleScreenBufferInfo()
	if err != nil {
		return err
	}

	size := sbi.dwSize.x
	sbi.dwCursorPosition.x = 0
	var written int
	return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
		uintptr(size),
		sbi.dwCursorPosition.ptr(),
		uintptr(unsafe.Pointer(&written)),
	)
}
