package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"path/filepath"
	"strconv"

	// __IMPORTS__
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/websocket/v2"
)

type CLIFlagOptions struct {
	Config    string
	Name      string
	Port      int
	Watch     bool
	ModuleDev bool
}

type Options struct {
	Name            string
	Root            string
	Port            int
	Ext             string
	AppDir          string
	AppPath         string
	OutDir          string
	OutPath         string
	CachePath       string
	CacheAppPath    string
	CacheClientPath string
	DepGraphPath    string
	Dev             bool
	ModuleDev       bool
	Public          string
	Styles          string
}

// __EMBEDS__

func main() {
	var opts Options

	json.Unmarshal([]byte(os.Args[1]), &opts)

	// https://docs.gofiber.io/api/fiber#config
	app := fiber.New(fiber.Config{
		AppName: "Esper Framework",
	})

	// TODO: Live Reload (Development)
	if opts.Dev == true {
		app.Use("/ws", func(ctx *fiber.Ctx) error {
			// IsWebSocketUpgrade returns true if the client
			// requested upgrade to the WebSocket protocol.
			if websocket.IsWebSocketUpgrade(ctx) {
				ctx.Locals("allowed", true)
				return ctx.Next()
			}
			return fiber.ErrUpgradeRequired
		})

		app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {
			// c.Locals is added to the *websocket.Conn
			// log.Println(c.Locals("allowed"))  // true
			// log.Println(c.Params("id"))       // reload

			var (
				mt  int
				msg []byte
				err error
			)

			for {
				if mt, msg, err = c.ReadMessage(); err != nil {
					fmt.Printf("Websocket set to %s\n", c.Params("id"))
					break
				}

				fmt.Printf("WebSocketMessage: %s", msg)

				if err = c.WriteMessage(mt, msg); err != nil {
					fmt.Println("WebSocketError:", err)
					break
				}
			}
		}))
	}

	// __ROUTES__
	app.Static("/", filepath.Join(opts.OutPath, "client"))

	app.Use(func(ctx *fiber.Ctx) error {

		ctx.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
		ctx.SendStatus(404)
		return ctx.SendString(string(readFile(opts.CacheClientPath, "__not-found.html")))
	})

	log.Fatal(app.Listen(fmt.Sprintf(":%s", setPort(opts.Port))))
}

func readFile(clientPath string, route string) []byte {
	file, err := ioutil.ReadFile(filepath.Join(clientPath, route))
	if err != nil {
		log.Fatalf("Failed to read HTML file at %s", filepath.Join(clientPath, route))
	}
	return file
}

func setPort(port int) string {
	_, err := strconv.ParseUint(fmt.Sprint(port), 10, 16)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Invalid port %d: %s", port, err)
		os.Exit(1)
	}

	i := 0
	for {
		busy := false
		if i > 100 {
			log.Printf("No open ports available")
			break
		}

		busy = checkPort(&port)

		if !busy {
			break
		}

		i++
	}

	return fmt.Sprint(port)
}

var ran = false

func checkPort(port *int) bool {
	defer func() {
		if !ran {
			fmt.Fprintf(os.Stderr, "Trying port %d\n\n", *port)
		}
		ran = true
	}()

	ln, err := net.Listen("tcp", ":"+fmt.Sprint(*port))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Port %s is busy...\n", fmt.Sprint(*port))
		*port++
		ran = false
		return true
	}

	err = ln.Close()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to terminal process on port %d: %s\n", *port, err)
		os.Exit(1)
	}

	ran = true

	return false
}
