'use strict';

import UI from "./ui.js";
import API_CONFIG from "./config.js";
import Icons from "./icons.js";
import { SphericalPhoto } from "./components/SphericalPhoto.js";

const MAPBOX_TOKEN = 'pk.eyJ1Ijoibmljb3BhIiwiYSI6ImNseWNwZjJjbjFidzcya3BoYTU0bHg4NnkifQ.3036JnCXZTEMt6jVgMzVRw';
const BASE_URL = location.href;

let API_URL = '';
if (BASE_URL.includes('localhost')) {
    API_URL = API_CONFIG.dev;
} else {
    API_URL = API_CONFIG.prod;
}

window.API_URL = API_URL;

// Global leaflet
/**
 * @namespace GIS
 */
const GIS = {};

GIS.CENTER_COORDS = [40.5492, 14.2317];
GIS.INIT_ZOOM = 14;
GIS.MIN_ZOOM = 11;
GIS.MAX_ZOOM = 24;

const optionsVincoli = {
    color: '#222',
    opacity: 0.8,
    weight: 1,
    fillColor: '#fa7861',
    fillOpacity: 0.8
};
const optionsSiti = {
    color: '#800040',
    opacity: 1,
    weight: 1.5,
    fillColor: '#800040',
    fillOpacity: 0.8
};
const optionsGrotta = {
    color: '#205dac',
    opacity: 1,
    weight: 1.5,
    fillColor: '#205dac',
    fillOpacity: 0.8
}
const optionsPaesistici = {
    color: '#222',
    opacity: 1,
    weight: 1.5,
    fillColor: '#88d28d',
    fillOpacity: 0.8
};
const optionsFabbricati = {
    color: '#222',
    opacity: 1,
    weight: 1.5,
    fillColor: '#5b5d5f',
    fillOpacity: 0.8
};
const clusterOptions = {
    spiderfyOnMaxZoom: false,
    showCoverageOnHover: false,
    disableClusteringAtZoom: 19,
};

/**
 * Capitalize a text string
 * @todo Move to utils
 * @param {?string} text 
 * @returns {?string} The capitalized string or null
 */
function capitalize(text) {
    let capital = text;
    if (text) {
        let words = text.split(' ');
        capital = '';

        for (let w of words) {
            w = w[0].toUpperCase() + w.slice(1);
            capital += w + ' ';
        }
        capital.trimEnd();
    }

    return capital;
}
/**
 * @param {string} mapId
 * @param {number} zoomLevel
 * @returns {Map}
 */
GIS.initMap = async function (mapId, zoomLevel = this.INIT_ZOOM) {
    let map = L.map(mapId, {
        //attributionControl: false,
        minZoom: GIS.MIN_ZOOM,
    }).setView(this.CENTER_COORDS, zoomLevel);

    map.crs = L.CRS.EPSG4326;

    const baseMap = await this.initLayers(map);
    // Add scale and ruler controls
    L.control.scale({imperial: false}).addTo(map);
    L.control.graphicScale({fill: 'hollow', position: 'bottomright'}).addTo(map);

    let layerVincoli = await this.loadGeoJSON('vincoli.geojson', optionsVincoli);
    let layerPaesistici = await this.loadGeoJSON('paesistici.geojson', optionsPaesistici);

    // Refactor with separate function...
    this.sites().then(data => {
        data.markers.addTo(map);
        data.geom.addTo(map);
        window.Sites = data.markers;
    });
    this.notConserved().then(group => {group.addTo(map); window.NotConserved = group});
    this.findings().then(group => {group.addTo(map); window.Findings = group});
    this.prehistoric().then(group => {group.addTo(map); window.Prehistoric = group});
    this.underwater().then(group => {group.addTo(map); window.Underwater = group});

    const archeo = {
        'Vincoli archeologici' : layerVincoli,
        'Vincoli paesistici' : layerPaesistici,
    };
    L.control.layers(baseMap, archeo).addTo(map);

    return map;
}
/**
 * Create markers for sites
 * @returns {{markers: L.markerClusterGroup, geom: L.LayerGroup}}
 */
GIS.sites = async function () {
    let sitesData = await fetch(`${API_URL}/sites`)
        .then(data => data.json());

    let sites = L.markerClusterGroup(clusterOptions);
    let geom = [];

    for (let record of sitesData) {
        if (record.geojson) {
            const options = record.gisId === 'grotta_azzurra' ?
                optionsGrotta : optionsSiti;
            geom.push(await this.loadSiteLayer(record, options));
        }
        sites.addLayer(L.marker(
                record.coordinates,
                {icon: Icons.site}
            ).bindTooltip(record.label + '<br>(Clicca per aprire scheda)')
            .on(
                'click',
                () => UI.openSiteModal(record, '#site-data')
            )
        );
    }

    return {markers: sites, geom: L.layerGroup(geom)};
}
/**
 * Create not conserved group
 * @returns {L.MarkerClusterGroup}
 */
GIS.notConserved = async function () {
    let notConserData = await fetch(`${API_URL}/not_conserved`)
        .then(data => data.json());

    let notConserved = L.markerClusterGroup(
        clusterOptions
    );

    for (let record of notConserData.records) {
        notConserved.addLayer(L.marker(
                record.coordinates,
                {icon: Icons.notConserved}
            ).bindTooltip(record.denomination)
            .on(
                'click',
                () => UI.openNotConserModal(record, '#not-conser-data')
            )
        );
    }

    return notConserved;
}
/**
 * Create findings group
 * @returns {L.MarkerClusterGroup}
 */
GIS.findings = async function () {
    let findingsData = await fetch(`${API_URL}/finding`)
        .then(data => data.json());

    let findings = L.markerClusterGroup(
        clusterOptions
    );

    for (let record of findingsData) {
        findings.addLayer(L.marker(
                record.coordinates,
                {icon: Icons.finding}
            ).bindTooltip(record.object)
            .on(
                'click',
                () => UI.openFindingModal(record, '#finding-data')
            )
        );
    }

    return findings;
}
/**
 * Create group for prehistoric sites
 * 
 */
GIS.prehistoric = async function () {
    let data = await fetch(`${API_URL}/prehistoric`)
        .then(data => data.json());

    let prehistoric = L.markerClusterGroup(
        clusterOptions
    );

    for (let record of data.records) {
        prehistoric.addLayer(L.marker(
                record.coordinates,
                {icon: Icons.prehistoric}
            ).bindTooltip(record.denomination)
            .on(
                'click',
                () => UI.openPrehistModal(record, '#prehist-data')
            )
        );
    }

    return prehistoric;
}
/**
 * Create underwater sites group
 * @returns {L.MarkerClusterGroup}
 */
GIS.underwater = async function () {
    let underwaterData = await fetch(`${API_URL}/underwater`)
        .then(data => data.json());

    let underwater = L.markerClusterGroup(clusterOptions);

    for (let record of underwaterData.records) {
        underwater.addLayer(L.marker(
                record.coordinates,
                {icon: Icons.underwater}
            ).bindTooltip(record.denomination)
            .on(
                'click',
                () => UI.openUnderwaterModal(record, '#underwater-data')
            )
        );
    }

    return underwater;
}
/**
 * Adds layers to map and returns an object
 * with {baseMap, archeoLayers, sitesLayerGroup}
 * @todo Load areas for sites that have them!!
 * @param {L.Map} map 
 * @returns {{baseMap: {"OpenStreetMap": L.TileLayer}}}
 */
GIS.initLayers = async function(map) {

    let osmap = new L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxNativeZoom : GIS.MAX_ZOOM,
        maxZoom: GIS.MAX_ZOOM,
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    });
    let mapbox = new L.tileLayer(`https://api.mapbox.com/v4/{id}/{z}/{x}/{y}@2x.jpg90?access_token=${MAPBOX_TOKEN}`, {
        id : 'mapbox.satellite',
        tileSize : 512,
        zoomOffset : -1,
        maxNativeZoom : GIS.MAX_ZOOM,
        maxZoom: GIS.MAX_ZOOM,
        attribution: '&copy; Mapbox'
    });
    let boundaries = await this.loadGeoJSON('confini.geojson', {}, false);
    let buildings = await this.loadGeoJSON('fabbricati.geojson', optionsFabbricati, false);
    let baseCatasto = new L.LayerGroup([buildings, boundaries]);

    const baseGroup = new L.LayerGroup([osmap]);
    baseGroup.addTo(map);
    const baseMap = {
        "OpenStreetMap" : osmap,
        "Satellite" : mapbox,
        "Cartografia catastale" : baseCatasto,
    };

    return baseMap;
}
/**
 * Toggle spherical photos on the map
 * based on zoom level
 */
GIS.toggleSpherical = async function(map) {
    // TODO Create points layer
    const spherical = await this._fetchData('spherical');
    let sphereMarkers = [];
    const sPhoto = new SphericalPhoto();

    if (spherical) {
        for (let pano of spherical) {
            let marker = L.marker(pano.coordinates, {
                icon: Icons.camera,
            });

            marker.bindTooltip('Apri foto sferica');
            marker.on('click', () => {
                let modal = document.querySelector('#spherical-modal');
                let viewer = document.querySelector('#pano-viewer');
                viewer.innerHTML = '';
                modal.classList.add('is-active');
                modal.querySelector('.modal-background').addEventListener('click', () => {
                    modal.classList.remove('is-active');
                });
                modal.querySelector('.modal-close').addEventListener('click', () => {
                    modal.classList.remove('is-active');
                })
                sPhoto.setViewer(pano.filename);
            });

            sphereMarkers.push(marker);
        }
    }

    let group = L.layerGroup(sphereMarkers);

    map.on('zoomend', () => {
        if (map.getZoom() >= 19) {
            group.addTo(map);
        } else {
            group.remove();
        }
    });
}
/**
 * @param {string} geoJSON geoJSON filename
 * @param {{color, opacity, weight, fillColor, fillOpacity}} options Style options for features
 * @param {boolean} popup Should the features have a popup? 
 * @returns {L.Layer}
 */
GIS.loadGeoJSON = async function (geoJSON, options, popup = true) {
    const geo = await fetch(`${BASE_URL}/geojson/${geoJSON}`)
        .then(res => res.json())
        .catch(error => console.error(`Can't load layer ${geoJSON}. Reason: ${error}`));
    
    // Show data from feature in popUp?
    const layer = new L.geoJson(geo, {
        style: function () {
            return options;
        },
        onEachFeature: function (feature, layer) {
            if (popup) {
                layer.bindPopup(GIS.featurePopup(geoJSON, feature));
            }
        }
    });

    return layer;
}
/**
 * @param {object} site Site record from DB
 * @param {{color, opacity, weight, fillColor, fillOpacity}} options Style options for features
 * @param {boolean} popup Should the features have a popup? 
 * @returns {L.Layer}
 */
GIS.loadSiteLayer = async function (site, options, popup = true) {
    const geoJSON = `${site.gisId}.geojson`;
    const geo = await fetch(`${BASE_URL}/geojson/${geoJSON}`)
        .then(res => res.json())
        .catch(error => console.error(`Can't load layer ${geoJSON}. Reason: ${error}`));
    
    this.cacheDBData(site.gisId, site.id);

    // Show data from feature in popUp?
    const layer = new L.geoJson(geo, {
        style: function () {
            return options;
        },
        onEachFeature: function (feature, layer) {
            if (popup) {
                layer.bindPopup(GIS.featurePopup(geoJSON, feature));
            }
            else {
                layer.on("click", async () => {
                    let data = GIS.layerData(site.gisId, site.id);
                    
                    if (typeof data === 'object') {
                        UI.openSiteModal(data);
                    }
                });
            }
        }
    });

    return layer;
}
/**
 * Retrieves data for a given layer
 * @param {string} layerId 
 * @param {string} dbId 
 * @returns {object} Data for this layer from DB or cache
 */
GIS.layerData = async function (layerId, dbId) {
    const fromStorage = localStorage.getItem(layerId);
    let data = {};

    if (fromStorage !== 'undefined') {
        try {
            data = JSON.parse(fromStorage);
        } catch (error) {
            console.log(error);
        }
    } else {
        data = await GIS._fetchData('sites/' + dbId);
    }

    return data;
}
/**
 * Get spherical photos for a site
 * @param {int} siteId
 */
GIS.getSpherical = async function (siteId) {
    const data = await this._fetchData('spherical/' + siteId);
}

/**
 * Cache data from DB in local storage
 * for a given layer
 * @param {string} layerId
 */
GIS.cacheDBData = async function (layerId, dbId) {
    const data = await this._fetchData('sites/' + dbId);
    localStorage.setItem(layerId, JSON.stringify(data));
}
/**
 * Generate proper content for features popup
 * @todo Hard-coded names!!
 * 
 * @param {string} layerName
 * @param {object} feature
 * @returns {string} The popup's content
 */
GIS.featurePopup = function (layerName, feature) {
    const html = `
        <table class="table is-striped is-size-6 m-2">
            <tr><th>Oggetto</th><td>${feature.properties.OGGETTO}</td></tr>
            <tr><th>Anno</th><td>${feature.properties.ANNO}</td></tr>
            <tr><th>Comune</th><td>${capitalize(feature.properties.COMUNE)}</td></tr>
            <tr><th>Località</th><td>${capitalize(feature.properties.LOCALITA)}</td></tr>
            <tr><th>Proprietà</th><td>${capitalize(feature.properties.PROPRIETA)}</td></tr>
        </table>
    `;
    const content = {
        'vincoli.geojson' : html,
        'paesistici.geojson' : html,
    };

    return content[layerName];
}
/**
 * Fetch data from API
 * @param {string} recordId
 */
GIS._fetchData = async function (recordId) {
    const data = await fetch(`${API_URL}/${recordId}`)
        .then(res => res.json())
        .catch(err => console.log('Error fetching data from DB: ' + err));

    return data;
}

export default GIS;
