package flags

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"strings"
)

// Parse command-line parameters, test input, store them in the Controller.
func (flags *Flags) ParseFlags() {

	// Define parameters
	flag.StringVar(&flags.Controller.Row1, "row1", "000000000", "1st row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row2, "row2", "000000000", "2nd row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row3, "row3", "000000000", "4rd row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row4, "row4", "000000000", "4th row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row5, "row5", "000000000", "5th row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row6, "row6", "000000000", "6th row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row7, "row7", "000000000", "7th row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row8, "row8", "000000000", "8th row of the sudoku puzzle.")
	flag.StringVar(&flags.Controller.Row9, "row9", "000000000", "9th row of the sudoku puzzle.")
	flag.IntVar(&flags.Controller.NumCPUs, "numcpu", runtime.NumCPU(), "Number of CPU cores to assign to this task.")
	flag.IntVar(&flags.Controller.Split, "split", 1, "Split the tasks in n parts. This depends on the availability of the first row.")
	flag.IntVar(&flags.Controller.Part, "part", 1, "Process part x in n parts. Cannot be lower than 1, or higher than specified in split.")
	flag.StringVar(&flags.Controller.Output, "output", "human", "Type of output. 'human' for human readable. 'flat' for flat as stored in memory output.")

	// Parse the flags
	flag.Parse()

	// Process any changes to the CPU usage.
	if flags.Controller.NumCPUs <= 0 {
		log.Printf("ERROR: Number of CPU cores must be 1 or higher.\n\n")
		flags.printUsage()
		os.Exit(1)
	}

	if flags.Controller.NumCPUs != runtime.NumCPU() {
		runtime.GOMAXPROCS(flags.Controller.NumCPUs)
	}

	// Process rows
	if flags.Controller.Row1 == "000000000" || flags.Controller.Row2 == "000000000" || flags.Controller.Row3 == "000000000" || flags.Controller.Row4 == "000000000" || flags.Controller.Row5 == "000000000" || flags.Controller.Row6 == "000000000" || flags.Controller.Row7 == "000000000" || flags.Controller.Row8 == "000000000" || flags.Controller.Row9 == "000000000" {
		log.Printf("ERROR: All parameters must be entered.\n\n")
		flags.printUsage()
		os.Exit(1)
	}

	// Validate the row (never trust user input)
	flags.validateRow("row1", flags.Controller.Row1)
	flags.validateRow("row2", flags.Controller.Row2)
	flags.validateRow("row3", flags.Controller.Row3)
	flags.validateRow("row4", flags.Controller.Row4)
	flags.validateRow("row5", flags.Controller.Row5)
	flags.validateRow("row6", flags.Controller.Row6)
	flags.validateRow("row7", flags.Controller.Row7)
	flags.validateRow("row8", flags.Controller.Row8)
	flags.validateRow("row9", flags.Controller.Row9)

	// Process workload splitting
	// Ensure split and part are 1 or higher
	if flags.Controller.Split <= 0 || flags.Controller.Part <= 0 {
		log.Printf("ERROR: '-split' and '-part' need to be 1 or higher.\n")
		flags.printUsage()
		os.Exit(1)
	}

	// Ensure part is between 1 and split
	if flags.Controller.Part > flags.Controller.Split {
		log.Printf("ERROR: '-part' cannot be bigger than `-split`.\n")
		flags.printUsage()
		os.Exit(1)
	}

	// Process output selection
	flags.Controller.Output = strings.ToLower(flags.Controller.Output)
	if flags.Controller.Output != "human" && flags.Controller.Output != "flat" {
		log.Printf("ERROR: Invalid output, can only be 'human' or 'flat'.\n")
		flags.printUsage()
		os.Exit(1)
	}

}

// Validate if a row is properly set.
// This check for:
// - Correct length
// - Correct numbers
// - Numbers only present once
func (flags *Flags) validateRow(name string, row string) {

	var found bool
	var double bool
	count := make(map[rune]int)

	// 1. Make sure the row is 9 in length
	if len(row) != 9 {
		log.Printf("ERROR: Invalid length of %s (%s), must be 9 numbers\n\n", name, row)
		flags.printUsage()
		os.Exit(1)
	}

	// 2. Ensure all digits are numbers
	for _, value := range row {
		found = flags.validChar(value)
	}

	if !found {
		log.Printf("ERROR: Invalid character of %s (%s), must be 9 numbers\n\n", name, row)
		flags.printUsage()
		os.Exit(1)
	}

	// 3. Ensure all digits (except zero) are there only once
	for _, digits := range row {
		count[digits] = count[digits] + 1
	}

	for key, value := range count {
		if value > 1 && key != 48 {
			double = true
		}
	}

	if double {
		log.Printf("ERROR: Double character of %s (%s), numbers between 1 and 9 may only be entered once\n\n", name, row)
		flags.printUsage()
		os.Exit(1)
	}

}

// Validate if the char provided is 0-9
func (flags *Flags) validChar(char rune) (valid bool) {
	decvals := [10]int{48, 49, 50, 51, 52, 53, 54, 55, 56, 57}

	for _, value := range decvals {
		if char == rune(value) {
			valid = true
		}
	}

	return
}

// Print help information for the end-user
func (flags *Flags) printUsage() {
	fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(flag.CommandLine.Output(), "\nPut every row of a Sudoku puzzle as paramters.\nUse '0' for what is currently blank in the puzzle you wish to solve.\n\n")
	fmt.Fprintf(flag.CommandLine.Output(), "Example: %s -row1 ... -row2 ... -row3 ... (etc)\n\n", os.Args[0])
	flag.PrintDefaults()
}