package terminal

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os"
	"sync"
	"syscall"
	"unsafe"

	"gitlab.com/liamg/raft/buffer"
	"go.uber.org/zap"
)

type Terminal struct {
	buffer        *buffer.Buffer
	lock          sync.Mutex
	pty           *os.File
	logger        *zap.SugaredLogger
	title         string
	size          Winsize
	colourScheme  ColourScheme
	titleHandlers []chan bool
}

type Line struct {
	Cells   []Cell
	wrapped bool
}

type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16 //ignored, but necessary for ioctl calls
	y      uint16 //ignored, but necessary for ioctl calls
}

type Position struct {
	Line int
	Col  int
}

func New(pty *os.File, logger *zap.SugaredLogger, colourScheme ColourScheme) *Terminal {

	return &Terminal{
		buffer: buffer.NewBuffer(0, 0, buffer.CellAttributes{
			FgColour: colourScheme.DefaultFg,
			BgColour: colourScheme.DefaultBg,
		}),
		pty:           pty,
		logger:        logger,
		colourScheme:  colourScheme,
		titleHandlers: []chan bool{},
	}
}

func (terminal *Terminal) GetCell(col int, row int) *buffer.Cell {
	return terminal.buffer.GetCell(col, row)
}

func (terminal *Terminal) AttachDisplayChangeHandler(handler chan bool) {
	terminal.buffer.AttachDisplayChangeHandler(handler)
}

func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) {
	terminal.titleHandlers = append(terminal.titleHandlers, handler)
}

func (terminal *Terminal) emitTitleChange() {
	for _, h := range terminal.titleHandlers {
		go func(c chan bool) {
			c <- true
		}(h)
	}
}

func (terminal *Terminal) GetTitle() string {
	return terminal.title
}

func (terminal *Terminal) SetTitle(title string) {
	terminal.title = title
	terminal.emitTitleChange()
}

// Write sends data, i.e. locally typed keystrokes to the pty
func (terminal *Terminal) Write(data []byte) error {
	_, err := terminal.pty.Write(data)
	return err
}

// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
func (terminal *Terminal) Read() error {

	buffer := make(chan rune, 0xffff)

	reader := bufio.NewReader(terminal.pty)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go terminal.processInput(ctx, buffer)
	for {
		r, size, err := reader.ReadRune()
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		} else if size > 0 {
			buffer <- r
		}
	}

	//clean exit
	return nil
}

func (terminal *Terminal) Clear() {
	terminal.buffer.Clear()
}

func (terminal *Terminal) GetSize() (int, int) {
	return int(terminal.size.Width), int(terminal.size.Height)
}

func (terminal *Terminal) SetSize(newCols int, newLines int) error {
	terminal.lock.Lock()
	defer terminal.lock.Unlock()

	terminal.size.Width = uint16(newCols)
	terminal.size.Height = uint16(newLines)

	terminal.buffer.ResizeView(terminal.size.Width, terminal.size.Height)

	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(terminal.pty.Fd()),
		uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&terminal.size)))
	if err != 0 {
		return fmt.Errorf("Failed to set terminal size vai ioctl: Error no %d", err)
	}

	return nil
}

/*
------------------ ->
ssssssssssssssssss
ssssPPPPPPPPPPPPPP
xxxxxxxxx
xxxxxxxxxxxxxxxxxx
--------------------------
ssssssssssssssssss
SsssPPPPPPPPPPPPPP
xxxxxxxxx
xxxxxxxxxxxxxxxxxx




*/
