package door

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"
	"syscall"
	"time"
)

const CRNL = "\r\n"

/*
door32.sys:

0                            Line 1 : Comm type (0=local, 1=serial, 2=telnet)
0                            Line 2 : Comm or socket handle
38400                        Line 3 : Baud rate
Mystic 1.07                  Line 4 : BBSID (software name and version)
1                            Line 5 : User record position (1-based)
James Coyle                  Line 6 : User's real name
g00r00                       Line 7 : User's handle/alias
255                          Line 8 : User's security level
58                           Line 9 : User's time left (in minutes)
1                            Line 10: Emulation *See Below
1                            Line 11: Current node number
*/

func Color(arg ...int) string {
	var result string = "\x1b["
	for i := range arg {
		result += fmt.Sprintf("%d;", arg[i])
	}
	result = result[:len(result)-1]
	result += "m"
	return result
}

func ColorText(color string) string {
	// split on spaces, uppercase, match first 3 letter
	var result []int
	var bg bool

	result = append(result, 0)

	parts := strings.Fields(strings.ToUpper(color))
	for _, part := range parts {
		switch part {
		case "BLACK", "BLA":
			if bg {
				result = append(result, 40)
			} else {
				result = append(result, 30)
			}

		case "RED":
			if bg {
				result = append(result, 41)
			} else {
				result = append(result, 31)
			}

		case "GREEN", "GRE":
			if bg {
				result = append(result, 42)
			} else {
				result = append(result, 32)
			}

		case "BROWN", "BRO":
			if bg {
				result = append(result, 43)
			} else {
				result = append(result, 33)
			}

		case "YELLOW", "YEL":
			if bg {
				result = append(result, 43)
			} else {
				result = append(result, 33)
			}

		case "BLUE", "BLU":
			if bg {
				result = append(result, 44)
			} else {
				result = append(result, 34)
			}

		case "MAGENTA", "MAG":
			if bg {
				result = append(result, 45)
			} else {
				result = append(result, 35)
			}

		case "CYAN", "CYA":
			if bg {
				result = append(result, 46)
			} else {
				result = append(result, 36)
			}

		case "WHITE", "WHI":
			if bg {
				result = append(result, 47)
			} else {
				result = append(result, 37)
			}

		case "BOLD", "BOL", "BRIGHT", "BRI":
			result = append(result, 1)

		case "ON":
			bg = true

		case "BLINK", "BLI":
			result = append(result, 5)

		case "INVERT", "INVERSE", "INV":
			result = append(result, 7)
		default:
			fmt.Println("ColorText Unknown:", part)
		}

	}
	// fmt.Println("ColorText:", result)
	return Color(result...)
}

type BoxStyle struct {
	top_left     string
	top_right    string
	top          string
	side         string
	bottom_left  string
	bottom_right string
	middle_left  string
	middle_right string
}

var boxes = [4]BoxStyle{
	/*
		┌──┐
		│  │
		├──┤
		└──┘
	*/
	BoxStyle{"\xda", "\xbf", "\xc4", "\xb3", "\xc0", "\xd9", "\xc3", "\xb4"},
	/*
		╔══╗
		║  ║
		╠══╣
		╚══╝
	*/
	BoxStyle{"\xc9", "\xbb", "\xcd", "\xba", "\xc8", "\xbc", "\xcc", "\xb9"},
	/*
		╒══╕
		│  │
		╞══╡
		╘══╛
	*/
	BoxStyle{"\xd6", "\xb8", "\xcd", "\xb3", "\xd4", "\xbe", "\xc6", "\xb5"},
	/*
		╓──╖
		║  ║
		╟──╢
		╙──╜
	*/
	BoxStyle{"\xd6", "\xb7", "\xc4", "\xba", "\xd3", "\xbd", "\xc7", "\xb6"}}

var unicode_boxes = []BoxStyle{
	BoxStyle{"\u250c", "\u2510", "\u2500", "\u2502", "\u2514", "\u2518", "\u251c", "\u2524"},
	BoxStyle{"\u2554", "\u2557", "\u2550", "\u2551", "\u255a", "\u255d", "\u2560", "\u2563"},
	BoxStyle{"\u2553", "\u2556", "\u2500", "\u2551", "\u2559", "\u255c", "\u255f", "\u2562"},
	BoxStyle{"\u2552", "\u2555", "\u2550", "\u2502", "\u2558", "\u255b", "\u255e", "\u2561"},
}

type Box struct {
	Width int
	Style int
}

func (b *Box) Top() string {
	var style *BoxStyle
	if Unicode {
		style = &unicode_boxes[b.Style]
	} else {
		style = &boxes[b.Style]
	}
	return style.top_left + strings.Repeat(style.top, b.Width) + style.top_right
}

func (b *Box) Middle(text string) string {
	var style *BoxStyle
	if Unicode {
		style = &unicode_boxes[b.Style]
	} else {
		style = &boxes[b.Style]
	}
	return style.side + text + strings.Repeat(" ", b.Width-len(text)) + style.side
}

func (b *Box) Bottom() string {
	var style *BoxStyle
	if Unicode {
		style = &unicode_boxes[b.Style]
	} else {
		style = &boxes[b.Style]
	}
	return style.bottom_left + strings.Repeat(style.top, b.Width) + style.bottom_right
}

var Reset string = Color(0)
var READFD int
var WRITEFD int

type DropfileConfig struct {
	comm_type      int
	comm_handle    int
	baudrate       int
	BBSID          string
	user_number    int
	real_name      string
	handle         string
	security_level int
	time_left      int
	emulation      int
	node_number    int
}

type Door struct {
	config  DropfileConfig
	READFD  int
	WRITEFD int
}

func (d *Door) ReadDropfile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		fmt.Printf("Open(%s): %s\n", filename, err)
		os.Exit(2)
	}

	defer file.Close()

	var lines []string
	// read line by line

	// The scanner handles DOS and linux file endings.

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		lines = append(lines, line)
		// fmt.Printf("[%s]\n", line)
	}

	d.config.comm_type, err = strconv.Atoi(lines[0])
	d.config.comm_handle, err = strconv.Atoi(lines[1])
	d.config.baudrate, err = strconv.Atoi(lines[2])
	d.config.BBSID = lines[3]
	d.config.user_number, err = strconv.Atoi(lines[4])
	d.config.real_name = lines[5]
	d.config.handle = lines[6]
	d.config.security_level, err = strconv.Atoi(lines[7])
	d.config.time_left, err = strconv.Atoi(lines[8])
	d.config.emulation, err = strconv.Atoi(lines[9])
	d.config.node_number, err = strconv.Atoi(lines[10])

	d.READFD = d.config.comm_handle
	//if d.READFD == 0 {
	//	d.WRITEFD = 1
	//} else {
	d.WRITEFD = d.config.comm_handle
	//}
}

func (d *Door) HasKey() bool {
	var fdsetRead = syscall.FdSet{}
	clearAll(&fdsetRead)
	set(&fdsetRead, d.READFD)
	timeout := syscall.Timeval{Sec: 0, Usec: 1}
	v, _ := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
	if v == -1 {
		return false
	}
	if v == 0 {
		return false
	}
	return true
}

var Unicode bool
var CP437 bool
var Full_CP437 bool
var Height int
var Width int

func (d *Door) detect() {
	// if d.config.comm_handle == 0 {
	// d.Write("\377\375\042\377\373\001") // fix telnet client
	// }
	d.Write("\x1b[0;30;40m\x1b[2J\x1b[H") // black on black, clrscr, go home
	d.Write("\x03\x04\x1b[6n")            // hearts and diamonds does CP437 work?

	d.Write(CRNL + "\u2615\x1b[6n")
	d.Write("\x1b[999C\x1b[999B\x1b[6n" + Reset + "\x1b[2J\x1b[H") // goto end of screen + cursor pos
	// time.Sleep(50 * time.Millisecond)
	time.Sleep(250 * time.Millisecond)
	// read everything
	// telnet term isn't in RAW mode, so keys are buffer until <CR>
	var results string

	if true { // d.HasKey() {
		buffer := make([]byte, 100)
		r, err := syscall.Read(d.READFD, buffer)
		results = string(buffer[:r])
		output := strings.Replace(results, "\x1b", "^[", -1)
		fmt.Println("DETECT:", r, err, output)
	} else {
		// local telnet echos the reply  :()
		fmt.Println("DETECT: Nothing received.")
		return
	}

	if ((strings.Index(results, "1;1R") != -1) ||
		(strings.Index(results, "1;3R") != -1)) &&
		((strings.Index(results, "2:2R") != -1) ||
			(strings.Index(results, "2;3R") != -1)) {
		Unicode = true
	} else {
		Unicode = false
		CP437 = true
	}
	if strings.Index(results, "1;3R") != -1 {
		Full_CP437 = true
	}

	// get screen size
	var err error

	pos := strings.LastIndex(results, "\x1b")
	if pos != -1 {
		pos++
		if results[pos] == '[' {
			pos++
			results = results[pos:]
			pos = strings.Index(results, ";")
			if pos != -1 {
				height := results[:pos]
				// Height, err = strconv.Atoi(results)
				Height, err = strconv.Atoi(height)
				pos++
				results = results[pos:]

				pos = strings.Index(results, "R")
				if pos != -1 {
					width := results[:pos]

					Width, err = strconv.Atoi(width)
					fmt.Printf("Width: %s, %d, %v\n", results, Width, err)
				}
			} else {
				Height = 0
				Width = 0
			}
		}
	}
	fmt.Printf("Unicode %v Screen: %d X %d\n", Unicode, Width, Height)
}

func (d *Door) Init() {
	var dropfile string

	flag.StringVar(&dropfile, "d", "", "Path to dropfile")
	flag.Parse()
	if len(dropfile) == 0 {
		flag.PrintDefaults()
		os.Exit(2)
	}
	fmt.Printf("Loading: %s\n", dropfile)

	d.ReadDropfile(dropfile)

	fmt.Printf("BBS %s, User %s / Handle %s / File %d\n", d.config.BBSID, d.config.real_name, d.config.handle, d.READFD)
	d.detect()
}

func (d *Door) Write(output string) {
	buffer := []byte(output)
	n, err := syscall.Write(d.WRITEFD, buffer)
	if err != nil {
		fmt.Println("Write error/HANGUP?", n)
	}
	// No, this isn't it.  The # of bytes in buffer == bytes written.
	if n != len(buffer) {
		fmt.Printf("Write fail: %d != %d\n", len(buffer), n)
	}
}

/*
func write(output string, config *DropfileConfig) {
	buffer := []byte(output)
	n, err := syscall.Write(config.comm_handle, buffer)
	if err != nil {
		fmt.Println("Write error/HANGUP?", n)
	}
}
*/

// from: https://github.com/yubo/dea_ng
// https://github.com/yubo/dea_ng/blob/master/go/src/directoryserver/streaming.go

func set(fdSetPtr *syscall.FdSet, fd int) {
	(*fdSetPtr).Bits[fd/64] |= 1 << uint64(fd%64)
}

func isSet(fdSetPtr *syscall.FdSet, fd int) bool {
	return ((*fdSetPtr).Bits[fd/64] & (1 << uint64(fd%64))) != 0
}

func clearAll(fdSetPtr *syscall.FdSet) {
	for index, _ := range (*fdSetPtr).Bits {
		(*fdSetPtr).Bits[index] = 0
	}
}

func (d *Door) SleepKey(sleep int64) int {
	// var fdsetRead, fdsetWrite, fdsete syscall.FdSet
	var fdsetRead syscall.FdSet
	// fdsetWrite := syscall.FdSet
	clearAll(&fdsetRead)
	// clearAll(&fdsetWrite)
	// clearAll(&fdsete)
	set(&fdsetRead, d.READFD)
	// timeout := syscall.Timeval{Sec: 0, Usec: 100}
	timeout := syscall.Timeval{Sec: sleep, Usec: 0}
	v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
	if v == -1 {
		fmt.Println("-1 : ", err)
		// hangup ?!
		return -2
	}
	if v == 0 {
		// timeout
		return -1
	}
	// var buffer []byte   -- 0 byte buffer.  doh!
	buffer := make([]byte, 1)
	r, err := syscall.Read(d.READFD, buffer)
	if r != 1 {
		fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err)
		// hangup
		return -2
	}
	return int(buffer[0])
}

func (d *Door) Getch() int {
	// var fdsetRead, fdsetWrite, fdsete syscall.FdSet
	var fdsetRead syscall.FdSet
	// fdsetWrite := syscall.FdSet
	clearAll(&fdsetRead)
	// clearAll(&fdsetWrite)
	// clearAll(&fdsete)
	set(&fdsetRead, d.READFD)
	// timeout := syscall.Timeval{Sec: 0, Usec: 100}
	timeout := syscall.Timeval{Sec: 120, Usec: 0}
	v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
	if v == -1 {
		fmt.Println("-1 : ", err)
		// hangup ?!
		return -2
	}
	if v == 0 {
		// timeout
		return -1
	}
	// var buffer []byte   -- 0 byte buffer.  doh!
	buffer := make([]byte, 1)
	r, err := syscall.Read(d.READFD, buffer)
	if r != 1 {
		fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err)
		// hangup
		return -2
	}
	return int(buffer[0])
}

/*
func sleep_key(config *DropfileConfig, secs int) int {
	// var fdsetRead, fdsetWrite, fdsete syscall.FdSet
	var fdsetRead = syscall.FdSet{}
	// fdsetWrite := syscall.FdSet
	clearAll(&fdsetRead)
	// clearAll(&fdsetWrite)
	// clearAll(&fdsete)
	set(&fdsetRead, config.comm_handle)
	timeout := syscall.Timeval{Sec: int64(secs), Usec: 0}
	// v, err := syscall.Select(config.comm_handle+1, &fdsetRead, &fdsetWrite, &fdsete, &timeout)
	v, err := syscall.Select(config.comm_handle+1, &fdsetRead, nil, nil, &timeout)
	fmt.Println("v:", v, "err:", err)
	if v == -1 {
		fmt.Println("-1 : ", err)
		// hangup ?!
		return -2
	}
	if v == 0 {
		// timeout
		return -1
	}
	// var buffer []byte
	buffer := make([]byte, 1)
	// var buffer [1]byte

	r, err := syscall.Read(config.comm_handle, buffer)
	if r != 1 {
		fmt.Printf("Read said ready, but didn't read a character %d %v ?\n", r, err)
		// hangup
		return -2
	}
	return int(buffer[0])
}
*/

/*
func main() {
	fmt.Println("doorgo")
	var dropfile string

	flag.StringVar(&dropfile, "dropfile", "", "Dropfile to use")
	flag.Parse()

	if len(dropfile) == 0 {
		flag.PrintDefaults()
		os.Exit(2)
	}
	fmt.Printf("Loading: %s\n", dropfile)

	var config DropfileConfig
	read_dropfile(dropfile, &config)

	fmt.Printf("BBS %s, User %s / Handle %s\n", config.BBSID, config.real_name, config.handle)
	message := "Welcome BBS User!\n\r"
	// buffer := []byte(message)
	// n, err := syscall.Write(config.comm_handle, buffer)
	write(message, &config)

	write("Press a key...", &config)
	key := sleep_key(&config, 20)
	write("\n\r", &config)
	message = fmt.Sprintf("Key %d / %x\n\r", key, key)
	write(message, &config)

	write("\n\rReturning to BBS.\n\r", &config)
	fmt.Println("Done.")
	// fmt.Println(n, err)
}
*/