package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

const (
	connHost = "0.0.0.0"
	connPort = "8080"
	connType = "tcp"
)

func main() {
	var port int
	var drain int
	var cp437 bool

	flag.IntVar(&port, "p", 0, "Port number to listen on")
	flag.IntVar(&drain, "d", 2, "Drain seconds")
	flag.BoolVar(&cp437, "c", false, "Force CP437 translation")
	flag.Parse()

	if port == 0 && flag.NArg() != 1 {
		fmt.Println("I need a Port and a commandline to execute.")
		flag.PrintDefaults()
		os.Exit(2)
	}

	fmt.Println("Starting " + connType + " server on " + connHost + ":" + strconv.Itoa(port))
	l, err := net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(port))
	if err != nil {
		log.Println("Error listening:", err)
		os.Exit(1)
	}
	defer l.Close()

	for {
		var c net.Conn
		var err error

		c, err = l.Accept()
		if err != nil {
			log.Println("Error connecting:", err)
			return
		}
		go Connection(c, drain, flag.Arg(0), cp437)
	}
}

func Conn_to_File(conn net.Conn) *os.File {
	var tcpconn *net.TCPConn = conn.(*net.TCPConn)

	// This creates a duplicate fd, but once closed -- the fd gets reused!
	var conn_file *os.File
	// var err error
	conn_file, _ = tcpconn.File()

	return conn_file
}

func ReadFrom_WriteTo(read net.Conn, write net.Conn, closed *bool) {
	var buff []byte = make([]byte, 128)
	var n int
	var err error

	defer func() {
		read.Close()
		write.Close()
		if !*closed {
			log.Println("*Closed*")
			*closed = true
		}
	}()

	for {
		n, err = read.Read(buff)
		if err != nil {
			return
		}
		n, err = write.Write(buff[:n])
		if err != nil {
			return
		}
	}
}

func ReadFrom_WriteToCP437(read net.Conn, write net.Conn, closed *bool) {
	var buff []byte = make([]byte, 128)
	var n int
	var err error

	defer func() {
		read.Close()
		write.Close()
		if !*closed {
			log.Println("*Closed*")
			*closed = true
		}
	}()

	for {
		n, err = read.Read(buff)
		if err != nil {
			return
		}
		var line = string(buff[:n])

		// This does convert everything to unicode
		// syncterm doesn't like it (because it doesn't understand
		// unicode!)
		// The "door" detects CP437 (not unicode)

		// This would allow a CP437 door to run as unicode.
		n, err = write.Write([]byte(CP437_to_Unicode(line)))
		if err != nil {
			return
		}
	}
}

func StartProxy(live net.Conn, monitor net.Conn, closed *bool, cp437 bool) {
	go ReadFrom_WriteTo(live, monitor, closed)

	if cp437 {
		go ReadFrom_WriteToCP437(monitor, live, closed)
	} else {
		go ReadFrom_WriteTo(monitor, live, closed)
	}
}

/*
	Read from live, write to server.
	Read from server, write to live.
*/
func setup_monitor(live net.Conn, closed *bool, cp437 bool) (monitor net.Conn) {
	var err error
	var tempsock net.Listener

	tempsock, err = net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}

	// I only need address for making the connection.
	// Get address of listening socket
	var address string
	address = tempsock.Addr().String()

	monitor, err = net.Dial("tcp", address)

	if err != nil {
		panic(err)
	}

	var server net.Conn
	server, err = tempsock.Accept()
	if err != nil {
		panic(err)
	}
	tempsock.Close()

	*closed = false
	// monitor established - forward live <-> monitor
	go StartProxy(live, server, closed, cp437)

	return monitor
}

func Connection(conn net.Conn, drain int, cmd string, cp437 bool) {
	log.Println("Client " + conn.RemoteAddr().String() + " connected.")

	// Configure telnet connection to work
	// local echo off, handle CRNL.
	conn.Write([]byte("\xff\xfb\x01\xff\xfb\x03\xff\xfd\x10"))
	Drain(conn, drain)

	var closed bool
	var proxy net.Conn
	proxy = setup_monitor(conn, &closed, cp437)

	// Write out dropfile
	var conn_file *os.File = Conn_to_File(proxy)
	var handle int = 3
	var err error
	var fp *os.File
	fp, err = os.Create("door32.sys")
	if err != nil {
		log.Println("os.Create:", err)
		return
	}

	fmt.Fprintf(fp, "2\n%d\n38400\nFake Door32\n1\nBugz Laundry\nBugz\n100\n120\n1\n1\n", handle)
	fp.Close()

	var parts []string = strings.Split(cmd, " ")
	var Exe *exec.Cmd = exec.Command(parts[0], parts[1:]...)
	Exe.ExtraFiles = make([]*os.File, 1)
	Exe.ExtraFiles[0] = conn_file

	Exe.Stderr = os.Stderr
	Exe.Stdout = os.Stdout

	err = Exe.Start()
	if err != nil {
		log.Println("exec.Cmd.Start():", err)
		return
	}
	log.Println("Door running..." + conn.RemoteAddr().String())
	// Add a timeout here - to make sure the door isn't hung.
	// Is there a way to detect if the conn is disconnected?

	err = Exe.Wait()
	if err != nil {
		log.Println("exec.Cmd.Wait():", err)
		return
	}

	if closed {
		log.Println("Closed!")
	}
	log.Println("Door ended..." + conn.RemoteAddr().String())
	conn_file.Close()
	proxy.Close()

	conn.Write([]byte("\r\nWelcome back...\r\n"))
	conn.Close()
}

func Drain(conn net.Conn, drain int) {
	conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(drain)))
	var buff []byte = make([]byte, 32)
	var n int
	var err error
	n, err = conn.Read(buff)
	if n > 0 {
		log.Printf("Drained %d bytes [%#v].\n", n, buff[:n])
	}
	if err != nil {
		log.Println("Drain:", err)
	}
	conn.SetReadDeadline(time.Time{})
}