MH - Rank-up Forecaster (v2.0)

Records wisdom data over time and predict rank-ups!

// ==UserScript==
// @name         MH - Rank-up Forecaster (v2.0)
// @version      1.0.14
// @description  Records wisdom data over time and predict rank-ups!
// @author       Chromatical
// @match        https://www.mousehuntgame.com/*
// @match        https://apps.facebook.com/mousehunt/*
// @icon         https://www.google.com/s2/favicons?domain=mousehuntgame.com
// @require      https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.2/chart.min.js
// @require      https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@next/dist/chartjs-adapter-date-fns.bundle.min.js
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @namespace https://greasyfork.org/users/748165
// ==/UserScript==
var debug = localStorage.getItem("Chro.debug") == 1? true : false;

//Data--------------------------
const title = ['Novice','Recruit','Apprentice','Initiate','Journeyman','Master','Grandmaster','Legendary','Hero','Knight','Lord','Baron','Count','Duke','Grand Duke','Archduke','Viceroy','Elder','Sage','Fabled'];
const wisdomRequired = [0,2000,5000,12500,31250,65440,137813,303188,667013,1467428,3228341,7102349,15625168,34375370,75625813,166376789,366028936,805263659,1771580048,3897476106];
const currentTitle = genderNeutral(user.title_name);
const currentPercent = user.title_percent_accurate;
const nextTitle = title[title.indexOf(currentTitle)+1];
//Data end-----------------------
(function(){
    addEventListener();
    /*global chart*/

   GM_addStyle(`
    .forecaster-chart-wrapper {
      position: fixed;
      left: 36vw;
      top: 48vh;
      background: rgba(255, 255, 255, 1);
      border: thin solid grey;
      z-index: 9999
    }
  `);
})()

function genderNeutral(name){
    var rankNeutral = name;
    name.includes("Journey") ? rankNeutral = "Journeyman/Journeywoman" : null;
    (name.includes("Lord") || name.includes("Lady"))? rankNeutral = "Lord/Lady" : null;
    name.includes("Du")? rankNeutral = "Duke/Duchess" : null;
    name.includes("Grand Du")? rankNeutral = "Grand Duke/Grand Duchess" : null;
    name.includes("Arch")? rankNeutral = "Archduke/Archduchess" : null;
    return rankNeutral
}

$(document).ajaxComplete(async(event,xhr,options) => {
    if(options.url == "https://www.mousehuntgame.com/managers/ajax/users/userInventory.php" ||
      options.url =="https://www.agiletravels.com/uuid.php"||
      options.url == "https://www.agiletravels.com/intake.php" ||
      options.url == "https://www.mousehuntgame.com/managers/ajax/users/getmiceeffectiveness.php"
      ){
         if (debug) console.log("WF - URL useless, ignored")
    } else if (options.url == "https://www.mousehuntgame.com/managers/ajax/users/changeenvironment.php"){
        const promise1 = await wisdomCheck("bypass")
        .then(
            res =>{
                if (debug) console.log("Travel so all bypassed and recorded!")
            })
    } else {
        var storageTime = localStorage.getItem("Chro-forecaster-time");
        const promise = await wisdomCheck()
        .then(
            res =>{
                if (debug) console.log("WF-Check passed! Recording Now")
            },
            err =>{
                if (debug) console.log("WF-Not time yet!")
            })
        }
});

$(document).ready(function(){
    wisdomCheck()
    var storageTime = localStorage.getItem("Chro-forecaster-time")
    if (storageTime){
        if (JSON.parse(storageTime).length == 0){
            localStorage.removeItem("Chro-forecaster-time")
        }
    }
})

function wisdomCheck(status){
    return new Promise(async(resolve,reject) =>{
        var storageTime = localStorage.getItem("Chro-forecaster-time")
        if (storageTime === null){
            var storageTimeParsed = [];
            var last = 0;
            var lastGetTime = 0;
            var currentTime = new Date()
        } else {
            storageTimeParsed = JSON.parse(storageTime)
            last = storageTimeParsed.length -1;
            lastGetTime = storageTimeParsed[last][0]
            currentTime = new Date()
        }
        if (storageTimeParsed.length <4){
            var interval = 3600000
            } else if (storageTimeParsed.length <8){
                interval = 43200000
            } else if (storageTimeParsed.length <12){
                interval = 86400000
            } else {
                interval = 172800000
            }
        if (debug) interval = 120000;
        var previousTimeISO = (new Date(lastGetTime)).getTime()
        var currentTimeISO = currentTime.getTime()
        if (debug) console.log("WF - Interval Differene is " + (currentTimeISO - previousTimeISO))
        if (currentTimeISO - previousTimeISO >= interval || status == "bypass"){
            if (debug && status != "bypass") console.log ("WF - Bigger Interval");
            if (status == "bypass") console.log ("Time check bypassed")
            const promise = await getWisdom()
            .then(
                async res=>{
                    var wisdom = res;
                    //if (status != "bypass"){
                        var oldArray = storageTimeParsed;
                       oldArray.push([currentTime,wisdom]);
                      //  while (oldArray.length > 14){
                        //    oldArray.shift()
                       // }
                        localStorage.setItem("Chro-forecaster-time",JSON.stringify(oldArray));
                    //}
                    const promise2 = await getTotalCalls()
                    .then(
                        res=>{
                            var calls = res;
                            var callArray = localStorage.getItem("Chro-forecaster-current-area")
                            if (callArray){
                                var parsedCallArray = JSON.parse(callArray);
                                //If same name with the current Area
                                if (parsedCallArray[0] == user.environment_name || status == "bypass"){
                                    if (status == "bypass") console.log ("Area checked bypassed")
                                    //Check whether the difference between now and past horns > 0
                                    if (calls-parsedCallArray[2] > 0) {
                                        //Check whether there is a global Area
                                        var areaArray = localStorage.getItem("Chro-forecaster-all-area");
                                        //If got global area
                                        if (areaArray){
                                            var parsedAreaArray = JSON.parse(areaArray)
                                            //Check whether there has already been records of current area
                                            var i = 0;
                                            while (i<parsedAreaArray.length){
                                                if (debug) console.log("WF-There are past records of current area")
                                                //By bypass
                                                if (status == "bypass"){
                                                    if (parsedAreaArray[i][0] == parsedCallArray[0]){
                                                        if (debug) console.log ("WF-Deleting bypass past data");
                                                        parsedAreaArray.splice(i,1);
                                                    } else {
                                                        i++
                                                    }
                                                 //Not by bypass
                                                } else {
                                                    if (parsedAreaArray[i][0] == user.environment_name){
                                                        if (debug) console.log ("WF-Deleting past data");
                                                        parsedAreaArray.splice(i,1);
                                                    } else {
                                                        i++
                                                    }
                                                }
                                            }
                                            if (debug) console.log ("WF-Writing new data")
                                            parsedAreaArray.push([parsedCallArray[0],wisdom-parsedCallArray[1],calls-parsedCallArray[2]])
                                            localStorage.setItem("Chro-forecaster-all-area",JSON.stringify(parsedAreaArray))
                                        //If no global array, write one
                                        } else {
                                            if (debug) console.log("WF-Writing new global array")
                                            var newAreaArray = [[parsedCallArray[0],wisdom-parsedCallArray[1],calls-parsedCallArray[2]]]
                                            localStorage.setItem("Chro-forecaster-all-area",JSON.stringify(newAreaArray))
                                        }
                                    } else if (debug) {console.log("WF-Hunt difference too small")}
                                    if (status == "bypass"){
                                        //bypassed so still need to re-record area
                                        console.log ("bypassed check, so re-record")
                                        callArray = [user.environment_name,wisdom,calls]
                                        localStorage.setItem("Chro-forecaster-current-area",JSON.stringify(callArray))
                                    }
                                } else {
                                    // If different area, re-record
                                    if (debug) console.log ("WF-Area changed! Recording new Area")
                                    callArray = [user.environment_name,wisdom,calls]
                                    localStorage.setItem("Chro-forecaster-current-area",JSON.stringify(callArray))
                                }
                            } else {
                                // if no call array
                                callArray = [user.environment_name,wisdom,calls]
                                localStorage.setItem("Chro-forecaster-current-area",JSON.stringify(callArray))
                            }
                        })
                    resolve()
                }
            )
            }
    })
}

function getWisdom(){
    return new Promise(async(resolve,reject) =>{
        postReq("https://www.mousehuntgame.com/managers/ajax/users/userInventory.php",`sn=Hitgrab&hg_is_ajax=1&item_types%5B%5D=wisdom_stat_item&action=get_items&uh=${user.unique_hash}`)
        .then(res=>{
            try{
                var data = JSON.parse(res.responseText);
                if (data){
                    var wisdom = data.items[0].quantity
                    resolve(wisdom);
                    }
            } catch (error){
                console.log(error)
            }
        })
    })
}

function getTotalCalls(){
    return new Promise(async(resolve,reject) =>{
        postReq("https://www.mousehuntgame.com/managers/ajax/users/userData.php",`sn=Hitgrab&hg_is_ajax=1&uh=${user.unique_hash}&sn_user_ids%5B%5D=${user.sn_user_id}&fields%5B%5D=num_total_turns`)
        .then(res=>{
            try{
                var data = JSON.parse(res.responseText);
                if (data){
                    var snuid = user.sn_user_id;
                    var calls = data.user_data[snuid].num_total_turns //parseInt(data.page.tabs.profile.subtabs[0].num_total_horn_calls.replace(",","").match(/\d+/g))
                    resolve(calls);
                    }
            } catch (error){
                console.log(error)
            }
        })
    })
}

function addEventListener(){
    var clickPoint;
    ($(".mousehuntHud-userStat-row.points>.label")[0])? clickPoint = $(".mousehuntHud-userStat-row.points>.label")[0] : clickPoint = $(".hudstatlabel")[7];
    //const clickPoint = $(".mousehuntHud-userStat-row.points>.label")[0]
    clickPoint.style.cursor = "pointer"
    clickPoint.addEventListener("click",()=>{
        forecastBox();
    })
}

async function forecastBox(){
    document
     .querySelectorAll("#chro-forecast-div")
     .forEach(el=> el.remove())

    const mainDiv = document.createElement("div")
    mainDiv.id = "chro-forecast-div"
    mainDiv.style.cursor = "default"
    $(mainDiv).css({
        "background-color": "#F5F5F5",
        "position": "fixed",
        "z-index": "9999",
        "left": "35vw",
        "top": "20vh",
        "border": "solid 3px #696969",
        "border-radius": "20px",
        "padding": "10px",
        "text-align": "center",
        "font-size": "12px",
        "min-width": "177px"
    })

    const headerDiv = document.createElement("div")
    headerDiv.id = "main-header-div"
    headerDiv.innerText = "Rank-up Forecaster"
    $(headerDiv).css({
        "float": "left",
        "font-weight":"bold",
        "width":"120px",
        "padding-top": "4px"
    })

    const contentDiv = document.createElement("div");
    contentDiv.id = "forecaster-content-div"
    $(contentDiv).css({
        "padding-top":"6px"
    })


    const btnDiv = document.createElement("div")
    btnDiv.id = "forecast-button-Div"
    $(btnDiv).css({
    })

    const minBtn = document.createElement("button")
    minBtn.id ="forecast-min-btn"
    minBtn.className = "forecaster-main-btn"
    minBtn.innerText = "-"
    minBtn.onclick = () =>{
        if (minBtn.innerText == "-"){
            contentDiv.style.display = "none"
            minBtn.innerText = "+"
        } else {
            contentDiv.style.display = ""
            minBtn.innerText = "-"
        }
    }

    const closeBtn = document.createElement("button")
    closeBtn.id = "forecast-close-btn"
    closeBtn.className = "forecaster-main-btn"
    closeBtn.innerText = "x"
    closeBtn.onclick = ()=>{
        document.body.removeChild(mainDiv);
        var historybox = $("#chro-forecast-history-div")[0]
        var canvasbox = $(".forecaster-chart-wrapper")[0]
        var locationbox = $("#chro-forecast-location-div")[0]
        if(historybox) document.body.removeChild($("#chro-forecast-history-div")[0]);
        if(canvasbox) document.body.removeChild($(".forecaster-chart-wrapper")[0]);
        if(locationbox) document.body.removeChild($("#chro-forecast-location-div")[0]);
    };
    [$(minBtn),$(closeBtn)].forEach(el=>{el.css({"margin-left":"5px"})});

    const titleTable = document.createElement("table");
    titleTable.id = "forecaster-table"
    $(titleTable).css({
        "border-spacing":"6px 3px"
    })

    const currentRow = document.createElement("tr")
    const currentHeaderData = document.createElement("td");
    currentHeaderData.innerText = "Current:"
    const currentTitleData = document.createElement("td");
    currentTitleData.innerText = currentTitle + " (" + currentPercent + "%)"

    const wisdomRow = document.createElement("tr")
    const wisdomHeaderData = document.createElement("td");
    wisdomHeaderData.innerText = "Wisdom:"
    const data = localStorage.getItem("Chro-forecaster-time")
    if(data){
        const parsedData = JSON.parse(data)
        var last = parsedData.length-1
        var wisdom = parsedData[last][1]
        } else {
            wisdom = 0
        }
    const wisdomTitleData = document.createElement("td");
    wisdomTitleData.innerText = numberWithCommas(wisdom);

    const nextRow = document.createElement("tr");
    const nextHeaderData = document.createElement("td");
    nextHeaderData.innerText = "Predict:"
    const nextTitleData = document.createElement("td");
    const titleList = document.createElement("select")
    titleList.style.width = "103px"
    titleList.id = "chro-forecaster-title-list"
    var a = title.indexOf(currentTitle)
    title.forEach(el=>{
        if (title.indexOf(el)>a || el == "Fabled"){
            var options = document.createElement("OPTION");
            options.innerText = el;
            titleList.appendChild(options);
        }
    });
    var selectedRank = titleList.value;
    var pointsRequired = wisdomRequired[title.indexOf(titleList.value)]
    titleList.onchange = async function processTitle(e){
        selectedRank = titleList.value;
        pointsRequired = wisdomRequired[title.indexOf(titleList.value)]
        var text = await getPredictDate(pointsRequired)
        .then(
           res => {
               $("#Chro-forecaster-forecasted-data")[0].innerText = res;
               if ($("#my-canvas")[0]){
                   renderChart(pointsRequired,selectedRank)
               }
           })
    }
    nextTitleData.appendChild(titleList);

    const timeRow = document.createElement("tr")
    const timeHeaderData = document.createElement("td");
    timeHeaderData.innerText = "Time:"
    const timeTitleData = document.createElement("td");
    timeTitleData.id = "Chro-forecaster-forecasted-data"
    timeTitleData.style.width = "10px"
    var p = await getPredictDate(pointsRequired)
    .then(
        res=> {timeTitleData.innerText = res}
        );
    //timeTitleData.innerText = inner_text;

     [$(currentHeaderData),$(wisdomHeaderData),$(nextHeaderData),$(timeHeaderData)].forEach(el=>{el.css({
         "font-weight": "bold",
         "text-align" : "right"
     })});

    const contButtonDiv = document.createElement("div")
    contButtonDiv.id = "forecast-content-button-div"

    const historyBtn = document.createElement("button")
    historyBtn.id = "forecast-history-button"
    historyBtn.className = "forecaster-content-button"
    historyBtn.innerText = "History"
    historyBtn.onclick = () => {
        if($("#chro-forecast-history-div")[0]){
            document.body.removeChild($("#chro-forecast-history-div")[0])
        } else {
        renderHistoryBox()
        }
    }

    const chartBtn = document.createElement("button")
    chartBtn.id = "forecast-history-button"
    chartBtn.className = "forecaster-content-button"
    chartBtn.innerText = "Chart"
    chartBtn.style.marginLeft = "5px";
    chartBtn.onclick = async () => {
        if($(".forecaster-chart-wrapper")[0]){
            document.body.removeChild($(".forecaster-chart-wrapper")[0])
        } else {
        var p = await renderChart(pointsRequired,selectedRank).then(
            res => {
                var x = $("#Chro-forecaster-chart")[0];
                dragElement(x,x)
            })
        }
    }

    const locationBtn = document.createElement("button")
    locationBtn.id = "forecast-location-button"
    locationBtn.className = "forecaster-content-button"
    locationBtn.innerText = "Location"
    locationBtn.style.marginLeft = "5px";
    locationBtn.onclick = async () => {
        if($("#chro-forecast-location-div")[0]){
            document.body.removeChild($("#chro-forecast-location-div")[0])
        } else {
            renderLocationBox()
        }
    }

    btnDiv.appendChild(minBtn)
    btnDiv.appendChild(closeBtn)
    mainDiv.appendChild(headerDiv)
    mainDiv.appendChild(btnDiv)
    currentRow.appendChild(currentHeaderData)
    currentRow.appendChild(currentTitleData)
    wisdomRow.appendChild(wisdomHeaderData)
    wisdomRow.appendChild(wisdomTitleData)
    nextRow.appendChild(nextHeaderData)
    nextRow.appendChild(nextTitleData)
    timeRow.appendChild(timeHeaderData)
    timeRow.appendChild(timeTitleData)
    titleTable.appendChild(currentRow)
    titleTable.appendChild(wisdomRow)
    titleTable.appendChild(nextRow)
    titleTable.appendChild(timeRow)
    contentDiv.appendChild(titleTable)
    contButtonDiv.appendChild(historyBtn);
    contButtonDiv.appendChild(chartBtn);
    contButtonDiv.appendChild(locationBtn);
    contentDiv.appendChild(contButtonDiv);
    mainDiv.appendChild(contentDiv)
    document.body.appendChild(mainDiv)
    dragElement(mainDiv,headerDiv)
}

function renderLocationBox(){
    document
     .querySelectorAll("#chro-forecast-location-div")
     .forEach(el=> el.remove())

    const locationDiv = document.createElement("div")
    locationDiv.id = "chro-forecast-location-div"
    $(locationDiv).css({
        "background-color": "#F5F5F5",
        "position": "fixed",
        "z-index": "9999",
        "left": "52vw",
        "top": "20vh",
        "border": "solid 3px #696969",
        "border-radius": "20px",
        "padding": "10px",
        "text-align": "center",
        "font-size": "12px",
        "min-width": "177px"
    })
    const locationMainTable = document.createElement("table")
    locationMainTable.id = "location-table"
    $(locationMainTable).css({
        "border-spacing": "1em 6px",
        "border-collapse": "collapse",
    })

    const placeHeading = document.createElement("th")
    placeHeading.id = "Chro-forecaster-place-heading"
    placeHeading.innerText = "Location";
    const wisdomHeading = document.createElement("th")
    wisdomHeading.id = "Chro-forecaster-location-wisdom-heading"
    wisdomHeading.innerText = "Wisdom/Hunt";
    const percentHeading = document.createElement("th")
    percentHeading.id = "Chro-forecaster-location-percent-heading"
    percentHeading.innerText = "%+/Hunt";
    const huntNumberHeading = document.createElement("th")
    huntNumberHeading.id = "Chro-forecaster-location-hunt-heading"
    huntNumberHeading.innerText = "Hunts";

    [$(placeHeading),$(wisdomHeading),$(percentHeading),$(huntNumberHeading)].forEach(el=>{el.css({
         "font-weight": "bold",
         "text-align" : "center",
         "background-color": "#eaeef0",
         "padding": "3px",
         "padding-top": "3px",
         "padding-bottom": "3px",
         "border":"0.5px solid #696969",
     })});

    locationMainTable.appendChild(placeHeading);
    locationMainTable.appendChild(wisdomHeading);
    locationMainTable.appendChild(percentHeading);
    locationMainTable.appendChild(huntNumberHeading);

    const locationData = localStorage.getItem("Chro-forecaster-all-area")
    if (locationData){
        var parsedLocationData = JSON.parse(locationData);
        for (var i=0;i<parsedLocationData.length;i++){
            var row = document.createElement("tr")
            var location_data = document.createElement("td")
            location_data.innerText = parsedLocationData[i][0]
            var hunt_data = document.createElement("td");
            var total_wisdom = parsedLocationData[i][1];
            var total_hunts = parsedLocationData[i][2];
            hunt_data.innerText = (total_wisdom/total_hunts).toFixed(2)
            var percent_data = document.createElement("td");
            var a = wisdomRequired[title.indexOf(currentTitle)];
            percent_data.innerText = ((total_wisdom/total_hunts)/a).toFixed(6) + "%";
            var hunt_number_data = document.createElement("td");
            hunt_number_data.innerText = total_hunts;

            if (i%2 ==0){
            [$(location_data),$(hunt_data),$(percent_data),$(hunt_number_data)].forEach(el=>{el.css({
            "text-align":"right",
            "border":"0.5px solid #696969",
            "padding":"3px",
            })})
        } else {
            [$(location_data),$(hunt_data),$(percent_data),$(hunt_number_data)].forEach(el=>{el.css({
            "text-align":"right",
            "border":"0.5px solid #696969",
            "padding":"3px",
            "background-color":"white"
            })})
        }
            row.appendChild(location_data);
            row.appendChild(hunt_data);
            row.appendChild(percent_data)
            row.appendChild(hunt_number_data);
            locationMainTable.appendChild(row)
        }
    }
    locationDiv.appendChild(locationMainTable)
    document.body.appendChild(locationDiv)
    dragElement(locationDiv,locationDiv)
}

function renderChart(pointsRequired,rank){
 return new Promise((resolve, reject) => {
    document
     .querySelectorAll("#my-canvas")
     .forEach(el=> el.remove())

    const parsedData = JSON.parse(localStorage.getItem("Chro-forecaster-time"));
    const timeData = [];
    const wisdomData = [];
    parsedData.forEach(el=>{
        var d = new Date(el[0])
        var date_converted = d.getTime();
        timeData.push(date_converted)
    });
    parsedData.forEach(el=>{wisdomData.push(el[1])});

    const createElement = (tagName, config = {}) => {
        const el = document.createElement(tagName);
        if (config.attrs) Object.entries(config.attrs).forEach(([attr, val]) => el.setAttribute(attr, val));
        if (config.props) Object.entries(config.props).forEach(([prop, val]) => el[prop] = val);
        if (config.css) Object.entries(config.css).forEach(([prop, val]) => el.style[prop] = val);
        if (config.children) config.children.forEach(child => el.append(child));
            return el;
        };

    function main() {
        const chartMain = document.body.prepend(createElement('div', {
            props: {
                className: 'forecaster-chart-wrapper',
                id: 'Chro-forecaster-chart'
            },
            children: [
                createElement('canvas', {
                    props: {
                        height: "200",
                        width: "400"
                    },
                    attrs: { id: 'my-canvas' }
                })
            ]
        }));

        const historyChartData = []
        for (var i=0;i<wisdomData.length;i++){
            historyChartData.push({
                x: timeData[i],
                y: wisdomData[i]
            });
        }

        const bfChartData = []
        bfChartData.push({
            x:timeData[0],
            y:wisdomData[0]
        });
        var last = timeData.length-1;
        var find_wisdom = findLineByLeastSquares(timeData,wisdomData)
        var y_value = find_wisdom[0]*(timeData[last])+find_wisdom[1]
        bfChartData.push({
            x: timeData[last],
            y: y_value
        });

        const predictChartData = []
        predictChartData.push({
            x: timeData[0],
            y: wisdomData[0]
        });
        predictChartData.push({
            x: findLineByLeastSquares(timeData,wisdomData,pointsRequired)[2],
            y: pointsRequired
        });

        const chartData = [{
            label: 'History',
            data: historyChartData,
            borderWidth: 1,
            fill: false,
            borderColor: '#25c9f6',
            backgroundColor: '#4dc9f6',
            tension: 0.1,
        },{
            label: 'Best Fit',
            data: bfChartData,
            borderWidth: 1,
            fill: false,
            borderColor: '#9966FF',
            backgroundColor: '#9966FF',
            tension: 0.1
        },{
            label: rank,
            data: predictChartData,
            borderWidth: 1,
            fill: false,
            borderColor: '#953553',
            backgroundColor: '#953553',
            tension: 0.1
        }];

        const myChart = createSimpleLineChart('#my-canvas', chartData);

    }

    function createChart (canvas, settings) {
        new Chart((typeof canvas === 'string' ? document.querySelector(canvas) : canvas).getContext('2d'), settings)
    };

    function createSimpleLineChart (selector, chartData){
        const {data,label} = chartData;
        return createChart(selector, {
            type: 'line',
            data: {datasets: chartData},
            options: {
                scales: {
                    yAxes: [{
                        ticks: {
                            beginAtZero: false,
                        }
                    }],
                    x: {
                        type: 'time',
                    }
                },
            },
            plugins: {
                datalabels: {
                    color: 'blue'
                }
            }
        });
    };
    main();
    resolve();
 })
};

function renderHistoryBox(){
    document
     .querySelectorAll("#chro-forecast-history-div")
     .forEach(el=> el.remove())

    const historyDiv = document.createElement("div")
    historyDiv.id = "chro-forecast-history-div"
    $(historyDiv).css({
        "background-color": "#F5F5F5",
        "position": "fixed",
        "z-index": "9999",
        "left": "18vw",
        "top": "20vh",
        "border": "solid 3px #696969",
        "border-radius": "20px",
        "padding": "10px",
        "text-align": "center",
        "font-size": "12px",
        "min-width": "177px"
    })
    const historyMainTable = document.createElement("table")
    historyMainTable.id = "history-table"
    $(historyMainTable).css({
        "border-spacing": "1em 6px",
        "border-collapse": "collapse",
    })

    const tableBody = document.createElement("tbody");

    const data = localStorage.getItem("Chro-forecaster-time")
    const parsedData = JSON.parse(data)

    const timeHeading = document.createElement("th")
    timeHeading.id = "Chro-forecaster-time-heading"
    timeHeading.innerText = "Date  " + String.fromCharCode("0x23F7");
    timeHeading.style.cursor = "pointer";
    timeHeading.onclick = function(){
        if (timeHeading.innerText == "Date ⏷"){
            timeHeading.innerText = "Date  " + String.fromCharCode("0x23F6")
        } else {
            timeHeading.innerText = "Date  " + String.fromCharCode("0x23F7")
        };
        reverse(tableBody);
    };
    const wisdomHeading = document.createElement("th")
    wisdomHeading.id = "Chro-forecaster-wisdom-heading"
    wisdomHeading.innerText = "Wisdom+";
    const percentageHeading = document.createElement("th")
    percentageHeading.id = "Chro-forecaster-percentage-heading"
    percentageHeading.innerText = "%";
    const percentageIncreaseHeading = document.createElement("th")
    percentageIncreaseHeading.id = "Chro-forecaster-percentage-increase-heading"
    percentageIncreaseHeading.innerText = "%+";
    const removeHeading = document.createElement("th")
    removeHeading.id = "Chro-forecaster-remove-heading";

    [$(timeHeading),$(wisdomHeading),$(percentageHeading),$(percentageIncreaseHeading),$(removeHeading)].forEach(el=>{el.css({
         "font-weight": "bold",
         "text-align" : "center",
         "background-color": "#eaeef0",
         "padding": "3px",
         "padding-top": "3px",
         "padding-bottom": "3px",
         "border":"0.5px solid #696969",
     })});

    historyMainTable.appendChild(timeHeading)
    historyMainTable.appendChild(wisdomHeading)
    //historyMainTable.appendChild(percentageHeading)
    historyMainTable.appendChild(percentageIncreaseHeading)

    const dataLength = parsedData.length
    for (var i=0;i<parsedData.length;i++){
        const row = document.createElement("tr")
        row.id = "row"+i
        const time_value = parsedData[i][0]
        const wisdom_value = parsedData[i][1]

        var d = new Date(time_value)
        var hunt_date = formatDate(d)//d.getDate() + '/' + (d.getMonth()+1) //+ '/' + d.getFullYear()
        var hunt_time = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2)

        const time_data = document.createElement("td")
        time_data.className = "Chro-forecaster-time-data"
        time_data.innerText = hunt_date + " " + hunt_time

        const wisdom_data = document.createElement("td")
        wisdom_data.className = "Chro-forecaster-wisdom-data"
        var previous_wisdom = parsedData[Math.max(i-1,0)][1]
        var wisdom_difference = wisdom_value-previous_wisdom
        wisdom_data.innerText = numberWithCommas(wisdom_difference);
        $(wisdom_data).css({"text-align":"right"})

        const percentage_data = document.createElement("td")
        percentage_data.innerText = (wisdom_value/(wisdomRequired[title.indexOf(currentTitle)+1])*100).toFixed(2)+ "%"

        const percentage_increase_data = document.createElement("td")
        var percentage_increase = ((wisdom_value-previous_wisdom)*100/(previous_wisdom)).toFixed(2)
        percentage_increase_data.innerText = percentage_increase + "%"

        const remove_btn = document.createElement("button")
        remove_btn.innerText = "x";
        remove_btn.onclick =()=>{
            for (var i=0;i<parsedData.length;i++){
                var d = new Date(parsedData[i][0])
                var hunt_date = formatDate(d);//d.getDate() + '/' + (d.getMonth()+1) //+ '/' + d.getFullYear()
                var hunt_time = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2)
                var remove_time = hunt_date + " " + hunt_time
                if(remove_time == remove_btn.parentElement.children[0].innerText){
                    parsedData.splice([i],1);
                    tableBody.removeChild(remove_btn.parentElement);
                }
            }
            var unparsed_data = JSON.stringify(parsedData)
            localStorage.setItem("Chro-forecaster-time",unparsed_data);
        }
        $(remove_btn).css({
            "width": "15px",
            "height": "15px",
            "font-size": "10px",
            "text-align": "center",
            "padding": "0px",
            "background-color":"#F5F5F5",
            "border":"none",
            "border-color":"#F5F5F5",
            "cursor":"pointer"
        });
        if (i%2 ==0){
            [$(time_data),$(wisdom_data),$(percentage_increase_data)].forEach(el=>{el.css({
            "text-align":"right",
            "border":"0.5px solid #696969",
            "padding":"3px",
            })})
        } else {
            [$(time_data),$(wisdom_data),$(percentage_increase_data)].forEach(el=>{el.css({
            "text-align":"right",
            "border":"0.5px solid #696969",
            "padding":"3px",
            "background-color":"white"
            })})
        }

        row.appendChild(time_data)
        row.appendChild(wisdom_data)
        //row.appendChild(percentage_data)
        row.appendChild(percentage_increase_data)
        row.appendChild(remove_btn)
        tableBody.appendChild(row)
    }
    historyMainTable.appendChild(tableBody)
    historyDiv.appendChild(historyMainTable)
    document.body.appendChild(historyDiv)

    dragElement(historyDiv,historyDiv);
    reverse(tableBody);

}

function getPredictDate(points){
    return new Promise((resolve, reject) => {
        const parsedData = JSON.parse(localStorage.getItem("Chro-forecaster-time"));
        if (parsedData){
        const timeData = [];
        const wisdomData = [];
        parsedData.forEach(el=>{
            var d = new Date(el[0])
            var date_converted = d.getTime();
            timeData.push(date_converted)
        });
        parsedData.forEach(el=>{wisdomData.push(el[1])});
        //function end --------------
          var timeRequired = findLineByLeastSquares(timeData,wisdomData,points)[2]
        var d = new Date(timeRequired);
        resolve(d);
        } else {
            d = "Insufficient Data"
        }
    })
}

function formatDate(d){
    var allMonths = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
    var month = d.getMonth()+1
    var monthFormat = allMonths[Number(month) -1]
    var dateFormat = (d.getFullYear()).toString().substr(-2);
    return(d.getDate() + ' ' + monthFormat + ' ' + dateFormat);
}

function reverse(table){
    $(table).each(function(elem,index){
            var arr = $.makeArray($("tr",this).detach());
            arr.reverse();
            $(this).append(arr);
        });
}

function numberWithCommas(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

function findLineByLeastSquares(values_x, values_y,rank_value) {
    var x_sum = 0;
    var y_sum = 0;
    var xy_sum = 0;
    var xx_sum = 0;
    var count = 0;

    /*
     * The above is just for quick access, makes the program faster
     */
    var x = 0;
    var y = 0;
    var values_length = values_x.length;

    if (values_length != values_y.length) {
        throw new Error('The parameters values_x and values_y need to have same size!');
    }

    //Above and below cover edge cases
    if (values_length === 0) {
        return [ [], [] ];
    }

    //Calculate the sum for each of the parts necessary.
    for (var i = 0; i< values_length; i++) {
        x = values_x[i];
        y = values_y[i];
        x_sum+= x;
        y_sum+= y;
        xx_sum += x*x;
        xy_sum += x*y;
        count++;
    }

    // y = m*x + b
    var m = (count*xy_sum - x_sum*y_sum) / (count*xx_sum - x_sum*x_sum);
    var b = (y_sum/count) - (m*x_sum)/count;

    //x = (y-b)/m
    var time_value = (rank_value - b)/m

    return [m, b,time_value];
}


function dragElement(elmnt,dragEl) {
  var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  /*if (elmnt.firstElementChild) {
    // if present, the header is where you move the DIV from:
   elmnt.firstElementChild.onmousedown = dragMouseDown;
  } else {*/
    // otherwise, move the DIV from anywhere inside the DIV:
    dragEl.onmousedown = dragMouseDown;
  //}

  function dragMouseDown(e) {
    e = e || window.event;
    e.preventDefault();
    // get the mouse cursor position at startup:
    pos3 = e.clientX;
    pos4 = e.clientY;
    document.onmouseup = closeDragElement;
    // call a function whenever the cursor moves:
    document.onmousemove = elementDrag;
  }

  function elementDrag(e) {
    e = e || window.event;
    e.preventDefault();
    // calculate the new cursor position:
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;
    // set the element's new position:
    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  }

  function closeDragElement() {
    // stop moving when mouse button is released:
    document.onmouseup = null;
    document.onmousemove = null;
  }
}

function postReq(url, form) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open("POST", url, true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.onreadystatechange = function () {
        if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
          resolve(this);
        }
      };
      xhr.onerror = function () {
        reject(this);
      };
      xhr.send(form);
    });
}