package cli

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	byte2 "git.nspix.com/golang/micro/helper/pool/byte"
	"git.nspix.com/golang/micro/helper/unsafestr"
	"github.com/peterh/liner"
	"net"
	"os"
	"strings"
	"time"
)

type Client struct {
	appName       string
	conn          net.Conn
	context       context.Context
	readyChan     chan struct{}
	completerChan chan *Frame
	responseChan  chan *Frame
}

func (client *Client) writePack(packet *Frame) (err error) {
	if packet.Timestamp == 0 {
		packet.Timestamp = time.Now().Unix()
	}
	err = writeFrame(client.conn, packet)
	return
}

func (client *Client) stdout(args ...interface{}) {
	fmt.Fprint(os.Stdout, args...)
}

func (client *Client) rdyLoop(c chan error) {
	var (
		err error
		buf []byte
	)
	buf = byte2.Get(MaxReadBufferLength)
	defer func() {
		byte2.Put(buf)
	}()
	for {
		packet := &Frame{}
		if packet, err = readFrame(client.conn); err != nil {
			c <- err
			break
		}
		switch packet.Type {
		case PacketTypeCompleter:
			client.completerChan <- packet
		case PacketTypeEcho:
			vs := make(map[string]string)
			if err = json.Unmarshal(packet.Data, &vs); err == nil {
				time.Sleep(time.Millisecond * 200)
				client.stdout("\u001B[2K")
				client.stdout("\u001B[0G")
				client.appName = vs["name"]
				client.stdout(fmt.Sprintf("Welcome to the %s monitor.", vs["name"]), EOL)
				client.stdout(fmt.Sprintf("Server Version: %s, connection id is %s", vs["version"], vs["cid"]), EOL)
				client.stdout(fmt.Sprintf("Last login: %s from %s", vs["login_at"], vs["client_addr"]), EOL)
				client.stdout("Type 'help;' for help. Type 'cls' to clear the current input statement.", EOL)
				select {
				case client.readyChan <- struct{}{}:
				default:
				}
			}
		case PacketTypeData:
			client.responseChan <- packet
		}
	}
	select {
	case c <- err:
	case <-client.context.Done():
	}
}

func (client *Client) interactive(c chan error) {
	var (
		err   error
		line  string
		state *liner.State
	)
	state = liner.NewLiner()
	state.SetCompleter(client.CompleterHandleFunc)
	select {
	case <-client.readyChan:
	case <-client.context.Done():
		return
	}
	defer func() {
		_ = state.Close()
		c <- err
	}()
	for {
		if line, err = state.Prompt(client.appName + "> "); err != nil {
			break
		}
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		if strings.ToLower(line) == "exit" || strings.ToLower(line) == "quit" {
			client.stdout("Bye Bye", EOL)
			return
		}
		if strings.ToLower(line) == "clear" || strings.ToLower(line) == "cls" {
			client.stdout("\033[2J")
			continue
		}
		if err = client.writePack(&Frame{Type: PacketTypeData, Data: []byte(line)}); err != nil {
			break
		}
		state.AppendHistory(line)
		select {
		case <-client.context.Done():
			err = client.context.Err()
			return
		case <-time.After(time.Second * 30):
			client.stdout("request timeout", EOL)
		case res, ok := <-client.responseChan:
			if !ok {
				break
			}
			if res.Error != "" {
				client.stdout(res.Error, EOL)
			} else {
				client.stdout(string(bytes.Trim(res.Data, "\r\n")), EOL)
			}
		}
	}
}

func (client *Client) CompleterHandleFunc(str string) (ss []string) {
	var err error
	if err = client.writePack(&Frame{
		Type: PacketTypeCompleter,
		Data: unsafestr.StringToBytes(str),
	}); err != nil {
		return
	}
	select {
	case <-time.After(time.Second * 5):
		return nil
	case <-client.context.Done():
		return nil
	case resp, ok := <-client.completerChan:
		if ok {
			ss = make([]string, 0)
			err = json.Unmarshal(resp.Data, &ss)
		}
	}
	return
}

func (client *Client) Run() (err error) {
	var (
		errChan chan error
	)
	errChan = make(chan error)
	defer func() {
		_ = client.conn.Close()
	}()
	if err = client.writePack(&Frame{
		Type: PacketTypeEcho,
	}); err != nil {
		return
	}
	client.stdout("connecting")
	go client.rdyLoop(errChan)
	go client.interactive(errChan)
	err = <-errChan
	return
}

func OpenInteractive(ctx context.Context, conn net.Conn) (err error) {
	if ctx == nil {
		ctx = context.Background()
	}
	cli := &Client{
		conn:          conn,
		context:       ctx,
		readyChan:     make(chan struct{}),
		completerChan: make(chan *Frame),
		responseChan:  make(chan *Frame, 1),
	}
	err = cli.Run()
	return
}
