package main

import (
	"errors"
	"fmt"
	"image/color"
	"maze-solver/io/reader"
	"maze-solver/io/writer"
	"maze-solver/maze"
	"maze-solver/maze/parser"
	"maze-solver/solver"
	"maze-solver/utils"
	"maze-solver/visualizer"
	"os"
	"strings"
	"sync"

	"github.com/akamensky/argparse"
	"github.com/mazznoer/colorgrad"
)

func main() {
	readerFactory, writerFactory, solverFactory, visFactory, ok := parse_arguments()

	if !ok {
		return
	}

	defer utils.Timer("TOTAL", 1)()
	reader := readerFactory.Get()

	m, err := parser.Parse(reader)
	utils.Check(err, "Couldn't read maze")

	var solved *maze.SolvedMaze
	if *visFactory.Type != "" {
		solved_chan := make(chan *maze.SolvedMaze, 3)
		solver := solverFactory.Get(solved_chan)
		vis := visFactory.Get()

		vis.Init(m)

		var wg sync.WaitGroup
		wg.Add(2)

		lets_go := make(chan bool, 1)

		go func() {
			if <-lets_go {
				solved = solver.Solve(m)
			}
			close(solved_chan)
			wg.Done()
		}()
		go func() {
			vis.Visualize(solved_chan)
			wg.Done()
		}()
		vis.Run(lets_go)
		wg.Wait()
	} else {
		solver := solverFactory.Get(nil)
		solved = solver.Solve(m)
	}
	if solved == nil { // cuz maybe with the window visualization, the user pressed "no"
		return
	}
	writer := writerFactory.Get(solved)

	err = writer.Write()
	utils.Check(err, "Couldn't write solved maze")
}

func parse_arguments() (*reader.ReaderFactory, *writer.WriterFactory, *solver.SolverFactory, *visualizer.VisualizerFactory, bool) {
	argparser := argparse.NewParser("maze-solver", "Solves the given maze (insane, right? who would've guessed?)")

	var verboseLevel *int = argparser.FlagCounter("v", "verbose", &argparse.Options{
		Help: `Verbose level of the solver
        0: nothing printed to stdout
        1: print the total time taken by the solver (time of the main() function)
        2: prints the time the solving algorithm took to run
        3: prints the time taken by each section (reader, solving algorithm, writer)`,
	})

	readerFactory := reader.ReaderFactory{}
	writerFactory := writer.WriterFactory{}
	solverFactory := solver.SolverFactory{}
	visFactory := visualizer.VisualizerFactory{}

	readerFactory.Type = reader.TYPES[".png"]
	readerFactory.Filename = argparser.String("i", "input", &argparse.Options{
		Help:    "Input file",
		Default: "maze.png",
		Validate: func(args []string) error {
			var ok bool
			extension := args[0][len(args[0])-4:]
			readerFactory.Type, ok = reader.TYPES[extension]
			if ok {
				return nil
			} else {
				return errors.New(fmt.Sprintf("Filetype not recognized %q", extension))
			}
		},
	})

	writerFactory.Type = writer.TYPES[".png"]
	writerFactory.Filename = argparser.String("o", "output", &argparse.Options{
		Help:    "Input file",
		Default: "maze_sol.png",
		Validate: func(args []string) error {
			var ok bool
			extension := args[0][len(args[0])-4:]
			writerFactory.Type, ok = writer.TYPES[extension]
			if ok {
				return nil
			} else {
				return errors.New(fmt.Sprintf("Filetype not recognized %q", extension))
			}
		},
	})

	readerFactory.PathChar = argparser.String("", "path-char-in", &argparse.Options{
		Help:    "Character to represent the path in a input text file",
		Default: " ",
		Validate: func(args []string) error {
			if len(args[0]) > 1 {
				return errors.New("Character must a string of length 1")
			}
			return nil
		},
	})

	readerFactory.WallChar = argparser.String("", "wall-char-in", &argparse.Options{
		Help:    "Character to represent the wall in a input text file",
		Default: "#",
		Validate: func(args []string) error {
			if len(args[0]) > 1 {
				return errors.New("Character must a string of length 1")
			}
			return nil
		},
	})

	writerFactory.PathChar = argparser.String("", "path-char-out", &argparse.Options{
		Help:    "Character to represent the path in a output text file",
		Default: " ",
		Validate: func(args []string) error {
			if len(args[0]) > 1 {
				return errors.New("Character must a string of length 1")
			}
			return nil
		},
	})

	writerFactory.WallChar = argparser.String("", "wall-char-out", &argparse.Options{
		Help:    "Character to represent the wall in a output text file",
		Default: "#",
		Validate: func(args []string) error {
			if len(args[0]) > 1 {
				return errors.New("Character must a string of length 1")
			}
			return nil
		},
	})

	cellSizeIn := argparser.Int("", "cell-size-in", &argparse.Options{
		Help:    "Size of a cell (in pixels) for input file of image type",
		Default: 3,
	})

	cellSizeOut := argparser.Int("", "cell-size-out", &argparse.Options{
		Help:    "Size of a cell (in pixels) for output file of image type",
		Default: 3,
	})

	solverFactory.Type = argparser.Selector("a", "algo", solver.TYPES, &argparse.Options{
		Help:    fmt.Sprintf("Algorithm to solve the maze, avaiable options: %s", strings.Join(solver.TYPES, ", ")),
		Default: solver.TYPES[0],
	})

	visFactory.Type = argparser.Selector("", "visualize", visualizer.VIZ_METHODS, &argparse.Options{
		Help:    fmt.Sprintf("Visualizer the progress of the solver, avaiable options: %s. Window will give a live feed of the solver, whereas video creates a video --output with mp4 extension", strings.Join(visualizer.VIZ_METHODS, ", ")),
		Default: "",
	})

	visFactory.Filename = argparser.String("", "video-name", &argparse.Options{
		Help:    "Name of the output file if --visualize is set to 'video'",
		Default: "maze_sol.mp4",
	})

	visFactory.Framerate = argparser.Float("", "video-framerate", &argparse.Options{
		Help:    "Framerate of the video if --visualize is set to 'video'",
		Default: 60.,
	})

	if err := argparser.Parse(os.Args); err != nil {
		fmt.Println(argparser.Usage(err))
		return nil, nil, nil, nil, false
	}
	utils.VERBOSE_LEVEL = *verboseLevel

	readerFactory.CellHeight, readerFactory.CellWidth = cellSizeIn, cellSizeIn
	readerFactory.WallColor = color.RGBA{0, 0, 0, 255}
	readerFactory.PathColor = color.RGBA{255, 255, 255, 255}

	writerFactory.CellHeight, writerFactory.CellWidth = cellSizeOut, cellSizeOut
	writerFactory.WallColor = color.RGBA{0, 0, 0, 255}
	writerFactory.PathColor = color.RGBA{255, 255, 255, 255}
	writerFactory.SolutionGradient = colorgrad.Warm()

	return &readerFactory, &writerFactory, &solverFactory, &visFactory, true
}
