package solver

import (
	"log"
	"strconv"
	"time"
)

func (solver *Solver) PopulateBlocks() {

	defer solver.timeTrack(time.Now(), "Populated blocks")
	log.Println("Populating blocks")

	solver.findBlocks(&solver.Row1, &solver.row1s)
	solver.findBlocks(&solver.Row2, &solver.row2s)
	solver.findBlocks(&solver.Row3, &solver.row3s)
	solver.findBlocks(&solver.Row4, &solver.row4s)
	solver.findBlocks(&solver.Row5, &solver.row5s)
	solver.findBlocks(&solver.Row6, &solver.row6s)
	solver.findBlocks(&solver.Row7, &solver.row7s)
	solver.findBlocks(&solver.Row8, &solver.row8s)
	solver.findBlocks(&solver.Row9, &solver.row9s)

	// This calculates and stores the total number of solutions to validate.
	solver.Iter = int64(len(solver.row1s)) * int64(len(solver.row2s)) * int64(len(solver.row3s)) * int64(len(solver.row4s)) * int64(len(solver.row5s)) * int64(len(solver.row6s)) * int64(len(solver.row7s)) * int64(len(solver.row8s)) * int64(len(solver.row9s))

}

func (solver *Solver) findBlocks(row *string, rows *[]int) {
	// Declare selection
	var selection []int
	var currBlocks []int
	funcRow := *row

	for letter := range funcRow {

		if len(selection) == 0 {
			currBlocks = solver.blocks
		} else {
			currBlocks = selection
			selection = nil
		}

		for _, block := range currBlocks {

			currRow := strconv.Itoa(block)

			if funcRow[letter] == currRow[letter] {
				foundRow, _ := strconv.Atoi(currRow)
				selection = append(selection, foundRow)
			}
			if funcRow[letter] == '0' {
				foundRow, _ := strconv.Atoi(currRow)
				selection = append(selection, foundRow)
			}

		} // End for-loop

	} // End for-loop

	*rows = selection
}

func (solver *Solver) CheckCombinations() {
	for rows1Index := range solver.row1s {
		for rows2Index := range solver.row2s {
			for rows3Index := range solver.row3s {
				for rows4Index := range solver.row4s {
					for rows5Index := range solver.row5s {
						for rows6Index := range solver.row6s {
							for rows7Index := range solver.row7s {
								for rows8Index := range solver.row8s {
									for rows9Index := range solver.row9s {
										go solver.validator(rows1Index, rows2Index, rows3Index, rows4Index, rows5Index, rows6Index, rows7Index, rows8Index, rows9Index)
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

func (solver *Solver) validator(rows1Index int, rows2Index int, rows3Index int, rows4Index int, rows5Index int, rows6Index int, rows7Index int, rows8Index int, rows9Index int) {

	solver.counter.Add(1)

	if solver.validateCombination(solver.row1s[rows1Index], solver.row2s[rows2Index], solver.row3s[rows3Index], solver.row4s[rows4Index], solver.row5s[rows5Index], solver.row6s[rows6Index], solver.row7s[rows7Index], solver.row8s[rows8Index], solver.row9s[rows9Index]) {
		solver.solutions = append(solver.solutions, solver.renderCombination(solver.row1s[rows1Index], solver.row2s[rows2Index], solver.row3s[rows3Index], solver.row4s[rows4Index], solver.row5s[rows5Index], solver.row6s[rows6Index], solver.row7s[rows7Index], solver.row8s[rows8Index], solver.row9s[rows9Index]))
	}

}

func (solver *Solver) Tracker() {

	// Add time tracking
	defer solver.timeTrack(time.Now(), "Validated solutions")
	log.Println("Validating solutions")

	// Determine if the main-loop is done
	var done bool

	// Tracking progress in percentages
	var percentage float32
	// Tracking progress in validated solutions
	var track int

	// Tracking the rate, starting point
	var rateStart int64
	// Tracking the rate, difference between previous iterations
	var rateDiff int64

	// Tracking duration
	var timerStart = time.Now()

	// Estimation how long it will take
	var est_fin string

	// While not needed for rateDiff anymore, it makes estimation calculations more accurate. ☹️
	time.Sleep(time.Second)

	// for solver.Iter != solver.counter { // Start for-loop
	for !done {

		// Determine how far we are.
		percentage = (float32(solver.counter.Load()) / (float32(solver.Iter) / 100))

		// Reset the loop
		rateDiff = solver.counter.Load() - rateStart

		if track <= int(percentage) || rateDiff == 0 { // Start if-statement

			// Make sure something happened, making rateStart the only reliable variable
			if solver.Iter == solver.counter.Load() {
				percentage = 100
				solver.counter.Store(solver.Iter)
				done = true
			}

			timer_elapsed := time.Since(timerStart)
			solver.rates = append(solver.rates, rateDiff)
			rate_avg := solver.calcAVG()

			// Estimate when this is finished
			if rateDiff == 0 {
				est_fin = "N/A"
			} else {
				duration_int := (solver.Iter - solver.counter.Load()) / rate_avg
				duration_string := strconv.Itoa(int(duration_int)) + "s"
				est, err := time.ParseDuration(duration_string)
				if err != nil {
					est_fin = "parse error"
				} else {
					est_fin = est.String()
				}
			}

			// Printing the progress
			log.Println("Processing: " + strconv.Itoa(int(percentage)) + "% (" + strconv.FormatInt(solver.counter.Load(), 10) + "/" + strconv.Itoa(int(solver.Iter)) + "); Rate: " + strconv.FormatInt(rateDiff, 10) + "/sec for " + timer_elapsed.String() + "; Time left (est.): " + est_fin)

			// After we are done printing, exit this for-loop
			if percentage == 100 {
				break
			}

			// Wrap up the loop or break
			if int(percentage) > track {
				track = int(percentage)
			} else {
				track = track + 1
			}

			timerStart = time.Now()

		}

		// Resert the rate counter
		rateStart = solver.counter.Load()

		// Sleep for a second
		if solver.Iter != solver.counter.Load() {
			time.Sleep(1 * time.Second)
		}
	} // End for-loop

}

func (solver *Solver) validateCombination(row1 int, row2 int, row3 int, row4 int, row5 int, row6 int, row7 int, row8 int, row9 int) (retval bool) {
	retval = true

	row1s := strconv.Itoa(row1)
	row2s := strconv.Itoa(row2)
	row3s := strconv.Itoa(row3)
	row4s := strconv.Itoa(row4)
	row5s := strconv.Itoa(row5)
	row6s := strconv.Itoa(row6)
	row7s := strconv.Itoa(row7)
	row8s := strconv.Itoa(row8)
	row9s := strconv.Itoa(row9)

	for index := range 9 {
		if row1s[index] == row2s[index] || row1s[index] == row3s[index] || row1s[index] == row4s[index] || row1s[index] == row5s[index] || row1s[index] == row6s[index] || row1s[index] == row7s[index] || row1s[index] == row8s[index] || row1s[index] == row9s[index] {
			retval = false
		}

		if row2s[index] == row1s[index] || row2s[index] == row3s[index] || row2s[index] == row4s[index] || row2s[index] == row5s[index] || row2s[index] == row6s[index] || row2s[index] == row7s[index] || row2s[index] == row8s[index] || row2s[index] == row9s[index] {
			retval = false
		}

		if row3s[index] == row1s[index] || row3s[index] == row2s[index] || row3s[index] == row4s[index] || row3s[index] == row5s[index] || row3s[index] == row6s[index] || row3s[index] == row7s[index] || row3s[index] == row8s[index] || row3s[index] == row9s[index] {
			retval = false
		}

		if row4s[index] == row1s[index] || row4s[index] == row2s[index] || row4s[index] == row3s[index] || row4s[index] == row5s[index] || row4s[index] == row6s[index] || row4s[index] == row7s[index] || row4s[index] == row8s[index] || row4s[index] == row9s[index] {
			retval = false
		}

		if row5s[index] == row1s[index] || row5s[index] == row2s[index] || row5s[index] == row3s[index] || row5s[index] == row4s[index] || row5s[index] == row6s[index] || row5s[index] == row7s[index] || row5s[index] == row8s[index] || row5s[index] == row9s[index] {
			retval = false
		}

		if row6s[index] == row1s[index] || row6s[index] == row2s[index] || row6s[index] == row3s[index] || row6s[index] == row4s[index] || row6s[index] == row5s[index] || row6s[index] == row7s[index] || row6s[index] == row8s[index] || row6s[index] == row9s[index] {
			retval = false
		}

		if row7s[index] == row1s[index] || row7s[index] == row2s[index] || row7s[index] == row3s[index] || row7s[index] == row4s[index] || row5s[index] == row6s[index] || row7s[index] == row6s[index] || row7s[index] == row8s[index] || row7s[index] == row9s[index] {
			retval = false
		}

		if row8s[index] == row1s[index] || row8s[index] == row2s[index] || row8s[index] == row3s[index] || row8s[index] == row4s[index] || row8s[index] == row5s[index] || row8s[index] == row6s[index] || row8s[index] == row7s[index] || row8s[index] == row9s[index] {
			retval = false
		}

		if row9s[index] == row1s[index] || row9s[index] == row2s[index] || row9s[index] == row3s[index] || row9s[index] == row4s[index] || row9s[index] == row5s[index] || row9s[index] == row6s[index] || row9s[index] == row7s[index] || row9s[index] == row8s[index] {
			retval = false
		}

	}

	return retval
}

func (solver *Solver) calcAVG() (avg int64) {
	var avgSum int64

	for _, value := range solver.rates {
		avgSum += value
	}

	avg = avgSum / int64(len(solver.rates))

	return
}