import { Coordinate, distance, rotate } from "ol/coordinate";
import { containsExtent, Extent, getCenter, getHeight, getWidth } from "ol/extent";
import Feature from "ol/Feature";
import GeoJSON from "ol/format/GeoJSON";
import { Circle, Geometry, LineString, Polygon, SimpleGeometry } from "ol/geom";
import VectorLayer from "ol/layer/Vector";
import VectorImageLayer from "ol/layer/VectorImage";
import Map from "ol/Map";
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
import VectorSource from "ol/source/Vector";
import proj4 from "proj4";
import { selectStyle } from "./MapStyles";
import { Type } from "ol/geom/Geometry";
import { Draw, Modify, Snap, Translate } from "ol/interaction";
import { noModifierKeys } from "ol/events/condition";
import { IGeometryType, IRectCoords } from "../../interfaces/map";
import { uploadCoordinates } from "../../actions/map";
import { ImageStatic } from "ol/source";
import ImageLayer from "ol/layer/Image";
import { IFigure, ILine } from "../../interfaces/gis";
import { fromCircle, fromExtent } from "ol/geom/Polygon";
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint, setStatusText } from "../../store/map";
import { MutableRefObject } from "react";
import { getSelectedCity, getSelectedYear, setSelectedRegion } from "../../store/objects";

const calculateAngle = (coords: [number, number][]) => {
    const [start, end] = coords;
    const dx = end[0] - start[0];
    const dy = end[1] - start[1];
    return Math.atan2(dy, dx); // Angle in radians
}

export function processLine(
    line: ILine,
    scaling: number,
    mapCenter: Coordinate,
    linesLayer: MutableRefObject<VectorLayer<VectorSource>>
) {
    const x1 = line.x1 * scaling
    const y1 = line.y1 * scaling
    const x2 = line.x2 * scaling
    const y2 = line.y2 * scaling

    const center = [mapCenter[0], mapCenter[1]]

    const testCoords: [number, number][] = [
        [center[0] + x1, center[1] - y1],
        [center[0] + x2, center[1] - y2],
    ]

    const feature = new Feature(new LineString(testCoords))

    feature.set('type', line.type)
    feature.set('geometry_type', 'line')
    feature.set('planning', line.planning)
    feature.set('object_id', line.object_id)
    feature.set('rotation', calculateAngle(testCoords))

    linesLayer.current?.getSource()?.addFeature(feature)
}

export function processFigure(
    figure: IFigure,
    scaling: number,
    mapCenter: Coordinate,
    figuresLayer: MutableRefObject<VectorLayer<VectorSource>>
) {
    if (figure.figure_type_id == 1) {
        const width = figure.width * scaling
        const height = figure.height * scaling

        const left = figure.left * scaling
        const top = figure.top * scaling

        const centerX = mapCenter[0] + left + (width / 2)
        const centerY = mapCenter[1] - top - (height / 2)

        const radius = width / 2;
        const circleGeom = new Circle([centerX, centerY], radius)

        const ellipseGeom = fromCircle(circleGeom, 64)
        ellipseGeom.scale(1, height / width)

        const feature = new Feature(ellipseGeom)

        feature.set('type', figure.type)
        feature.set('object_id', figure.object_id)
        feature.set('planning', figure.planning)
        figuresLayer.current?.getSource()?.addFeature(feature)
    }

    if (figure.figure_type_id == 3) {
        const x = figure.left * scaling
        const y = figure.top * scaling

        const center = [mapCenter[0] + x, mapCenter[1] - y]

        const coords = figure.points?.split(' ').map(pair => {
            const [x, y] = pair.split(';').map(Number)
            return [
                center[0] + (x * scaling),
                center[1] - (y * scaling)
            ]
        })

        if (coords) {
            const polygon = new Polygon([coords])

            const feature = new Feature({
                geometry: polygon
            })

            feature.set('object_id', figure.object_id)
            feature.set('planning', figure.planning)
            feature.set('type', figure.type)
            figuresLayer.current?.getSource()?.addFeature(feature)
        }
    }

    if (figure.figure_type_id == 4) {
        const width = figure.width * scaling
        const height = figure.height * scaling
        const left = figure.left * scaling
        const top = figure.top * scaling

        const halfWidth = width / 2
        const halfHeight = height / 2

        const center = [mapCenter[0] + left + halfWidth, mapCenter[1] - top - halfHeight]

        const testCoords = [
            [center[0] - halfWidth, center[1] - halfHeight],
            [center[0] - halfWidth, center[1] + halfHeight],
            [center[0] + halfWidth, center[1] + halfHeight],
            [center[0] + halfWidth, center[1] - halfHeight],
            [center[0] - halfWidth, center[1] - halfHeight]
        ]

        const geometry1 = new Polygon([testCoords])
        const anchor1 = center
        geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
        const feature1 = new Feature(geometry1)
        feature1.set('object_id', figure.object_id)
        feature1.set('planning', figure.planning)
        feature1.set('type', figure.type)
        feature1.set('angle', figure.angle)
        figuresLayer.current?.getSource()?.addFeature(feature1)
    }
}

// Function to update the image layer with a new source when extent changes
export const updateImageSource = (
    imageUrl: string,
    imageLayer: React.MutableRefObject<ImageLayer<ImageStatic>>,
    polygonFeature: Feature<Polygon>,
    setPolygonExtent: (value: React.SetStateAction<Extent | undefined>) => void,
    setRectCoords: React.Dispatch<React.SetStateAction<IRectCoords | undefined>>
) => {
    const newExtent = polygonFeature.getGeometry()?.getExtent();

    const bottomLeft = polygonFeature.getGeometry()?.getCoordinates()[0][0]
    const topLeft = polygonFeature.getGeometry()?.getCoordinates()[0][1]
    const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2]
    const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3]

    setRectCoords({
        bl: bottomLeft,
        tl: topLeft,
        tr: topRight,
        br: bottomRight
    })

    setPolygonExtent(newExtent)

    if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
        const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
        const newImageSource = new ImageStatic({
            url: imageUrl,
            imageExtent: originalExtent,
            projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
        });
        imageLayer.current.setSource(newImageSource);
    }
};

export const addInteractions = (
    drawingLayerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
    translate: React.MutableRefObject<Translate | null>,
    draw: React.MutableRefObject<Draw | null>,
    map: React.MutableRefObject<Map | null>,
    snap: React.MutableRefObject<Snap | null>,
    measureDraw: React.MutableRefObject<Draw | null>,
    measureSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
    measureModify: React.MutableRefObject<Modify>,
) => {
    const currentTool = getCurrentTool()
    const clearPrevious = getMeasureClearPrevious()
    const measureType = getMeasureType()
    const tipPoint = getTipPoint()

    if (currentTool !== 'Measure' && currentTool !== 'Mover' && currentTool !== 'Edit') {
        draw.current = new Draw({
            source: drawingLayerSource.current,
            type: currentTool as Type,
            condition: noModifierKeys
        })

        draw.current.on('drawend', function (s) {
            console.log(s.feature.getGeometry()?.getType())
            let type: IGeometryType = 'POLYGON'

            switch (s.feature.getGeometry()?.getType()) {
                case 'LineString':
                    type = 'LINE'
                    break
                case 'Polygon':
                    type = 'POLYGON'
                    break
                default:
                    type = 'POLYGON'
                    break
            }
            const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
            uploadCoordinates(coordinates, type)
        })

        map?.current?.addInteraction(draw.current)
        snap.current = new Snap({ source: drawingLayerSource.current })
        map?.current?.addInteraction(snap.current)
    }

    if (currentTool == 'Measure') {
        const drawType = measureType;
        const activeTip =
            'Кликните, чтобы продолжить рисовать ' +
            (drawType === 'Polygon' ? 'многоугольник' : 'линию');
        const idleTip = 'Кликните, чтобы начать измерение';
        let tip = idleTip;

        measureDraw.current = new Draw({
            source: measureSource.current,
            type: drawType,
            style: function (feature) {
                return measureStyleFunction(feature, drawType, tip);
            },
        });
        measureDraw.current.on('drawstart', function () {
            if (clearPrevious) {
                measureSource.current.clear();
            }
            measureModify.current.setActive(false);
            tip = activeTip;
        });
        measureDraw.current.on('drawend', function () {
            modifyStyle.setGeometry(tipPoint as Geometry);
            measureModify.current.setActive(true);
            map.current?.once('pointermove', function () {
                modifyStyle.setGeometry('');
            });
            tip = idleTip;
        });
        measureModify.current.setActive(true);
        map.current?.addInteraction(measureDraw.current);
    }

    if (currentTool == 'Mover') {
        translate.current = new Translate()
        map?.current?.addInteraction(translate.current)
    }

    if (currentTool == 'Edit') {
        //const modify = new Modify()
        //map?.current?.addInteraction(translate.current)
    }
}

export function regionsInit(
    map: React.MutableRefObject<Map | null>,
    selectedRegion: React.MutableRefObject<Feature<Geometry> | null>,
    regionsLayer: React.MutableRefObject<VectorImageLayer<Feature<Geometry>, VectorSource<Feature<Geometry>>>>,
) {
    regionsLayer.current.once('change', function () {
        if (getSelectedCity() || getSelectedYear()) return
        const extent = regionsLayer.current.getSource()?.getExtent()

        if (extent && !extent?.every(val => Math.abs(val) === Infinity)) {
            map.current?.getView().fit(fromExtent(extent) as SimpleGeometry, { duration: 500, maxZoom: 18, padding: [60, 60, 60, 60] })
        }
    })

    map.current?.on('click', function (e) {
        if (selectedRegion.current !== null) {
            selectedRegion.current = null
        }

        if (map.current) {
            map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
                if (layer === regionsLayer.current) {
                    selectedRegion.current = feature as Feature
                    // Zoom to the selected feature
                    zoomToFeature(map, selectedRegion.current)

                    if (feature.get('id')) {
                        setSelectedRegion(feature.get('id'))
                    }
                    return true
                } else return false
            });
        }
    })

    // Show current selected region
    map.current?.on('pointermove', function (e) {
        if (selectedRegion.current !== null) {
            selectedRegion.current.setStyle(undefined)
            selectedRegion.current = null
        }

        if (map.current) {
            map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
                if (layer === regionsLayer.current) {
                    selectedRegion.current = feature as Feature
                    selectedRegion.current.setStyle(selectStyle)

                    if (feature.get('district')) {
                        setStatusText(feature.get('district'))
                    }

                    return true
                } else return false
            })
        }
    })

    // Hide regions layer when fully visible
    map.current?.on('moveend', function () {
        const viewExtent = map.current?.getView().calculateExtent(map.current.getSize())
        const features = regionsLayer.current.getSource()?.getFeatures()

        let isViewCovered = false

        features?.forEach((feature: Feature) => {
            const featureExtent = feature?.getGeometry()?.getExtent()
            if (viewExtent && featureExtent) {
                if (containsExtent(featureExtent, viewExtent)) {
                    isViewCovered = true
                }
            }
        })

        regionsLayer.current.setVisible(!isViewCovered)
    })
}

const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature) => {
    const geometry = feature.getGeometry()
    const extent = geometry?.getExtent()

    if (map.current && extent) {
        map.current.getView().fit(extent, {
            duration: 300,
            maxZoom: 19,
        })
    }
}

// Function to save features to localStorage
export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource> | null>) => {
    const features = layerRef.current?.getSource()?.getFeatures()
    if (features && features.length > 0) {
        const geoJSON = new GeoJSON()
        const featuresJSON = geoJSON.writeFeatures(features)
        localStorage.setItem('savedFeatures', featuresJSON)
    }
}

// Function to load features from localStorage
export const loadFeatures = (layerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>) => {
    const savedFeatures = localStorage.getItem('savedFeatures')
    if (savedFeatures) {
        const geoJSON = new GeoJSON()
        const features = geoJSON.readFeatures(savedFeatures, {
            featureProjection: 'EPSG:4326', // Ensure the projection is correct
        })
        layerSource.current?.addFeatures(features)  // Add features to the vector source
        //drawingLayer.current?.getSource()?.changed()
    }
}

function rotateProjection(projection: ProjectionLike, angle: number, extent: Extent) {
    function rotateCoordinate(coordinate: Coordinate, angle: number, anchor: Coordinate) {
        const coord = rotate(
            [coordinate[0] - anchor[0], coordinate[1] - anchor[1]],
            angle
        );
        return [coord[0] + anchor[0], coord[1] + anchor[1]];
    }

    function rotateTransform(coordinate: Coordinate) {
        return rotateCoordinate(coordinate, angle, getCenter(extent));
    }

    function normalTransform(coordinate: Coordinate) {
        return rotateCoordinate(coordinate, -angle, getCenter(extent));
    }

    const normalProjection = get(projection);

    if (normalProjection) {
        const rotatedProjection = new Projection({
            code: normalProjection.getCode() + ":" + angle.toString() + ":" + extent.toString(),
            units: normalProjection.getUnits(),
            extent: extent
        });
        addProjection(rotatedProjection);

        addCoordinateTransforms(
            "EPSG:4326",
            rotatedProjection,
            function (coordinate) {
                return rotateTransform(transform(coordinate, "EPSG:4326", projection));
            },
            function (coordinate) {
                return transform(normalTransform(coordinate), projection, "EPSG:4326");
            }
        );

        addCoordinateTransforms(
            "EPSG:3857",
            rotatedProjection,
            function (coordinate) {
                return rotateTransform(transform(coordinate, "EPSG:3857", projection));
            },
            function (coordinate) {
                return transform(normalTransform(coordinate), projection, "EPSG:3857");
            }
        );

        // also set up transforms with any projections defined using proj4
        if (typeof proj4 !== "undefined") {
            const projCodes = Object.keys(proj4.defs);
            projCodes.forEach(function (code) {
                const proj4Projection = get(code) as Projection;
                if (proj4Projection) {
                    if (!getTransform(proj4Projection, rotatedProjection)) {
                        addCoordinateTransforms(
                            proj4Projection,
                            rotatedProjection,
                            function (coordinate) {
                                return rotateTransform(
                                    transform(coordinate, proj4Projection, projection)
                                );
                            },
                            function (coordinate) {
                                return transform(
                                    normalTransform(coordinate),
                                    projection,
                                    proj4Projection
                                );
                            }
                        );
                    }
                }
            });
        }

        return rotatedProjection;
    }
}

const calculateCentroid = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => {
    const x = (bottomLeft[0] + topLeft[0] + topRight[0] + bottomRight[0]) / 4;
    const y = (bottomLeft[1] + topLeft[1] + topRight[1] + bottomRight[1]) / 4;
    return [x, y];
}

function calculateRotationAngle(bottomLeft: Coordinate, bottomRight: Coordinate) {
    // Calculate the difference in x and y coordinates between bottom right and bottom left
    const deltaX = bottomRight[0] - bottomLeft[0];
    const deltaY = bottomRight[1] - bottomLeft[1];

    // Calculate the angle using atan2
    const angle = -Math.atan2(deltaY, deltaX);

    return angle;
}

function calculateExtent(bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) {
    const width = distance(bottomLeft, bottomRight);
    const height = distance(bottomLeft, topLeft);

    // Calculate the centroid of the polygon
    const [centerX, centerY] = calculateCentroid(bottomLeft, topLeft, topRight, bottomRight);

    // Define the extent based on the center and dimensions
    const extent = [
        centerX - width / 2, // minX
        centerY - height / 2, // minY
        centerX + width / 2, // maxX
        centerY + height / 2  // maxY
    ];

    return extent;
}

function getTilesPerSide(zoom: number) {
    return Math.pow(2, zoom)
}

function normalize(value: number, min: number, max: number) {
    return (value - min) / (max - min)
}

function getTileIndex(normalized: number, tilesPerSide: number) {
    return Math.floor(normalized * tilesPerSide)
}

function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number) {
    const tilesPerSide = getTilesPerSide(zoom);
    const minX = extent[0]
    const minY = extent[1]
    const maxX = extent[2]
    const maxY = extent[3]

    // Normalize the coordinates
    const xNormalized = normalize(x, minX, maxX);
    const yNormalized = normalize(y, minY, maxY);

    // Get tile indices
    const tileX = getTileIndex(xNormalized, tilesPerSide);
    const tileY = getTileIndex(1 - yNormalized, tilesPerSide);

    return { tileX, tileY };
}

function calculateCenter(geometry: SimpleGeometry) {
    let center, coordinates, minRadius;
    const type = geometry.getType();
    if (type === 'Polygon') {
        let x = 0;
        let y = 0;
        let i = 0;
        coordinates = (geometry as Polygon).getCoordinates()[0].slice(1);
        coordinates.forEach(function (coordinate) {
            x += coordinate[0];
            y += coordinate[1];
            i++;
        });
        center = [x / i, y / i];
    } else if (type === 'LineString') {
        center = (geometry as LineString).getCoordinateAt(0.5);
        coordinates = geometry.getCoordinates();
    } else {
        center = getCenter(geometry.getExtent());
    }
    let sqDistances;
    if (coordinates) {
        sqDistances = coordinates.map(function (coordinate: Coordinate) {
            const dx = coordinate[0] - center[0];
            const dy = coordinate[1] - center[1];
            return dx * dx + dy * dy;
        });
        minRadius = Math.sqrt(Math.max(...sqDistances)) / 3;
    } else {
        minRadius =
            Math.max(
                getWidth(geometry.getExtent()),
                getHeight(geometry.getExtent()),
            ) / 3;
    }
    return {
        center: center,
        coordinates: coordinates,
        minRadius: minRadius,
        sqDistances: sqDistances,
    };
}

export {
    rotateProjection,
    calculateRotationAngle,
    calculateExtent,
    calculateCentroid,
    getTilesPerSide,
    normalize,
    getTileIndex,
    getGridCellPosition,
    calculateCenter
}