const VALUES = "123456789"
const UNKNOWN = '.'
const SUGESTION_DELAY = 60000 //ms

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 selectedValue = ""
let history = []
let accessKeyModifiers = "AccessKey+"
let penStyle = "ink-pen"
let highlighting = false

window.onload = function() {
    let rowId = 0
    for (let row of grid.getElementsByTagName('tr')) {
        let columnId = 0
        for (let box of row.getElementsByTagName('input')) {
            let regionId = rowId - rowId%3 + Math.floor(columnId/3)
            if (!box.disabled) {
                box.onfocus = onfocus
                box.oninput = oninput
                box.onblur = onblur
                box.onclick = onclick
                box.previousPlaceholder = ""
            }
            box.oncontextmenu = oncontextmenu
            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++
    }
    
    let savedGame = localStorage[location.href]
    if (savedGame) {
        boxes.forEach((box, i) => {
            if (!box.disabled && savedGame[i] != UNKNOWN) {
                box.value = savedGame[i]
                box.previousValue = savedGame[i]
            }
        })
    }
    
    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)
        searchCandidatesOf(box)
    })
    
    if (/Win/.test(navigator.platform) || /Linux/.test(navigator.platform)) accessKeyModifiers = "Alt+Maj+"
    else if (/Mac/.test(navigator.platform)) accessKeyModifiers = "⌃⌥"
    for(node of document.querySelectorAll("*[accesskey]")) {
        node.title += " [" + (node.accessKeyLabel || accessKeyModifiers + node.accessKey) + "]"
    }
    
    enableButtons()
    highlightAndTab()
    
    document.onclick = function (event) {
        contextMenu.style.display = "none"
    }
    suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY)
    
    if ("serviceWorker" in navigator) {
        navigator.serviceWorker.register("service-worker.js")
    }
}

function searchCandidatesOf(box) {
    box.candidates = new Set(VALUES)
    box.neighbourhood.forEach(neighbour => box.candidates.delete(neighbour.value))
    if (!box.disabled) {
        switch (box.candidates.size) {
            case 0:
                box.title = "Aucune possibilité !"
            break
            case 1:
                box.title = "1 possibilité [Clic-droit]"
            break
            default:
                box.title = box.candidates.size + " possibilités [Clic-droit]"
        }
    }
}

function onclick() {
    if (selectedValue) {
        if (penStyle == "ink-pen") {
            this.value = selectedValue
        } else {
            if (!this.value.includes(selectedValue))
                this.value += selectedValue
        }
        oninput.apply(this)
    }
}

function onfocus() {
    if (penStyle == "pencil" && this.value == "") {
        this.value = this.placeholder
        this.placeholder = ""
        this.classList.add("pencil")
    } else {
        this.select()
    }
}

function oninput() {
    history.push({box: this, value: this.previousValue, placeholder: this.previousPlaceholder})
    this.previousValue = this.value
    this.previousPlaceholder = this.placeholder
    undoButton.disabled = false
    if (penStyle != "pencil") {
        refresh(this)
    }
}

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

function refresh(box) {
    localStorage[location.href] = boxes.map(box => box.value || ".").join("")
    
    box.neighbourhood.concat([box]).forEach(neighbour => {
        searchCandidatesOf(neighbour)
        neighbour.setCustomValidity("")
        neighbour.required = false
    })
    
    enableButtons()
    highlightAndTab()
    
    for (neighbour1 of box.neighbourhood) {
        if (neighbour1.value.length == 1) {
            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) {
                        for (neighbour of [neighbour1, neighbour2]) {
                            neighbour.setCustomValidity(`Il y a un autre ${neighbour.value} dans cette ${area.name}.`)
                        }
                    }
        } else {
            if (neighbour1.candidates.size == 0) {
                neighbour1.setCustomValidity("Aucun chiffre possible !")
                neighbour1.required = true
            }
        }
    }
            
    if (box.form.checkValidity()) { // Correct grid
        if (boxes.filter(box => box.value == "").length == 0) {
            setTimeout(() => alert(`Bravo ! Vous avez résolu la grille.`), 0)
        } else {
            if (suggestionTimer) clearTimeout(suggestionTimer)
            suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY)
        }
    } else { // Errors on grid
        box.form.reportValidity()
        box.select()
    }
}

function onblur() {
    if (this.classList.contains("pencil")) {
        this.placeholder = this.value
        this.value = ""
        this.classList.remove("pencil")
    }
}

function enableButtons() {
    for (button of buttons.getElementsByTagName("button")) {
        if (boxes.filter(box => box.value == "").some(box => box.candidates.has(button.textContent))) {
            button.disabled = false
            if (button.previousTitle) {
                button.title = button.previousTitle
                button.previousTitle = null
            }
        } else {
            button.disabled = true
            button.previousTitle = button.title
            button.title = "Tous les " + button.textContent + " sont posés"
            if (selectedValue == button.textContent) selectedValue = ""
        }
    }
}

function highlight(value) {
    if (value == selectedValue) {
        selectedValue = ""
    } else {
        selectedValue = value
    }
    for (button of buttons.getElementsByTagName("button")) {
        if (button.textContent == selectedValue) button.classList.add("pressed")
        else button.classList.remove("pressed")
    }
    highlightAndTab()
    boxes.filter(box => box.value == "" && box.tabIndex == 0)[0].focus()
}

function highlightAndTab() {
    if (highlighting && selectedValue) {
        boxes.forEach(box => {
            if (box.value == selectedValue) {
                box.classList.add("same-value")
                box.tabIndex = -1
            }
            else { 
                box.classList.remove("same-value")
                if (box.candidates.has(selectedValue)) {
                    box.classList.remove("forbidden-value")
                    box.tabIndex = 0
                } else {
                    box.classList.add("forbidden-value")
                    box.tabIndex = -1
                }
            }
        })
    } else {
        boxes.forEach(box => {
            box.classList.remove("same-value", "forbidden-value")
            box.tabIndex = 0
        })
    }
}

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)[0].placeholder = "💡"
    } else {
        clearTimeout(suggestionTimer)
        suggestionTimer = null
    }
}

function oncontextmenu(event) {
    event.preventDefault()
    while (contextMenu.firstChild) contextMenu.firstChild.remove()
    const box = event.target
    if (box.candidates.size) {
        candidatesArray = Array.from(box.candidates).sort().forEach(candidate => {
            li = document.createElement("li")
            li.innerText = candidate
            li.onclick = function (event) {
                contextMenu.style.display = "none"
                onfocus.apply(box)
                box.value = event.target.innerText
                oninput.apply(box)
                onblur.apply(box)
            }
            contextMenu.appendChild(li)
        })
    } else {
        li = document.createElement("li")
        li.innerText = "Aucune possibilité !"
        li.classList.add("error")
        contextMenu.appendChild(li)
    }
    contextMenu.style.left = `${event.pageX}px`
    contextMenu.style.top = `${event.pageY}px`
    contextMenu.style.display = "block"
    return false
}

function useInkPen() {
    inkPenButton.classList.add("pressed")
    pencilButton.classList.remove("pressed")
    penStyle = "ink-pen"
}

function usePencil() {
    pencilButton.classList.add("pressed")
    inkPenButton.classList.remove("pressed")
    penStyle = "pencil"
}

function erase(someBoxes) {
    for (box of someBoxes) {
        box.value = ""
        box.placeholder = ""
        searchCandidatesOf(box)
        refresh(box)
    }
    enableButtons()
    highlightAndTab()
}

function erasePencil() {
    if (confirm("Effacer les chiffres écrits au crayon ?")) {
        boxes.filter(box => !box.disabled).forEach(box => {
            box.placeholder = ""
        })
    }
}

function eraseAll() {
    if (confirm("Effacer tous les chiffres écrits au crayon et au stylo ?")) {
        boxes.filter(box => !box.disabled).forEach(box => {
            box.value = ""
            box.placeholder = ""
            box.setCustomValidity("")
            box.required = false
        })
        boxes.forEach(searchCandidatesOf)
        enableButtons()
        highlightAndTab()
    }
}

function toggleHighlighting() {
    highlighting = !highlighting
    if (highlighting) {
        highlighterButton.classList.add("pressed")
    } else {
        highlighterButton.classList.remove("pressed")
    }
    highlightAndTab()
}