/**
 * Represents a table object.
 */
class Table {
    /**
     * Initializes a new instance of the Table class.
     * @param {HTMLElement} tableElement - The HTML table element.
     * @param {HTMLElement} rcMenu - The right-click menu element.
     */
    constructor(tableElement, rcMenu) {
        this.tableElement = tableElement
        this.tableBody = tableElement.querySelector("tbody")
        this.tableHead = tableElement.querySelector("thead")
        this.rcMenu = rcMenu

        this.importElement = document.getElementById("import")
        this.exportElement = document.getElementById("export")

        // Initialize right-click menu options
        this.rcAddRow = rcMenu.querySelector("#rcAddRow")
        this.rcDelRow = rcMenu.querySelector("#rcDelRow")
        this.rcAddCol = rcMenu.querySelector("#rcAddCol")
        this.rcDelCol = rcMenu.querySelector("#rcDelCol")

        this.selectedCell = null

        this.addEventListeners(this.rcMenu, this.tableElement)
    }

    addEventListeners() {
        // Hide context menu when mouse button is clicked anywhere on the window
        window.addEventListener('mousedown', (e) => {
            this.rcMenu.style.display = "none"
        })

        // Show context menu when right-clicking on the data table
        this.tableElement.addEventListener('contextmenu', (e) => {
            const pos = {
                x: e.clientX,
                y: e.clientY
            }

            this.handleContextMenu(this.rcMenu, this.tableElement, pos)

            this.selectedCell = e.target

            // Prevent default context menu from appearing
            e.preventDefault()
            e.stopPropagation()

        })

        this.rcAddRow.addEventListener("mousedown", (e) => { this.addRow() })
        this.rcDelRow.addEventListener("mousedown", (e) => { this.delRow() })
        this.rcAddCol.addEventListener("mousedown", (e) => { this.addCol() })
        this.rcDelCol.addEventListener("mousedown", (e) => { this.delCol() })

        this.importElement.addEventListener("input", (event) => {
            let file = this.importElement.files[0]
            let reader = new FileReader()
            reader.onload = (e) => {
                let result = e.target.result

                let rows = result.split("\n")
                let header = rows[0].split(",")
                rows.shift()

                this.selectedCell = this.tableBody.querySelector("#chart_table_0_values_0")

                while (this.tableBody.rows.length > 1)
                    this.delRow()
                
                while (this.tableHead.lastElementChild.childElementCount > header.length)
                    this.delCol()

                while (this.tableHead.lastElementChild.childElementCount < header.length)
                    this.addCol()

                for (let i = 0; i < header.length; i++) {
                    this.tableHead.querySelector("#chart_table_" + i +"_col_name").value = header[i]
                }

                for (let i = 1; i < rows.length - 1; i++)
                    this.addRow()
                for (let i = 0; i < rows.length - 1; i++) {
                    let cells = rows[i].split(",")
                    for (let j = 0; j < header.length; j++) {
                        console.log("#chart_table_" + j + "_values_" + i, +cells[j])
                        this.tableBody.querySelector("#chart_table_" + j + "_values_" + i).value = +cells[j]
                    }
                }
            }
            reader.readAsText(file)
        })

        this.exportElement.addEventListener("input", (e) => {
            let newName = ""
            let fileURL = ""

            let choice = this.exportElement.value
            if (choice === "png") {
                let iframe = document.getElementById("chartDiv")
                let canvas = iframe.contentWindow.document.getElementById("chartCanvas")
                fileURL = canvas.toDataURL("image/png")
                newName = 'chart.png'
            }
            else if (choice === "csv" || choice === "txt") {
                // Variable to store the final csv data
                let csv_data = []
                let rows = document.getElementsByTagName('tr');
                for (let i = 0; i < rows.length; i++) {

                    // Get each column data
                    let cols = rows[i].querySelectorAll('td,th');

                    // Stores each csv row data
                    let csvrow = [];
                    for (let j = 0; j < cols.length; j++) {

                        // Get the text data of each cell of
                        // a row and push it to csvrow
                        csvrow.push(cols[j].querySelector("input").value);
                    }

                    // Combine each column value with comma
                    csv_data.push(csvrow.join(","));
                }
                // Combine each row data with new line character
                csv_data = csv_data.join('\n');

                let CSVFile = new Blob([csv_data], { type: "text/csv" })
                fileURL = window.URL.createObjectURL(CSVFile)
                newName = 'data.' + choice
            }

            let downloadLink = document.createElement("a")
            downloadLink.download = newName
            downloadLink.href = fileURL
            downloadLink.click()
        })
    }

    handleContextMenu(rcMenu, tableElement, pos) {
        rcMenu.style.display = "block"

        // Position the context menu relative to the mouse pointer
        if (pos.x + rcMenu.clientWidth <= tableElement.clientWidth)
            rcMenu.style.left = pos.x + "px"
        else
            rcMenu.style.left = pos.x - rcMenu.clientWidth + "px"

        if (pos.y + rcMenu.clientHeight <= window.innerHeight + document.documentElement.scrollTop)
            rcMenu.style.top = pos.y + "px"
        else
            rcMenu.style.top = pos.y - rcMenu.clientHeight + "px"

    }

    /**
     * Gets the position of the cell in the table.
     * @param {HTMLElement} cell - The HTML cell element.
     * @returns {Object} An object containing the column and row indexes.
     */
    getCellPos(cell) {
        // Name contains the column and row indexes.
        let name = cell.name

        let match = name.match(/\d+/g)
        let col = +match[0]
        let row = +match[1]
        row = isNaN(row) ? -1 : row

        return {
            col: col,
            row: row
        }
    }

    /**
     * Adds a new row to the table below the selected cell.
     */
    addRow() {
        // Clone the last row to create a new row.
        let lastRow = this.tableBody.lastElementChild
        let newRow = lastRow.cloneNode(true)

        // Get all input cells in the new row.
        let cells = Array.from(newRow.children).map(cell => cell.querySelector("input"))
        // Determine the row index of the last row.
        let lastRowIndex = this.getCellPos(cells[0]).row

        // Update IDs and names of input cells in the new row.
        cells.forEach(cell => {
            let pos = this.getCellPos(cell)
            cell.id = `chart_table_${pos.col}_values_${pos.row + 1}`
            cell.name = `chart[table][${pos.col}][values][${pos.row + 1}]`
        })

        // Shift existing row data down to make room for a new row.
        let pos = this.getCellPos(this.selectedCell)
        //pos.row = pos.row === -1 ? 0 : pos.row
        pos.row++
        for (let i = lastRowIndex; i > pos.row; i--) {
            for (let j = 0; j < cells.length; j++) {
                let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`)
                let previousCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i - 1}`)
                currentCell.value = previousCell.value
            }
        }

        this.tableBody.appendChild(newRow)

        // Clear input cells in the new row.
        for (let j = 0; j < cells.length; j++) {
            let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${pos.row}`)
            currentCell.value = ""
        }
    }

    /**
     * Deletes the row containing the selected cell.
     */
    delRow() {
        // Get the number of rows and columns in the table.
        let rowCount = this.tableBody.children.length
        let colCount = this.tableBody.lastElementChild.children.length

        // Get the position of the selected cell.
        let pos = this.getCellPos(this.selectedCell)

        // Don't delete heading row.
        if (pos.row === -1)
            return

        // Shift row data
        for (let i = pos.row; i < rowCount - 1; i++) {
            for (let j = 0; j < colCount; j++) {
                let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`)
                let previousCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i + 1}`)
                currentCell.value = previousCell.value
            }
        }

        // Remove the last row from the table.
        this.tableBody.removeChild(this.tableBody.lastElementChild)
    }

    /**
     * Adds a new column to the table.
     */
    addCol() {
        // Get the rows and column count of the table.
        let rows = Array.from(this.tableBody.children)
        let rowCount = rows.length
        let colCount = this.tableBody.lastElementChild.children.length
        // Clone the last cell in the heading row to create a new header cell for the new column.
        let newHeadCell = this.tableHead.lastElementChild.lastElementChild.cloneNode(true)
        let newHeadCellInput = newHeadCell.querySelector("input[type='text']")
        newHeadCellInput.id = `chart_table_${colCount}_col_name`
        newHeadCellInput.name = `chart[table][${colCount}][col_name]`
        let newHeadCellColor = newHeadCell.querySelector("input[type='color']")
        newHeadCellColor.id = `chart_table_${colCount}_color`
        newHeadCellColor.name = `chart[table][${colCount}][color]`
        this.tableHead.lastElementChild.appendChild(newHeadCell)

        // Clone the last cell in each row to create a new cell for the new column in each row.
        for (let i = 0; i < rowCount; i++) {
            let newCell = rows[i].lastElementChild.cloneNode(true)
            let newCellInput = newCell.querySelector("input")
            newCellInput.id = `chart_table_${colCount}_values_${i}`
            newCellInput.name = `chart[table][${colCount}][values][${i}]`

            rows[i].appendChild(newCell)
        }

        // Shift existing column data to the right to make room for a new column.
        let pos = this.getCellPos(this.selectedCell)
        pos.col++
        for (let i = 0; i < rowCount; i++) {
            for (let j = colCount; j > pos.col; j--) {
                let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`)
                let previousCell = this.tableBody.querySelector(`#chart_table_${j - 1}_values_${i}`)
                currentCell.value = previousCell.value
            }
        }

        // Shift existing column headings to the right to make room for a new column heading.
        for (let j = colCount; j > pos.col; j--) {
            let currentCell = this.tableHead.querySelector(`#chart_table_${j}_col_name`)
            let previousCell = this.tableHead.querySelector(`#chart_table_${j - 1}_col_name`)
            currentCell.value = previousCell.value
        }

        // Shift existing column colors to the right to make room for a new column color.
        for (let j = colCount; j > pos.col; j--) {
            let currentCell = this.tableHead.querySelector(`#chart_table_${j}_color`)
            let previousCell = this.tableHead.querySelector(`#chart_table_${j - 1}_color`)
            currentCell.value = previousCell.value
        }

        // Clear input cells in the new column.
        for (let i = 0; i < rowCount; i++) {
            let currentCell = this.tableBody.querySelector(`#chart_table_${pos.col}_values_${i}`)
            currentCell.value = ""
        }

        // Clear the input and color of the new column heading.
        let headCell = this.tableHead.lastElementChild.children[pos.col]
        headCell.querySelector("input[type='text']").value = ""
        headCell.querySelector("input[type='color']").value = "#FFFFFF"
    }

    /**
     * Deletes the selected column from the table.
     */
    delCol() {
        let rows = Array.from(this.tableBody.children)
        let rowCount = rows.length
        let colCount = this.tableBody.lastElementChild.children.length

        // Shift existing column data to the left.
        let pos = this.getCellPos(this.selectedCell)
        for (let i = 0; i < rowCount; i++) {
            for (let j = pos.col; j < colCount - 1; j++) {
                let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`)
                let previousCell = this.tableBody.querySelector(`#chart_table_${j + 1}_values_${i}`)
                currentCell.value = previousCell.value
            }
        }

        // Shift existing column titles to the left.
        for (let i = pos.col; i < colCount - 1; i++) {
            let currentCell = this.tableHead.querySelector(`#chart_table_${i}_col_name`)
            let previousCell = this.tableHead.querySelector(`#chart_table_${i + 1}_col_name`)
            currentCell.value = previousCell.value
        }

        // Shift existing column colors to the left.
        for (let i = pos.col; i < colCount - 1; i++) {
            let currentCell = this.tableHead.querySelector(`#chart_table_${i}_color`)
            let previousCell = this.tableHead.querySelector(`#chart_table_${i + 1}_color`)
            currentCell.value = previousCell.value
        }

        // Remove the last cell from each row.
        rows.forEach(row => {
            row.removeChild(row.lastElementChild)
        })

        // Remove the last header cell.
        this.tableHead.lastElementChild.removeChild(this.tableHead.lastElementChild.lastElementChild)
    }
}
