/// <reference path="..\..\GSMyAdmin\WebRoot\Scripts\UI.js" />
/// <reference path="..\..\GSMyAdmin\WebRoot\Scripts\API.js" />

/* jshint undef: true, unused: false */
/* global API,UI,PluginHandler */

this.plugin = {
    PreInit: function () {
        //Called prior to the plugins initialisation, before the tabs are loaded.
        //This method must not invoke any module/plugin specific API calls.
    },

    PostInit: function () {
        //The tabs have been loaded. You should wire up any event handlers here.
        //You can use module/specific API calls here.
        RegisterViewmodel(AnalyticsVM);
    },

    Reset: function () {
        //Tear everything down. Ideally it should be possible to continually do 
        //PreInit -> PostInit -> Reset -> PreInit -> PostInit... over and over.
    },

    StartupFailure: function () {
        //Gets called when the application fails to start. Mainly for Modules rather than plugins.
    },

    SettingChanged: function (node, value) {
        //Invoked whenever a setting is changed. Only applies to changes in the current session rather
        //than made by other users.
    },

    AMPDataLoaded: function () {
        //Any data you might need has been loaded, you should wire up event handlers which use data such as settings here.
    },

    PushedMessage: function (source, message, parameters) {
        //Invoked when your associated plugin invokes IPluginMessagePusher.Push(message) - you only receive messages pushed
        //from your plugin and not from other plugins/modules.
    }
};

this.tabs = [
    {
        File: "DataBrowser.html", //URL to fetch the tab contents from. Relative to the plugin WebRoot directory.
        ExternalTab: false,   //If True, 'File' is treated as an absolute URL to allow contents to be loaded from elsewhere. 
        //Note that the appropriate CORS headers are required on the hosting server to allow this.
        ShortName: "Analytics",    //Name used for the element. Prefixed with tab_PLUGINNAME_
        Name: "Analytics",     //Display name for the tab.
        Icon: "analytics",             //Icon to show in the tab.
        RequiredPermission: "Analytics.Analytics.AccessAnalytics",
        ViewModel: "AnalyticsDisplayVM",
        Click: () => { AnalyticsVM.Init(); },
    },
];

this.stylesheet = "Analytics.css";    //Styles for tab-specific styles

const AnalyticsVM = new AnalyticsDisplayVM();

function AnalyticsDataSummaryVM() {
    const self = this;
    this.stats = ko.observableArray();
    this.graphData = [];
    this.countryData = ko.observableArray();
    this.topPlayers = ko.observableArray();
    this.isDemo = ko.observable(false);
}

function AnalyticsFilterVM(field, value, displayValue, vm) {
    const self = this;
    this.Field = field;
    this.Value = value;
    this.DisplayValue = ko.observable(displayValue);
    this.ViewModel = vm;

    this.Remove = function () {
        self.ViewModel.Filters.remove(self);
    }
}

function AnalyticsDisplayVM() {
    //ViewModel for the Analytics tab
    const self = this;
    this.StartDate = ko.observable(new Date());
    this.PeriodDays = ko.observable(30);
    this.SummaryData = new AnalyticsDataSummaryVM();
    this.GraphSeries = [];
    this.Graph = null;
    this.Setup = false;
    this.ShowDemo = ko.observable(false);
    this.Filters = ko.observableArray();
    this.MapView = ko.observable(true);
    this.SuppressDemoPrompt = ko.observable(false);
    this.ShowAnalyticsDisable = ko.observable(false);
    this.ShowPersonalDataDisabled = ko.observable(false);

    this.ShowAnalyticsSetting = function () {
        currentSettings["AnalyticsPlugin.Privacy.EnableAnalytics"]?.highlight();
        currentSettings["AnalyticsPlugin.Privacy.UserSpecificData"]?.highlight();
    }

    this.RefreshNotices = function () {
        this.ShowAnalyticsDisable(!GetSetting("AnalyticsPlugin.Privacy.EnableAnalytics")); // && self.GraphSeries.length == 0);
        this.ShowPersonalDataDisabled(!GetSetting("AnalyticsPlugin.Privacy.UserSpecificData"));
    }

    this.AddFilter = function (field, value, displayValue = null) {
        //If there's a filter for the same field, change its value/displayValue rather than adding another filter.
        const existingFilter = self.Filters().find((f) => f.Field === field);
        if (existingFilter) {
            existingFilter.Value = value;
            existingFilter.DisplayValue(displayValue || value);
            self.LoadData();
        }
        else {
            self.Filters.push(new AnalyticsFilterVM(field, value, displayValue || value, self));
        }
    }

    this.GetFiltersAsObject = function () {
        //Create an object whose keys are the filter fields and values are the filter values
        const result = {};
        self.Filters().forEach((f) => result[f.Field] = f.Value);
        return result;
    };

    this.Init = async function () {
        self.RefreshNotices();
        if (self.Setup) { return; }
        await self.LoadData();
        //Check if the demo prompt has been suppressed
        const suppressDemoPrompt = localStorage.getItem("SuppressAnalyticsDemoPrompt") === "true";
        self.ShowDemo(self.SummaryData.isDemo() && !suppressDemoPrompt);
        this.PeriodDays.subscribe(() => self.LoadData());
        this.Filters.subscribe(() => self.LoadData());
        self.SetupMap();
    }

    this.DismissDemo = function () {
        self.ShowDemo(false);
        plausible("DismissAnalyticsDemo", { props: { forever: self.SuppressDemoPrompt() } });
        if (self.SuppressDemoPrompt()) {
            //Use LocalStorage to suppress the prompt permanently
            localStorage.setItem("SuppressAnalyticsDemoPrompt", true);
        }
    }

    this.ShortTimeFormat = function (val) {
        const date = new Date(val);
        const formatter = new Intl.DateTimeFormat('en-US', {
            month: 'short',
            day: 'numeric',
            hour: 'numeric',
            hour12: true
        });
        return formatter.format(date);
    };

    this.DrawGraph = function (data) {
        function weekendAreas(axes) {

            let markings = [],
                d = new Date(axes.xaxis.min);

            // go to the first Saturday

            d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
            d.setUTCSeconds(0);
            d.setUTCMinutes(0);
            d.setUTCHours(0);

            let i = d.getTime();

            // when we don't set yaxis, the rectangle automatically
            // extends to infinity upwards and downwards

            do {
                markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 }, color: "#ffffff08" });
                i += 7 * 24 * 60 * 60 * 1000;
            } while (i < axes.xaxis.max);

            return markings;
        }


        if (self.Setup) {
            self.Graph.setData([data]);
            let axes = self.Graph.getAxes()
            axes.xaxis.min = data[0][0].getTime();
            axes.xaxis.max = data[data.length - 1][0].getTime();
            axes.yaxis.max = Math.max(...data.map(point => point[1])) + 1;
            self.Graph.resize();
            self.Graph.setupGrid();
            self.Graph.draw();
            return;
        }
        self.Setup = true;

        const options = {
            canvas: true,
            grid: {
                borderWidth: 0,
                color: "rgba(255,255,255,0.2)",
                minBorderMargin: 0,
                margin: {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                },
                hoverable: true,
                clickable: true,
                autoHighlight: false,
                labelMargin: 10,
                markings: weekendAreas,
                mouseActiveRadius: 32,
            },
            series: {
                lines: {
                    show: true,
                    fill: true,
                    lineWidth: 3,
                    fillColor: { colors: ["#65CD7000", "#65CD70FF"] }
                },
                points: {
                    show: false
                },
                highlightColor: "#65CD70FF"
            },
            xaxis: {
                mode: "time",
                timeformat: "milliseconds",
                autoscale: "none",
                show: true,
                reserveSpace: true,
                tickFormatter: (val, axis) => AnalyticsVM.PeriodDays() < 8 ? self.ShortTimeFormat(new Date(val)) : new Date(val).toLocaleDateString(),
                color: "rgba(255,255,255,0.5)",
                font: {
                    size: 12,
                    fill: "rgba(255,255,255,0.5)"
                },
                minTickSize: 10,
            },
            yaxis: {
                ticks: 4,
                fill: true,
                reserveSpace: true,
                show: true,
                color: "rgba(255,255,255,0.5)",
                font: {
                    size: 12,
                    fill: "rgba(255,255,255,0.5)"
                },
                tickFormatter: (val, axis) => val < 1000 ? Math.floor(val) : (val / 1000).toFixed(2) + "k"
            },
            colors: ["#65CD70"]
        };

        self.Graph = $.plot("#analyticsMainGraph", [data], options);

        $("#analyticsMainGraph").bind("plothover", function (event, pos) {
            //Looking at the X position only, find the element in the data array that is closest to the X position and get both the data element and its index.
            const closest = self.Graph.getData()[0].data.reduce((prev, curr, index) => {
                const prevDiff = Math.abs(prev[0] - pos.x);
                const currDiff = Math.abs(curr[0] - pos.x);
                return (currDiff < prevDiff) ? [curr[0], curr[1], index] : prev;
            }, [Infinity, Infinity, -1]);
            const date = new Date(closest[0]);
            const count = closest[1];
            const formatter = new Intl.DateTimeFormat('en-US', {
                month: 'short',
                day: 'numeric',
                hour: 'numeric',
                hour12: true,
                weekday: 'short'
            });
            const formattedDate = formatter.format(date);
            $("#analyticsGraphTooltip .time").text(`${formattedDate}`);
            $("#analyticsGraphTooltip .value").text(`${count}`);

            //Position the tooltip at the Y position of the closest data point, but the X position of the mouse cursor. The tooltip is position:relative to the graph container.
            const graphPos = event.target.getBoundingClientRect();
            $("#analyticsGraphTooltip").css({
                top: self.Graph.pointOffset({ x: closest[0], y: closest[1] }).top + graphPos.top + 30,
                left: Math.max(Math.min(pos.pageX - 50, graphPos.right - 200), 32)
            });

            //Highlight the selected data point
            self.Graph.unhighlight();
            self.Graph.highlight(0, closest[2]);
        });

        //When the mouse leaves the graph, hide the highlight.
        $("#analyticsMainGraph").bind("mouseout", function () {
            self.Graph.unhighlight();
        });
    }

    this.LoadData = async function () {
        const data = await API.AnalyticsPlugin.GetAnalyticsSummaryAsync(self.PeriodDays(), self.StartDate(), self.GetFiltersAsObject());
        ko.quickmap.map(self.SummaryData, data);
        self.GraphSeries = self.SummaryData.graphData.map((g) => [parseDate(g.Date), g.Count]);
        self.DrawGraph(self.GraphSeries);
        self.DrawMap();
    };

    this.SetupMap = function () {
        const mapElements = "#analyticsMap g[id], #analyticsMap path[id]";
        $(mapElements).click((e) => {
            const country = e.currentTarget.id.toUpperCase();
            self.AddFilter("Country", country, self.GetCountryCodeName(country));
        });

        //When #analyticsMap is hovered over (use .on instead of .hover to allow for dynamic elements), show the tooltip.)
        $(mapElements).on("mousemove", (e) => {
            const country = e.currentTarget.id.toUpperCase();
            const countryData = self.SummaryData.countryData().find((c) => c.Country === country);
            if (!countryData || countryData.SessionCount == 0) {
                $("#analyticsMapTooltip").removeClass("show");
                return;
            }
            const percent = countryData.SessionsPercent;
            const friendlyValue = self.GetFriendlyValue(countryData.SessionCount);
            const flag = self.getFlagEmoji(country);
            $("#analyticsMapTooltip .country").text(`${flag} ${self.GetCountryCodeName(country)}`);
            $("#analyticsMapTooltip .value").text(`${friendlyValue} (${percent}%)`);
            $("#analyticsMapTooltip").css({
                top: e.pageY - 32,
                left: e.pageX + 32
            }).addClass("show");
        });
    };


    this.DrawMap = function () {
        const mapElements = "#analyticsMap g[style], #analyticsMap path[style]";
        $(mapElements).removeAttr("style");
        this.SummaryData.countryData().forEach((c) => {
            const highestPercent = Math.max(...this.SummaryData.countryData().map((c) => c.SessionTimePercent));
            const percentAsFraction = 0.2 + ((c.SessionTimePercent / highestPercent) * 0.8);
            const percentOpacityAsHex = Math.floor(percentAsFraction * 255).toString(16).padStart(2, 0);
            const color = `#6570CD${percentOpacityAsHex}`;
            $(`#analyticsMap #${c.Country.toLowerCase()}`).css("fill", color);
        });
    };

    this.GetFriendlyValue = function (value) {
        return value < 1000 ? value : (value / 1000).toFixed(2) + "k";
    };

    this.getFlagEmoji = function (countryCode) {
        switch (countryCode) {
            case "XX": return "🌍";
            case "XL": return "🖥️";
            case "XN": return "🏠";
        }

        const codePoints = countryCode
            .toUpperCase()
            .split('')
            .map(char => 127397 + char.charCodeAt());
        return String.fromCodePoint(...codePoints);
    }

    this.GetCountryCodeName = function (code) {
        const data = {
            "AF": "Afghanistan",
            "AX": "Aland Islands",
            "AL": "Albania",
            "DZ": "Algeria",
            "AS": "American Samoa",
            "AD": "Andorra",
            "AO": "Angola",
            "AI": "Anguilla",
            "AQ": "Antarctica",
            "AG": "Antigua And Barbuda",
            "AR": "Argentina",
            "AM": "Armenia",
            "AW": "Aruba",
            "AU": "Australia",
            "AT": "Austria",
            "AZ": "Azerbaijan",
            "BS": "Bahamas",
            "BH": "Bahrain",
            "BD": "Bangladesh",
            "BB": "Barbados",
            "BY": "Belarus",
            "BE": "Belgium",
            "BZ": "Belize",
            "BJ": "Benin",
            "BM": "Bermuda",
            "BT": "Bhutan",
            "BO": "Bolivia",
            "BA": "Bosnia And Herzegovina",
            "BW": "Botswana",
            "BV": "Bouvet Island",
            "BR": "Brazil",
            "IO": "British Indian Ocean Territory",
            "BN": "Brunei Darussalam",
            "BG": "Bulgaria",
            "BF": "Burkina Faso",
            "BI": "Burundi",
            "KH": "Cambodia",
            "CM": "Cameroon",
            "CA": "Canada",
            "CV": "Cape Verde",
            "KY": "Cayman Islands",
            "CF": "Central African Republic",
            "TD": "Chad",
            "CL": "Chile",
            "CN": "China",
            "CX": "Christmas Island",
            "CC": "Cocos (Keeling) Islands",
            "CO": "Colombia",
            "KM": "Comoros",
            "CG": "Congo",
            "CD": "Congo, Democratic Republic",
            "CK": "Cook Islands",
            "CR": "Costa Rica",
            "CI": "Cote D\"Ivoire",
            "HR": "Croatia",
            "CU": "Cuba",
            "CY": "Cyprus",
            "CZ": "Czech Republic",
            "DK": "Denmark",
            "DJ": "Djibouti",
            "DM": "Dominica",
            "DO": "Dominican Republic",
            "EC": "Ecuador",
            "EG": "Egypt",
            "SV": "El Salvador",
            "GQ": "Equatorial Guinea",
            "ER": "Eritrea",
            "EE": "Estonia",
            "ET": "Ethiopia",
            "FK": "Falkland Islands (Malvinas)",
            "FO": "Faroe Islands",
            "FJ": "Fiji",
            "FI": "Finland",
            "FR": "France",
            "GF": "French Guiana",
            "PF": "French Polynesia",
            "TF": "French Southern Territories",
            "GA": "Gabon",
            "GM": "Gambia",
            "GE": "Georgia",
            "DE": "Germany",
            "GH": "Ghana",
            "GI": "Gibraltar",
            "GR": "Greece",
            "GL": "Greenland",
            "GD": "Grenada",
            "GP": "Guadeloupe",
            "GU": "Guam",
            "GT": "Guatemala",
            "GG": "Guernsey",
            "GN": "Guinea",
            "GW": "Guinea-Bissau",
            "GY": "Guyana",
            "HT": "Haiti",
            "HM": "Heard Island & Mcdonald Islands",
            "VA": "Holy See (Vatican City State)",
            "HN": "Honduras",
            "HK": "Hong Kong",
            "HU": "Hungary",
            "IS": "Iceland",
            "IN": "India",
            "ID": "Indonesia",
            "IR": "Iran, Islamic Republic Of",
            "IQ": "Iraq",
            "IE": "Ireland",
            "IM": "Isle Of Man",
            "IL": "Israel",
            "IT": "Italy",
            "JM": "Jamaica",
            "JP": "Japan",
            "JE": "Jersey",
            "JO": "Jordan",
            "KZ": "Kazakhstan",
            "KE": "Kenya",
            "KI": "Kiribati",
            "KR": "Korea",
            "KP": "North Korea",
            "KW": "Kuwait",
            "KG": "Kyrgyzstan",
            "LA": "Lao People\"s Democratic Republic",
            "LV": "Latvia",
            "LB": "Lebanon",
            "LS": "Lesotho",
            "LR": "Liberia",
            "LY": "Libyan Arab Jamahiriya",
            "LI": "Liechtenstein",
            "LT": "Lithuania",
            "LU": "Luxembourg",
            "MO": "Macao",
            "MK": "Macedonia",
            "MG": "Madagascar",
            "MW": "Malawi",
            "MY": "Malaysia",
            "MV": "Maldives",
            "ML": "Mali",
            "MT": "Malta",
            "MH": "Marshall Islands",
            "MQ": "Martinique",
            "MR": "Mauritania",
            "MU": "Mauritius",
            "YT": "Mayotte",
            "MX": "Mexico",
            "FM": "Micronesia, Federated States Of",
            "MD": "Moldova",
            "MC": "Monaco",
            "MN": "Mongolia",
            "ME": "Montenegro",
            "MS": "Montserrat",
            "MA": "Morocco",
            "MZ": "Mozambique",
            "MM": "Myanmar",
            "NA": "Namibia",
            "NR": "Nauru",
            "NP": "Nepal",
            "NL": "Netherlands",
            "AN": "Netherlands Antilles",
            "NC": "New Caledonia",
            "NZ": "New Zealand",
            "NI": "Nicaragua",
            "NE": "Niger",
            "NG": "Nigeria",
            "NU": "Niue",
            "NF": "Norfolk Island",
            "MP": "Northern Mariana Islands",
            "NO": "Norway",
            "OM": "Oman",
            "PK": "Pakistan",
            "PW": "Palau",
            "PS": "Palestinian Territory, Occupied",
            "PA": "Panama",
            "PG": "Papua New Guinea",
            "PY": "Paraguay",
            "PE": "Peru",
            "PH": "Philippines",
            "PN": "Pitcairn",
            "PL": "Poland",
            "PT": "Portugal",
            "PR": "Puerto Rico",
            "QA": "Qatar",
            "RE": "Reunion",
            "RO": "Romania",
            "RU": "Russian Federation",
            "RW": "Rwanda",
            "BL": "Saint Barthelemy",
            "SH": "Saint Helena",
            "KN": "Saint Kitts And Nevis",
            "LC": "Saint Lucia",
            "MF": "Saint Martin",
            "PM": "Saint Pierre And Miquelon",
            "VC": "Saint Vincent And Grenadines",
            "WS": "Samoa",
            "SM": "San Marino",
            "ST": "Sao Tome And Principe",
            "SA": "Saudi Arabia",
            "SN": "Senegal",
            "RS": "Serbia",
            "SC": "Seychelles",
            "SL": "Sierra Leone",
            "SG": "Singapore",
            "SK": "Slovakia",
            "SI": "Slovenia",
            "SB": "Solomon Islands",
            "SO": "Somalia",
            "ZA": "South Africa",
            "GS": "South Georgia And Sandwich Isl.",
            "ES": "Spain",
            "LK": "Sri Lanka",
            "SD": "Sudan",
            "SR": "Suriname",
            "SJ": "Svalbard And Jan Mayen",
            "SZ": "Swaziland",
            "SE": "Sweden",
            "CH": "Switzerland",
            "SY": "Syrian Arab Republic",
            "TW": "Taiwan",
            "TJ": "Tajikistan",
            "TZ": "Tanzania",
            "TH": "Thailand",
            "TL": "Timor-Leste",
            "TG": "Togo",
            "TK": "Tokelau",
            "TO": "Tonga",
            "TT": "Trinidad And Tobago",
            "TN": "Tunisia",
            "TR": "Turkey",
            "TM": "Turkmenistan",
            "TC": "Turks And Caicos Islands",
            "TV": "Tuvalu",
            "UG": "Uganda",
            "UA": "Ukraine",
            "AE": "United Arab Emirates",
            "GB": "United Kingdom",
            "US": "United States",
            "UM": "United States Outlying Islands",
            "UY": "Uruguay",
            "UZ": "Uzbekistan",
            "VU": "Vanuatu",
            "VE": "Venezuela",
            "VN": "Vietnam",
            "VG": "Virgin Islands, British",
            "VI": "Virgin Islands, U.S.",
            "WF": "Wallis And Futuna",
            "EH": "Western Sahara",
            "YE": "Yemen",
            "ZM": "Zambia",
            "ZW": "Zimbabwe",
            "XX": "No Data",
            "XL": "Localhost",
            "XN": "Local Network",
        };
        return data[code] || "Unknown";
    };
}