import { apiHost } from '../../consts'
import { fetchBuffer } from '../../helpers/fetch'
import { valueToColorInterpolated } from '../../helpers/interpolateColors'
import { GribMessage, MeteoConversion } from '../interfaces'
import { WIND_SPEED } from './constants'
import { rotateWind } from './windRotate'

const CELL_SIZE = 12

export function windDirectionArrows(
    imgData: ImageData,
    messages: GribMessage[],
    buffers: Uint8Array[],
) {
    const [, metaU, metaV] = messages
    const { conversion: convU } = metaU
    const { conversion: convV } = metaV
    const [, bufferU, bufferV] = buffers
    const cols = imgData.width
    const rows = imgData.height
    const canvas = document.createElement('canvas')
    canvas.width = imgData.width
    canvas.height = imgData.height
    const ctx = canvas.getContext('2d')!
    ctx.putImageData(imgData, 0, 0) // draw wind speed color below direction
    const directions: number[][] = []
    const bytesPerPoint = metaU.bitsPerDataPoint/8

    const lambert = (messages[0].grid as any).lambert
    let rot_lat = lambert[0] / 1_000_000
    let rot_lon = lambert[1] / 1_000_000
    // rot_lat = -9999.0
    // rot_lon = -9999.0
    let reg_lat = 56.530592
    let reg_lon = -2.918742

    for (let row = 0; row < rows; row++) {
        directions[row] = []
        for (let col = 0; col < cols; col++) {
            const bufferI = (row * cols + col) * bytesPerPoint
    
            const encodedU = toInt(bufferU.slice(bufferI, bufferI+bytesPerPoint))
            const encodedV = toInt(bufferV.slice(bufferI, bufferI+bytesPerPoint))
            const windSpeedU = (convU.reference + encodedU * Math.pow(2, convU.binaryScale)) * Math.pow(10, -convU.decimalScale)
            const windSpeedV = (convV.reference + encodedV * Math.pow(2, convV.binaryScale)) * Math.pow(10, -convV.decimalScale)

            // const directionDeg = (Math.atan2(windSpeedU, windSpeedV)*180/Math.PI + 360 +45) % 360
            const directionDeg = rotateWind(rot_lat, rot_lon, reg_lat, reg_lon, windSpeedU, windSpeedV)[0]
            const directionRad = (Math.PI / 180) * directionDeg

            directions[row][col] = directionRad
        }
    }

    const gridH = Math.floor(directions.length/CELL_SIZE)
    const gridW = Math.floor(directions[0].length/CELL_SIZE)
    for (let row = 0; row < gridH; row++) {
        for (let col = 0; col < gridW; col++) {
            const directionAvg = true
                ? getAvgDirection(directions, row, col)
                : getDirection(directions, row, col)

            const centerX = col * CELL_SIZE + CELL_SIZE/2
            const centerY = row * CELL_SIZE + CELL_SIZE/2
            const arrowLength = CELL_SIZE*0.9

            ctx.save()
            ctx.translate(centerX, centerY)
            ctx.rotate(directionAvg)

            ctx.beginPath();
            ctx.moveTo(-arrowLength / 2, 0)
            ctx.lineTo(arrowLength / 2, 0)
            ctx.stroke()

            const arrowheadSize = CELL_SIZE/4
            ctx.beginPath()
            ctx.moveTo(arrowLength / 2, 0)
            ctx.lineTo(arrowLength / 2 - arrowheadSize, -arrowheadSize / 2)
            ctx.lineTo(arrowLength / 2 - arrowheadSize, arrowheadSize / 2)
            ctx.closePath()
            ctx.fill()

            ctx.restore()
        }
    }

    return ctx.getImageData(0, 0, imgData.width, imgData.height)
}

function getDirection(directions: number[][], gridRow: number, gridCol: number) {
    const directionRow = gridRow * CELL_SIZE + Math.round(CELL_SIZE/2)
    const directionCol = gridCol * CELL_SIZE + Math.round(CELL_SIZE/2)
    return directions[directionRow][directionCol]
}

// in radians
function getAvgDirection(directions: number[][], gridRow: number, gridCol: number) {
    let sumSin = 0; // Sum of sine components
    let sumCos = 0; // Sum of cosine components

    for (let row = 0; row < CELL_SIZE; row++) {
        for (let col = 0; col < CELL_SIZE; col++) {
            const directionRow = gridRow * CELL_SIZE + row;
            const directionCol = gridCol * CELL_SIZE + col;

            const directionRad = directions[directionRow][directionCol]

            sumSin += Math.sin(directionRad);
            sumCos += Math.cos(directionRad);
        }
    }

    // Compute the average direction
    const avgDirection = Math.atan2(sumSin, sumCos); // Result is in radians
    return (avgDirection + 2 * Math.PI) % (2 * Math.PI); // Ensure the result is in [0, 2π)
}

// actually calculates and draws wind speed
export function windDirectionColors(
    encodedU: number,
    encodedV: number,
    convU: MeteoConversion,
    convV: MeteoConversion,
) {
    const windSpeedU = (convU.reference + encodedU * Math.pow(2, convU.binaryScale)) * Math.pow(10, -convU.decimalScale)
    const windSpeedV = (convV.reference + encodedV * Math.pow(2, convV.binaryScale)) * Math.pow(10, -convV.decimalScale)
    const windSpeed = Math.sqrt(Math.pow(windSpeedU, 2) + Math.pow(windSpeedV, 2))

    return valueToColorInterpolated(windSpeed, WIND_SPEED)
}

export function windSpeedColors(
    encodedValue: number,
    { reference, binaryScale, decimalScale}: MeteoConversion,
) {
    const windSpeed = (reference + encodedValue * Math.pow(2, binaryScale)) * Math.pow(10, -decimalScale)
    return valueToColorInterpolated(windSpeed, WIND_SPEED)
}

export function fetchWindData(
    customMessage: GribMessage,
    gribArr: GribMessage[],
): Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> {
    const windU = gribArr.find(m => m.meteo.discipline===0 && m.meteo.category===2 && m.meteo.product===2 && m.meteo.levelType===103 && m.meteo.levelValue===10)
    const windV = gribArr.find(m => m.meteo.discipline===0 && m.meteo.category===2 && m.meteo.product===3 && m.meteo.levelType===103 && m.meteo.levelValue===10)
    if (!windU || !windV) throw new Error('Didnt found u/v components of wind')

    const section7u = windU.sections.find(section => section.id === 7)
    const section7v = windV.sections.find(section => section.id === 7)
    if (!section7u || !section7v) throw new Error('Didnt found binaru section for wind u/v')

    const uBinaryOffset = section7u.offset + 5
    const uBinaryLength = section7u.size - 5

    const vBinaryOffset = section7v.offset + 5
    const vBinaryLength = section7v.size - 5

    return Promise.all([
        fetchBuffer(`${apiHost}/api/binary-chunk/${uBinaryOffset}/${uBinaryLength}`),
        fetchBuffer(`${apiHost}/api/binary-chunk/${vBinaryOffset}/${vBinaryLength}`),
    ]).then(([bufferU, bufferV]) => {
        const buffer = new Uint8Array(bufferU.byteLength + bufferV.byteLength)
        buffer.set(new Uint8Array(bufferU))
        buffer.set(new Uint8Array(bufferV), bufferU.byteLength)
        const messages = [customMessage, windU, windV]
        const buffers = [bufferU, bufferU, bufferV]
        return [messages, buffers, []]
    })
}

function toInt(bytes: Uint8Array): number {
    return bytes.reduce((acc, curr) => acc * 256 + curr)
}
