package main

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"
)

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	addr := ":7765"
	if len(os.Args) > 1 {
		addr = os.Args[1]
	}

	poolSize := 10
	if len(os.Args) > 2 {
		if v, err := strconv.ParseInt(os.Args[2], 10, 64); err != nil {
			log.Fatal(err)
		} else {
			poolSize = int(v)
		}
	}

	log.Printf("listening at %v (workers=%v)", addr, poolSize)

	listener, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		<-ctx.Done()
		if err := listener.Close(); err != nil {
			log.Printf("failed to close listener: %v", err)
		}
	}()

	workers := []*worker{}
	requests := make(chan net.Conn)

	for i := 0; i < poolSize; i++ {
		w := &worker{id: i, r: requests}
		go w.Start(ctx)
		workers = append(workers, w)
	}

	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(time.Microsecond):
			continue
		default:
			log.Printf("entering accept condition")

			conn, err := listener.Accept()
			if err != nil {
				if errors.Is(err, net.ErrClosed) {
					return
				}

				log.Printf("failed to accept connection: %v", err)
				continue
			}

			conn.SetDeadline(time.Now().Add(5 * time.Second))

			log.Printf("accepted connection %v", conn.RemoteAddr())
			requests <- conn
		}
	}
}

type worker struct {
	id int
	r  chan net.Conn
}

func (w *worker) Start(ctx context.Context) {
	log.Printf("[worker %v] starting loop", w.id)

	for {
		select {
		case <-ctx.Done():
			log.Printf("[worker %v] exiting", w.id)
			return
		case conn := <-w.r:
			log.Printf("[worker %v] handling connection %v", w.id, conn.RemoteAddr())

			fmt.Fprintf(conn, "ITS ECHO TIME\n")

			scanner := bufio.NewScanner(conn)
			for scanner.Scan() {
				req := strings.TrimSpace(scanner.Text())

				if req == "BYE" {
					fmt.Fprintf(conn, "BYE BYE NOW\n")
					break
				}

				fmt.Fprintf(conn, "YOU SAID: %v\n", req)
			}

			if err := conn.Close(); err != nil {
				log.Printf("[worker %v] failed to echo: %v", w.id, err)
			}
		}
	}
}
