const VALUES = "123456789"

let boxes = []
let rows = Array.from(Array(9), x => [])
let columns = Array.from(Array(9), x => [])
let regions = Array.from(Array(9), x => [])
let suggestionTimer= null
let highlightedValue = ""
let history = []

window.onload = function() {
    let rowId = 0
    for (row of grid.getElementsByTagName('tr')) {
        let columnId = 0
        for (box of row.getElementsByTagName('input')) {
            let regionId = rowId - rowId%3 + Math.floor(columnId/3)
            if (!box.readOnly) {
                box.onfocus = onfocus
                box.oninput = oninput
            }
            box.rowId = rowId
            box.columnId = columnId
            box.regionId = regionId
            boxes.push(box)
            rows[rowId].push(box)
            columns[columnId].push(box)
            regions[regionId].push(box)
            columnId++
        }
        rowId++
    }
    boxes.forEach(box => {
        box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId]))
        box.neighbourhood.delete(box)
        box.neighbourhood = Array.from(box.neighbourhood)
    })
    boxes.forEach(searchCandidatesOf)
    enableButtons()
    boxes.forEach(showCandidatesOn)
    for(box of boxes) {
        if (!box.readOnly) {
            box.focus()
            break
        }
    }
    suggestionTimer = setTimeout(showSuggestion, 30000)
}

function searchCandidatesOf(box) {
    box.candidates = new Set(VALUES)
    box.neighbourhood.forEach(neighbour => box.candidates.delete(neighbour.value))
}

function showCandidatesOn(box) {
    box.required = box.candidates.size == 0
    if (box.value.length) {
        box.title = ""
    } else if (box.candidates.size) {
        const candidatesArray = Array.from(box.candidates).sort()
        box.title = candidatesArray.length ==1 ? candidatesArray[0] : candidatesArray.slice(0, candidatesArray.length-1).join(", ") + " ou " + candidatesArray[candidatesArray.length-1]
    } else {
        box.title = "Aucune valeur possible !"
    }
}

function onfocus() {
	this.previousValue = this.value
	this.select()
}

function oninput() {
	history.push({input: this, value: this.previousValue})
	undoButton.disabled = false
	refresh(this)
}

function refresh(box) {
    box.style.color = colorPicker.value

    box.neighbourhood.concat([box]).forEach(neighbour => {
        searchCandidatesOf(neighbour)
        showCandidatesOn(neighbour)
        neighbour.setCustomValidity("")
    })
    
    for (neighbour1 of box.neighbourhood) {
        neighbour1.setCustomValidity("")
        if (neighbour1.value.length) {
            for (area of [
                {name: "région", neighbours: regions[neighbour1.regionId]},
                {name: "ligne", neighbours: rows[neighbour1.rowId]},
                {name: "colonne", neighbours: columns[neighbour1.columnId]},
            ])
                for (neighbour2 of area.neighbours)
                    if (neighbour2 != neighbour1 && neighbour2.value == neighbour1.value) {
                        neighbour1.setCustomValidity(`Il y a un autre ${neighbour1.value} dans cette ${area.name}.`)
                        neighbour2.setCustomValidity(`Il y a un autre ${neighbour1.value} dans cette ${area.name}.`)
                    }
        }
    }
    
    enableButtons()
    highlightAndTab()

            
    if (box.form.checkValidity()) { // Correct grid
        if (boxes.filter(box => box.value == "").length == 0) {
            alert(`Bravo ! Vous avez résolu la grille.`)
        } else {
            if (suggestionTimer) clearTimeout(suggestionTimer)
            suggestionTimer = setTimeout(showSuggestion, 30000)
        }
    } else { // Errors on grid
        box.select()
        box.reportValidity()
    }
}

function undo() {
	if (history.length) {
		previousState = history.pop()
		previousState.input.value = previousState.value
    	refresh(previousState.input)
    	if (history.length < 1) undoButton.disabled = true
    }
}

function enableButtons() {
    for (button of buttons.getElementsByTagName("button")) {
        if (boxes.filter(box => box.value == "").some(box => box.candidates.has(button.textContent))) {
            button.disabled = false
        } else {
            button.disabled = true
            if (highlightedValue == button.textContent) highlightedValue = ""
        }
    }
}

function moveOn(area, position, direction) {
    if (area.filter(box => box.disabled).length < 9) {
        do {
            position = (position + direction) % 9
        } while (area[position].disabled)
        area[position].focus()
    }
}

function highlight(value) {
    if (value == highlightedValue) {
        highlightedValue = ""
    } else {
        highlightedValue = value
    }
    highlightAndTab()
    boxes.filter(box => box.value == "" && box.tabIndex == 0)[0].focus()
}

function highlightAndTab() {
    if (highlightedValue) {
        boxes.forEach(box => {
            if (box.value == highlightedValue) {
                box.className = "same-value"
                box.tabIndex = -1
            }
            else if (box.candidates.has(highlightedValue)) {
                box.className = ""
                box.tabIndex = 0
            } else {
                box.className = "forbidden-value"
                box.tabIndex = -1
            }
        })
    } else {
        boxes.forEach(box => box.className = "")
    }
}

function shuffle(iterable) {
    array = Array.from(iterable)
    if (array.length > 1) {
        let i, j, tmp
        for (i = array.length - 1; i > 0; i--) {
            j = Math.floor(Math.random() * (i+1))
            tmp = array[i]
            array[i] = array[j]
            array[j] = tmp
        }
    } 
    return array
}

easyFirst = (box1, box2) => box1.candidates.size - box2.candidates.size

function showSuggestion() {
    const emptyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1)
    if (emptyBoxes.length) {
        shuffle(emptyBoxes).placeholder = "!"
    } else {
        clearTimeout(suggestionTimer)
        suggestionTimer = null
    }
}