// Package telnet provides some fancy tools, like an ASCII-art stream.
package telnet

import (
	"fmt"
	"io"
	"log"
	"net"
	"strings"
	"time"
)

var (
	// Cfg contains the different options of the telnet package, see below
	// TODO Config should not be exported
	Cfg            *Options
	currentMessage map[string]*string
	clientCount    map[string]int
)

// Options holds telnet package configuration
type Options struct {
	Enabled       bool
	ListenAddress string
	Width         int
	Height        int
	Delay         int
}

// Serve starts the telnet server and listen to clients
func Serve(config *Options) {
	Cfg = config

	if !config.Enabled {
		return
	}

	currentMessage = make(map[string]*string)
	clientCount = make(map[string]int)

	listener, err := net.Listen("tcp", config.ListenAddress)
	if err != nil {
		log.Printf("Error while listening to the address %s: %s", config.ListenAddress, err)
		return
	}

	go func() {
		for {
			s, err := listener.Accept()
			if err != nil {
				log.Printf("Error while accepting TCP socket: %s", s)
				continue
			}

			go func(s net.Conn) {
				streamID := ""
				// Request for stream ID
				for {
					_, err = s.Write([]byte("[GHOSTREAM]\nEnter stream ID: "))
					if err != nil {
						log.Println("Error while requesting stream ID to telnet client")
						_ = s.Close()
						return
					}
					buff := make([]byte, 255)
					n, err := s.Read(buff)
					if err != nil {
						log.Println("Error while requesting stream ID to telnet client")
						_ = s.Close()
						return
					}

					// Avoid bruteforce
					time.Sleep(3 * time.Second)

					streamID = string(buff[:n])
					streamID = strings.Replace(streamID, "\r", "", -1)
					streamID = strings.Replace(streamID, "\n", "", -1)

					if len(streamID) > 0 {
						if strings.ToLower(streamID) == "exit" {
							_, _ = s.Write([]byte("Goodbye!\n"))
							_ = s.Close()
							return
						}
						if _, ok := currentMessage[streamID]; !ok {
							_, err = s.Write([]byte("Unknown stream ID.\n"))
							if err != nil {
								log.Println("Error while requesting stream ID to telnet client")
								_ = s.Close()
								return
							}
							continue
						}
						break
					}
				}

				clientCount[streamID]++

				// Hide terminal cursor
				_, _ = s.Write([]byte("\033[?25l"))

				for {
					n, err := s.Write([]byte(*currentMessage[streamID]))
					if err != nil {
						log.Printf("Error while sending TCP data: %s", err)
						_ = s.Close()
						clientCount[streamID]--
						break
					}
					if n == 0 {
						_ = s.Close()
						clientCount[streamID]--
						break
					}
					time.Sleep(time.Duration(config.Delay) * time.Millisecond)
				}
			}(s)
		}
	}()

	log.Println("Telnet server initialized")
}

// GetNumberConnectedSessions returns the numbers of clients that are viewing the stream through a telnet shell
func GetNumberConnectedSessions(streamID string) int {
	if Cfg == nil || !Cfg.Enabled {
		return 0
	}
	return clientCount[streamID]
}

// StartASCIIArtStream send all packets received by ffmpeg as ASCII Art to telnet clients
func StartASCIIArtStream(streamID string, reader io.ReadCloser) {
	if !Cfg.Enabled {
		_ = reader.Close()
		return
	}

	currentMessage[streamID] = new(string)
	pixelBuff := make([]byte, Cfg.Width*Cfg.Height)
	textBuff := strings.Builder{}
	for {
		n, err := reader.Read(pixelBuff)
		if err != nil {
			log.Printf("An error occurred while reading input: %s", err)
			break
		}
		if n == 0 {
			// Stream is finished
			break
		}

		// Header
		textBuff.Reset()
		textBuff.Grow((40*Cfg.Width+6)*Cfg.Height + 47)
		for i := 0; i < 42; i++ {
			textBuff.WriteByte('\n')
		}

		// Convert image to ASCII
		for i, pixel := range pixelBuff {
			if i%Cfg.Width == 0 {
				// New line
				textBuff.WriteString("\033[49m\n")
			}

			// Print two times the character to make a square
			text := fmt.Sprintf("\033[48;2;%d;%d;%dm ", pixel, pixel, pixel)
			textBuff.WriteString(text)
			textBuff.WriteString(text)
		}
		textBuff.WriteString("\033[49m")

		*(currentMessage[streamID]) = textBuff.String()
	}
}