package choose_fonts

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"kitty/tools/utils"
)

var _ = fmt.Print

type kitty_font_backend_type struct {
	from                    io.ReadCloser
	to                      io.WriteCloser
	json_decoder            *json.Decoder
	cmd                     *exec.Cmd
	stderr                  strings.Builder
	lock                    sync.Mutex
	r                       io.ReadCloser
	w                       io.WriteCloser
	wait_for_exit           chan error
	started, exited, failed bool
	timeout                 time.Duration
}

func (k *kitty_font_backend_type) start() (err error) {
	exe := utils.KittyExe()
	if exe == "" {
		exe = utils.Which("kitty")
	}
	if exe == "" {
		return fmt.Errorf("Failed to find the kitty executable, this kitten requires the kitty executable to be present. You can use the environment variable KITTY_PATH_TO_KITTY_EXE to specify the path to the kitty executable")
	}

	k.cmd = exec.Command(exe, "+runpy", "from kittens.choose_fonts.backend import main; main()")
	k.cmd.Stderr = &k.stderr

	if k.r, k.to, err = os.Pipe(); err != nil {
		return err
	}
	k.cmd.Stdin = k.r
	if k.from, k.w, err = os.Pipe(); err != nil {
		return err
	}
	k.cmd.Stdout = k.w
	k.json_decoder = json.NewDecoder(k.from)
	if err = k.cmd.Start(); err != nil {
		return err
	}
	k.started = true
	k.timeout = 60 * time.Second
	k.wait_for_exit = make(chan error)
	go func() {
		k.wait_for_exit <- k.cmd.Wait()
	}()
	return
}

var kitty_font_backend kitty_font_backend_type

func (k *kitty_font_backend_type) send(v any) error {
	if k.to == nil {
		return fmt.Errorf("Trying to send data when to pipe is nil")
	}
	data, err := json.Marshal(v)
	if err != nil {
		return fmt.Errorf("Could not encode message to kitty with error: %w", err)
	}
	c := make(chan error)
	go func() {
		if _, err = k.to.Write(data); err != nil {
			c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err)
			return
		}
		if _, err = k.to.Write([]byte{'\n'}); err != nil {
			c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err)
			return
		}
		c <- nil
	}()
	select {
	case err := <-c:
		return err
	case <-time.After(k.timeout):
		return fmt.Errorf("Timed out waiting to write to kitty font backend after %v", k.timeout)
	case err := <-k.wait_for_exit:
		k.exited = true
		if err == nil {
			err = fmt.Errorf("kitty font backend exited with no error while waiting for a response from it")
		} else {
			k.failed = true
		}
		return err
	}
}

func (k *kitty_font_backend_type) query(action string, cmd map[string]any, result any) error {
	k.lock.Lock()
	defer k.lock.Unlock()
	if cmd == nil {
		cmd = make(map[string]any)
	}
	cmd["action"] = action
	if err := k.send(cmd); err != nil {
		return err
	}
	c := make(chan error)
	go func() {
		if err := k.json_decoder.Decode(result); err != nil {
			c <- fmt.Errorf("Failed to decode JSON from kitty with error: %w", err)
		}
		c <- nil
	}()
	select {
	case err := <-c:
		return err
	case <-time.After(k.timeout):
		return fmt.Errorf("Timed out waiting for response from kitty font backend after %v", k.timeout)
	case err := <-k.wait_for_exit:
		k.exited = true
		if err == nil {
			err = fmt.Errorf("kitty font backed exited with no error while waiting for a response from it")
		} else {
			k.failed = true
		}
		return err
	}
}

func (k *kitty_font_backend_type) release() (err error) {
	if k.r != nil {
		k.r.Close()
		k.r = nil
	}
	if k.to != nil {
		k.to.Close()
		k.to = nil
	}
	if k.w != nil {
		k.w.Close()
		k.w = nil
	}
	if k.from != nil {
		k.from.Close()
		k.from = nil
	}
	if k.started && !k.exited {
		timeout := 2 * time.Second
		select {
		case err = <-k.wait_for_exit:
			k.exited = true
			if err != nil {
				k.failed = true
			}
		case <-time.After(timeout):
			k.failed = true
			err = fmt.Errorf("Timed out waiting for kitty font backend to exit for %v", timeout)
		}
	}
	os.Stderr.WriteString(k.stderr.String())
	return
}
