Reversewigo solver ✓🐵

Reversewigo solver ✓🐵 - solve reverse wherigo automatically on cache page. Solver code originated from Rick Rickhardson's reverse-wherigo source (public domain). Also, creates show source button for mystery cacahes.

26.03.2023 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name        Reversewigo solver ✓🐵
// @namespace    http://tampermonkey.net/
// @version      0.16
// @description  Reversewigo solver ✓🐵  - solve reverse wherigo automatically on cache page. Solver code originated from Rick Rickhardson's reverse-wherigo source (public domain). Also, creates show source button for mystery cacahes.
// @author       [email protected]
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_setClipboard
// @include      https://*geocaching.com/geocache/*
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require      https://cdn.jsdelivr.net/gh/xxjapp/[email protected]/xdialog.js#sha256=sha256-r4yDdWZiML48gILBpDiF+c3/aq7ljwt7wRCtqzl6AvI=
// @resource   IMPORTED_CSS https://cdn.jsdelivr.net/gh/xxjapp/xdialog@3/xdialog.css
// @grant      GM_getResourceText
// @grant      GM_addStyle
//

// ==/UserScript==


// placeholder for calculated reverse wherigo coordinates
// used by pollDOM to update them to popup dialog
// TODO: definitely not the best practise but a quick hack that seems to work (for now)
var newCoordinates = "";

GM_registerMenuCommand("Configure", () => { GM_config.open() }, "C");
GM_registerMenuCommand("Set codes", () => { readCodes() }, "S");

GM_config.init(
    {
      'id': 'MyConfig', // The id used for this instance of GM_config
      'title': 'Script Settings', // Panel Title
      'fields': // Fields object
      {
        'OutputDD':
        {
        'label': 'Show dd (decimal degrees) format (60.12345 25.4321) coordinates in output', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        },
        'OutputCodes':
        {
        'label': 'Show wherigo codes in output', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        }
        ,
        'OutputDistance':
        {
        'label': 'Show distance to bogus in output', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        },
        'replaceInfoHTML':
        {
        'label': 'Replace HTML element with content? (default: only append)', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        },
        'infoHtmlElID':
        {
        'label': 'HTML element id for info', // Appears next to field
        'type': 'text', // Makes this setting a checkbox input
        'default': "ctl00_ContentBody_CacheInformationTable" // Default value if user doesn't change it
        },
        'replaceRegexHTML':
        {
        'label': 'Replace HTML element with regexp result? (default: only append)', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': false // Default value if user doesn't change it
        },
         'regexHtmlElID':
        {
        'label': ' HTML element id for regexp results', // Appears next to field
        'type': 'text', // Makes this setting a checkbox input
        'default': "ctl00_ContentBody_detailWidget" // Default value if user doesn't change it
        }
        ,
        'addBogusDDToPage':
        {
        'label': 'Show bogus in DD', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': true // Default value if user doesn't change it
        },
        'addMinMaxLatLonToPage':
        {
        'label': 'Show min/max lon/lat', // Appears next to field
        'type': 'checkbox', // Makes this setting a checkbox input
        'default': true // Default value if user doesn't change it
        }
      }
    });

/* async */ function dd2dm(latdd, londd) {
    var lat = latdd;
    var lon = londd;

    var latResult, lonResult, dmsResult;

    lat = parseFloat(lat);
    lon = parseFloat(lon);

    latResult = /* await */ lat_dd2dm(lat);
    lonResult = /* await */ lon_dd2dm(lon);

    // Joining both variables and separate them with a space.
    dmsResult = latResult + ' ' + lonResult;

    // Return the resultant string
    return { result: dmsResult, lat: latResult, lon: lonResult };
}

/* async */ function lat_dd2dm(val) {
    var valDeg, valMin, result;

    val = Number(val);
    result = (val >= 0)? 'N' : 'S';
    result += " ";

    val = Math.abs(val);
    valDeg = Math.floor(val);

    if( valDeg < 10 ) {
    result += "0" + valDeg;
    }
    else {
    result += valDeg;
    }
    result += "\xB0";
    result += " ";

    valMin = ((val - valDeg) * 60).toFixed(3);
    if( valMin < 10) {
        result += "0" + valMin;
    }
    else {
        result += valMin;
    }
    return result;
}

/* async */ function lon_dd2dm(val) {
    var valDeg, valMin, result;

    val = Number(val);

    result = (val >= 0)? 'E' : 'W';
    result += " ";

    val = Math.abs(val);
    valDeg = Math.floor(val);

    if( valDeg < 10 ) {
        result += "00" + valDeg;
    }
    else if( valDeg < 100) {
        result += "0" + valDeg;
    }
    else {
        result += valDeg;
    }
    result += "\xB0";
    result += " ";

    valMin = ((val - valDeg) * 60).toFixed(3);
    if( valMin < 10) {
        result += "0" + valMin;
    }
    else {
        result += valMin;
    }
    return result;
}

// calculate minimum possible latitude (3200m south from bogus)
// @return latitude in DD
function minLat() {
    bogusDD = getBogusCoordsDD();
    console.log( JSON.stringify( bogusDD ) );
    res = haversineProjection( bogusDD.lat, bogusDD.lon, 180, 3200 );
    console.log( JSON.stringify(res) );
    return res[0];
}
// calculate maximum possible latitude (3200m north from bogus)
// @return latitude in DD
function maxLat() {
    bogusDD = getBogusCoordsDD();
    res = haversineProjection( bogusDD.lat, bogusDD.lon, 0, 3200 );
    return res[0];
}
// calculate minimum possible longitude (3200m west from bogus)
// @return longitude in DD
function minLon() {
    bogusDD = getBogusCoordsDD();
    res = haversineProjection( bogusDD.lat, bogusDD.lon, 270, 3200 );
    return res[1];
}
// calculate maximum possible longitude (3200m east from bogus)
// @return longitude in DD
function maxLon() {
    bogusDD = getBogusCoordsDD();
    res = haversineProjection( bogusDD.lat, bogusDD.lon, 90, 3200 );
    return res[1];
}


/* async */ function code2LatLon(varA, varB, varC) {
    let latSign, lonSign, lonValue, latValue;

    console.debug("Converting [" + varA + ", " + varB + ", " + varC + "] to LatLon" )

    // 123456 => digit 1 (d1) = 6; digit 2 (d2) = 5; ...
    // syntax for varA => digit 1 var A = A1; digit 2 varA = A2; ...
    // A3
    if ((varA % 1000 - varA % 100) / 100 == 1) {
        latSign = 1;
        lonSign = 1;
    }
    // A3
    else if ((varA % 1000 - varA % 100) / 100 == 2) {
        latSign = -1;
        lonSign = 1;
    }
    // A3
    else if ((varA % 1000 - varA % 100) / 100 == 3) {
        latSign = 1;
        lonSign = -1;
    }
    // A3
    else if ((varA % 1000 - varA % 100) / 100 == 4) {
        latSign = -1;
        lonSign = -1;
    }
//T41140 / Q1TQ01 / 14S4RS
    // A6 B3 B4 B6 C1 C2 C4
    // TODO: how to iterate only these, not full range ??
    // C (d5 + d2) eli C5 + C2 = parillinen
    if ( ((varC % 100000 - varC % 10000) / 10000 + (varC % 100 - varC % 10) / 10) % 2 === 0) {
        // A4 B2  B5 C3 A6 C2 A1
        latValue = Number(((varA % 10000 - varA % 1000) / 1000 * 10 + (varB % 100 - varB % 10) / 10 + (varB % 100000 - varB % 10000) / 10000 * 0.1 + (varC % 1000 - varC % 100) / 100 * 0.01 + (varA % 1000000 - varA % 100000) / 100000 * 0.001 + (varC % 100 - varC % 10) / 10 * 1.0E-4 + varA % 10 * 1.0E-5));
        // A5 C6 C1  B3 B6 A2 C5 B1
        lonValue = Number(((varA % 100000 - varA % 10000) / 10000 * 100 + (varC % 1000000 - varC % 100000) / 100000 * 10 + varC % 10 + (varB % 1000 - varB % 100) / 100 * 0.1 + (varB % 1000000 - varB % 100000) / 100000 * 0.01 + (varA % 100 - varA % 10) / 10 * 0.001 + (varC % 100000 - varC % 10000) / 10000 * 1.0E-4 + varB % 10 * 1.0E-5));
    }
    // C (d5 + d2) eli C5+C2= pariton
    else if ( ((varC % 100000 - varC % 10000) / 10000 + (varC % 100 - varC % 10) / 10) % 2 !== 0) {
        // B6 A1  A4 C6 C3 C2 A6
        latValue = Number(((varB % 1000000 - varB % 100000) / 100000 * 10 + varA % 10 + (varA % 10000 - varA % 1000) / 1000 * 0.1 + (varC % 1000000 - varC % 100000) / 100000 * 0.01 + (varC % 1000 - varC % 100) / 100 * 0.001 + (varC % 100 - varC % 10) / 10 * 1.0E-4 + (varA % 1000000 - varA % 100000) / 100000 * 1.0E-5))
        // B2 C1 A2  A5 B3 B1 C5 B5
        lonValue = Number(((varB % 100 - varB % 10) / 10 * 100 + varC % 10 * 10 + (varA % 100 - varA % 10) / 10 + (varA % 100000 - varA % 10000) / 10000 * 0.1 + (varB % 1000 - varB % 100) / 100 * 0.01 + varB % 10 * 0.001 + (varC % 100000 - varC % 10000) / 10000 * 1.0E-4 + (varB % 100000 - varB % 10000) / 10000 * 1.0E-5));
    }
    // B4 C4 = ALWAYS ignore

    latValue = latSign * latValue;
    lonValue = lonSign * lonValue;

    return { lat: latValue, lon: lonValue }
}

function fix(v, left, right) {
    return (v < 0 ? '-' : ' ') + Math.abs(v).toFixed(right).padStart(left + right + 1, '0');
}

function haversineDistance(lat1,lon1,lat2,lon2) {
    const R = 6371e3; // metres
    const φ1 = lat1 * Math.PI/180; // φ, λ in radians
    const φ2 = lat2 * Math.PI/180;
    const Δφ = (lat2-lat1) * Math.PI/180;
    const Δλ = (lon2-lon1) * Math.PI/180;

    const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ/2) * Math.sin(Δλ/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    const d = R * c; // in metres
    return d;
  }

const degrees_to_radians = deg => (deg * Math.PI) / 180.0;
const radians_to_degrees = rad => (rad * 180.0) / Math.PI;

// φ is latitude, λ is longitude,
function haversineProjection(lat1, lon1, degr, dist) {
    const R = 6371e3; // metres

    const φ1 = lat1 * Math.PI/180; // φ, λ in radians
    const λ1 = lon1 * Math.PI/180;
    θ = degrees_to_radians(degr);
    d = dist;

    const δ = d/R;
    const Δφ = δ * Math.cos(θ);
    const φ2 = φ1 + Δφ;

    const Δψ = Math.log(Math.tan(φ2/2+Math.PI/4)/Math.tan(φ1/2+Math.PI/4));
    const q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1); // E-W course becomes ill-conditioned with 0/0

    const Δλ = δ*Math.sin(θ)/q;
    const λ2 = λ1 + Δλ;

    // check for some daft bugger going past the pole, normalise latitude if so
    if (Math.abs(φ2) > Math.PI/2) φ2 = φ2>0 ? Math.PI-φ2 : -Math.PI-φ2;

    lat2 = φ2 / Math.PI * 180;
    lon2 = λ2 / Math.PI * 180;
    return [ lat2 , lon2 ]
}

  function dm2dd( nsew, deg, min ) {
      let sign;
      if( nsew == 'N' || nsew == 'E' ) {
          sign = 1;
      }
      else {
          sign = -1;
      }

      let numdeg = parseInt(deg);
      let nummin = parseFloat(min);

      let dd = sign * (numdeg + nummin/60);
      return dd;
  }

function escapeHtml(str)  {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}

function createShowSourceButton() {
    let mybutton = document.createElement("srcbutton");
    mybutton.innerHTML = "View source";
    mybutton.setAttribute("style", "-webkit-appearance: button;-moz-appearance: button;appearance: button;  background-color: green; color: white; border: none; text-align: center;  display: inline-block; padding: 3px 10px; border: 1px solid black; cursor: pointer;");
    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    noteref.appendChild(mybutton);

    mybutton.addEventListener("click", function() {
        var desc1 = document.getElementById("ctl00_ContentBody_ShortDescription").innerHTML;
        var desc2 = document.getElementById("ctl00_ContentBody_LongDescription").innerHTML;
        xdialog.open({
            title: 'UserSuppliedContent from source',
            body: '\
            <div style="height: auto; border: 1px solid green; padding: 0.5em;">\
            <pre>'+escapeHtml(desc1)+"\n"+escapeHtml(desc2)+
            '</pre>\
            </div>',
            style: 'width: 80%;'
        });
//        alert( desc1 + desc2 )
    });
}

function createShowSolveREReverseButton() {
//    let mybutton = document.createElement("solvebutton");
    let mybutton = document.createElement("a");
    mybutton.innerHTML = "Solve Regexp";

    mybutton.className = "mybtn"
    //css
    mybutton.setAttribute("style", "-webkit-appearance: button;-moz-appearance: button;appearance: button;  background-color: green; color: white; border: none; text-align: center;  display: inline-block; padding: 3px 10px; border: 1px solid black; cursor: pointer;");


    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    noteref.appendChild(mybutton);

    mybutton.addEventListener("click", function() {
        solveReverseRE();
    });

    /*
    mybutton.addEventListener("click", function() {
        $( function() {
            $( "#dialog" ).dialog({width: "auto"})
            .next(".ui-widget-overlay")
            .css({
                opacity: 1.0,
                filter: "Alpha(Opacity=100)",
                backgroundColor: "black"
            });
          } );
        solveReverseRE();
    });
    */
}

function createDialog( ) {
    let mydialog = document.createElement("div");
    mydialog.id = "dialog";
    mydialog.title = "Results";
    let resultmsg = document.createElement("p");
    resultmsg.id = "resultmsg";
    mydialog.appendChild(resultmsg);

    // regexHtmlElID by default ctl00_ContentBody_detailWidget
    let noteref = document.getElementById( GM_config.get('regexHtmlElID') );

    let replace = GM_config.get('replaceInfoHTML')
    if( replace ) {
        noteref = mydialog;
    }
    else {
       noteref.appendChild(mydialog);
    }

}

// TODO: definitely not the best practice, but works (for now)
function pollDOM() {
    const newCrd = document.getElementById('newCoordinates');

    if (newCrd != null) {
      // Do something with el
      newCrd.value = newCoordinates;

      let submitBtn = document.getElementsByClassName("btn-cc-parse")[0];
      //ssubmitBtn.click();
      } else {
      setTimeout(pollDOM, 300); // try again in 300 milliseconds
    }
}

function updateCoordinates( newCoords ) {
    let crd = document.getElementById("uxLatLonLink");
    crd.click();

    newCoordinates = newCoords;

    pollDOM();
    console.debug("XX: " + newCoords);
}

// retrieve bogus coordinates in ddm
// returns array [ lat side (N/S), lat deg, lat mindec, lon side (E/W), lon deg, lon mindec ];
function getBogusCoordsFromPage() {
    let coords = document.getElementById("uxLatLon").textContent.split(" ");
    if( coords.length != 6 ) {
        console.error("Error parsing coordinates");
        return;
    }
    return coords;
}

function getBogusCoordsDD() {
    let bogusLatLonArr = getBogusCoordsFromPage();
    // convert bogus coordinates from deg-min-dec to deg-dec
    let bogusDD = latLonDmArr2dd(bogusLatLonArr);
    return bogusDD;
}

// convert latitude longitude array [ lat side (N/S), lat deg, lat mindec, lon side (E/W), lon deg, lon mindec ] to dd
// return { lat: latitude dd , lon: longitude dd }
function latLonDmArr2dd( latLonDmArray ) {
    // convert bogus coordinates from deg-mindec to deg-dec
    let bogusLat = dm2dd( latLonDmArray[0], latLonDmArray[1], latLonDmArray[2] );
    let bogusLon = dm2dd( latLonDmArray[3], latLonDmArray[4], latLonDmArray[5] );
    return { lat : bogusLat, lon : bogusLon }
}

// htmlsnip = html snippet in string
// replace = boolean; true = replace existing html fully
function addHTMLtoPage( htmlsnip ) {
//    let docEl = document.getElementsByClassName("Note Disclaimer")[0];
    //let docEl = document.getElementById("ctl00_ContentBody_CacheInformationTable");
    let docEl = document.getElementById( GM_config.get('infoHtmlElID') );

    let replace = GM_config.get('replaceInfoHTML')
    if( replace ) {
        docEl.innerHTML = htmlsnip;
    }
    else {
        docEl.innerHTML = docEl.innerHTML + htmlsnip;
    }
}

function onlyUnique(value, index, self) {
  return self.indexOf(value) === index;
}

function* makeRangeIterator(start = 0, end = 100, step = 1) {
    let iterationCount = 0;
    for (let i = start; i < end; i += step) {
        iterationCount++;
        yield i;
    }
    return iterationCount;
}

function* makeRangeIterator2(variables = 1, start = 0, end = 100, step = 1) {
    let iterationCount = 0;
    for (let i = start; i < end; i += step) {
        iterationCount++;
        yield i;
    }
    return iterationCount;
}

function testGenerator() {
    const it = makeRangeIterator(0, 9, 1);

    let result = it.next();
    while (!result.done) {
        console.log(result.value); // 0 1 2 3 4 5 6 7 8 9
        result = it.next();
    }

}

// Works in Chrome devtools console
function* codeGenerator ( codes, vars ) {
    // check if no variables left => return final codes
    console.debug("Vars.length = " + vars.length);

    if( vars == null || vars.length == 0 ) {
        console.debug("Yield" + JSON.stringify(codes));
        yield codes;
    }
    else {
        // generate regexp to replace VAR => number
        let replRE = new RegExp("/"+vars[0]+"/g");
        console.debug("RE " + JSON.stringify(replRE));

        // for each var value [0,9]
        for(let i=0; i<10; i++ ) {
            let c = [];
            c[0] = codes[0].replace(vars[0], i.toString());
            c[1] = codes[1].replace(vars[0], i.toString());
            c[2] = codes[2].replace(vars[0], i.toString());
            console.debug("/"+vars[0]+"/g -> " + JSON.stringify(c));
            // recursively get the other variables processed
            yield* codeGenerator( c, vars.slice(1) );
        }
    }
}

// replace char at position index for string with replacement
String.prototype.replaceAt = function(index, replacement) {
    return this.substr(0, index) + replacement + this.substr(index + replacement.length);
}

/* async */ function solveReverseRE() {
    console.debug("Attempting to parse codes from note.");

    let resultmsg = document.getElementById("resultmsg");

    resultmsg.innerText  = "Generated coordinates:\n";

    let codes = parseCodesFromNote();
    if(codes.length != 3) {
        console.error("Error: >3 or <3 codes found:" + JSON.stringify(codes));
    }

    // cleanse
    for(let i=0; i<3; i++) {
      codes[i] = codes[i].trim();
    }
    console.debug("Parsed: " + JSON.stringify(codes));
    // TODO: remove vars from non meaningful items : B4 C4 = ALWAYS ignore
    console.log("Emptying non-meaninful digits B4 C4");
    codes[1] = codes[1].replaceAt( 6-4, "0"); //reset B4
    codes[2] = codes[2].replaceAt( 6-4, "0"); //reset C4

    console.debug("Cleansed: " + JSON.stringify(codes));

    // calculate # of variables/unknowns per code
    let vars = [];
    for(let i=0; i<3; i++) {
        let matching = codes[i].match(/[A-Za-z]/g);
        if(matching != null)
            vars = vars.concat( matching );
    }
    console.debug("Vars: " + JSON.stringify(vars));
    let uniqVars = vars.filter(onlyUnique);

    console.log(uniqVars.length + " unique variables");
    console.warn("Possible total combos: " + 10**uniqVars.length);

    let bogusDD = getBogusCoordsDD();

    console.debug("Parsed coords: " + JSON.stringify(bogusLatLonArr));
    console.debug("Converted coords: " + bogusDD.lat + "  " + bogusDD.lon);
    console.debug("Iterating...");

    console.log("code1,code2,code3,lat(dd),lon(dd),lat(gps),lon(gps),dist(m)");
    let counter = 0;

    let codeGen = codeGenerator( codes, uniqVars );

    let nextCode = codeGen.next();
    console.debug(JSON.stringify(nextCode));
    while (!nextCode.done) {
        // convert to dd
        let ddlatlon = code2LatLon( parseInt( nextCode.value[0] ), parseInt( nextCode.value[1] ) , parseInt( nextCode.value[2]) );
        console.debug("Convertion results: " + JSON.stringify(ddlatlon));
        if( ddlatlon.lat == NaN || ddlatlon.lon == NaN ) continue;
        else {
            let dist = haversineDistance( bogusDD.lat, bogusDD.lon, ddlatlon.lat, ddlatlon.lon );
            if( dist < 3220 ) {
                counter = counter+1;
                let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                let result = nextCode.value[0] + "," + nextCode.value[1] + "," + nextCode.value[2] + "," + fix(ddlatlon.lat, 2, 6) + "," + fix(ddlatlon.lon, 3, 6) + "," + dmlatlon.lat + "," + dmlatlon.lon + "," + dist.toFixed(0) ;
                console.log(result);

                let result2 = "";
                // if config output DD on, print also DD coords to output (makes it easier to deduce some variables)
                if( GM_config.get('OutputDD') ) {
                    result2 = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6) + " | ";
                }
                // default output
                result2 = result2 + dmlatlon.lat + " " + dmlatlon.lon;
                // if config option OutputCodes = true, output also calculated wigo codes
                if( GM_config.get('OutputCodes') ) {
                    result2 = result2 + " [" + nextCode.value[0] + "," + nextCode.value[1] + "," + nextCode.value[2] + "]" ;
                }
                if( GM_config.get('OutputDistance') ) {
                    result2 = result2 + " ⟼ " +  dist.toFixed(0) + " m";
                }
                resultmsg.innerText = resultmsg.innerText + "\n" + result2;
            }
            /* else {
                let dmlatlon = dd2dm( ddlatlon.lat, ddlatlon.lon);
                let result = nextCode.value[0] + "," + nextCode.value[1] + "," + nextCode.value[2] + "," + fix(ddlatlon.lat, 2, 6) + "," + fix(ddlatlon.lon, 3, 6) + "," + dmlatlon.lat + "," + dmlatlon.lon + "," + dist.toFixed(0) + "out of range" ;
                console.warn(result);
            } */
        }

        nextCode = codeGen.next();
    }
    console.log("Total hits in range: " + counter);

}



// returns { minLat, minLon, maxLat, maxLon } ie
// minimum and maximum latitude/longitude values with 3.2km from bogus
function minmaxRange() {
    minLatVal = minLat();
    maxLatVal = maxLat();
    minLonVal = minLon();
    maxLonVal = maxLon();
    return { minLat : minLatVal, minLon : minLonVal, maxLat : maxLatVal, maxLon : maxLonVal }
}


/*********
 * calculate min and max possible lat & lon
 */

function minmaxLatLon2DDM( minmaxObj ) {
    minLatDDM = lat_dd2dm(minmaxObj.minLat)
    maxLatDDM = lat_dd2dm(minmaxObj.maxLat)

    minLonDDM = lon_dd2dm(minmaxObj.minLon);
    maxLonDDM = lon_dd2dm(minmaxObj.maxLon);

    return { minLat : minLatDDM, minLon : minLonDDM, maxLat : maxLatDDM, maxLon : maxLonDDM }
}




function addBogusDDToPage() {
    let bogusDD = getBogusCoordsDD();

    console.debug("Converted coords: " + bogusDD.lat + "  " + bogusDD.lon);

    // add bogus dd coordinates on page
    addHTMLtoPage( "<strong> Bogus: " + bogusDD.lat + "  " + bogusDD.lon + " </strong><br>" );

    /*let locnode = document.getElementById("ctl00_ContentBody_LocationSubPanel");
    locnode.textContent = bogusLat + "  " + bogusLon;
    locnode.appendChild( document.createElement("br"));*/
}

function addMinMaxLatLonToPage() {
    mm = minmaxLatLon2DDM( minmaxRange() );

    console.debug("MinMax lat range: " + mm.minLat + "  " + mm.maxLat);
    console.debug("MinMax lon range: " + mm.minLon + "  " + mm.maxLon);

    // add bogus dd coordinates on page
    addHTMLtoPage( "<strong> " + mm.minLat + "  " + mm.minLon + " (min) </strong><br>" );
    addHTMLtoPage( "<strong> " + mm.maxLat + "  " + mm.maxLon + " (max) </strong><br>" );

    /*let locnode = document.getElementById("ctl00_ContentBody_LocationSubPanel");
    locnode.textContent = bogusLat + "  " + bogusLon;
    locnode.appendChild( document.createElement("br"));*/
}


// takes wigo codes as strings (may consist not numbers (variables) e.g. 022345 042x21 924ab9)
// returns resulting coordinate (DD) string
function evalPartialReverseWigo(codeA, codeB, codeC) {
    // validate that these are numbers
    console.debug("Input: " + codeA + ", " + codeB + ", " + codeC);
    if( isNaN( codeC[6-5] ) == false && isNaN( codeC[6-2] ) == false ) {
        // C5 + C2 = parillinen
        if( (Number(codeC[6-5]) + Number(codeC[6-2])) % 2 === 0 ) {
            console.debug("parillinen");
            // A4 B2  B5 C3 A6 C2 A1
            let resLat = "" + codeA[6-4] + codeB[6-2] + "." + codeB[6-5] + codeC[6-3] + codeA[6-6] + codeC[6-2] + codeA[6-1];
            // A5 C6 C1  B3 B6 A2 C5 B1
            let resLon = "" + codeA[6-5] + codeC[6-6] + codeC[6-1] + "." + codeB[6-3] + codeB[6-6] + codeA[6-2] + codeC[6-5] + codeB[6-1];
            console.debug(resLat + " " + resLon);
            return resLat + " " + resLon;
        }
        // C (d5 + d2) eli C5+C2= pariton
        else if ( (Number(codeC[6-5]) + Number(codeC[6-2])) %2 !== 0 ) {
            console.debug("pariton");
            // B6 A1  A4 C6 C3 C2 A6
            let resLat = "" + codeB[6-6] + codeA[6-1] + "." + codeA[6-4] + codeC[6-6] + codeC[6-3] + codeC[6-2] + codeA[6-6];
            // B2 C1 A2  A5 B3 B1 C5 B5
            let resLon = "" + codeB[6-2] + codeC[6-1] + codeA[6-2] + "." + codeA[6-5] + codeB[6-3] + codeB[6-1] + codeC[6-5] + codeB[6-5];
            console.debug(resLat + " " + resLon);
            return resLat + " " + resLon;

        }
    }
    else {
        console.debug("parillinen tai pariton");
        console.warn("Cannot deduce which equation to use - showing both results");
            let resLat1 = "" + codeA[6-4] + codeB[6-2] + "." + codeB[6-5] + codeC[6-3] + codeA[6-6] + codeC[6-2] + codeA[6-1];
            // A5 C6 C1  B3 B6 A2 C5 B1
            let resLon1 = "" + codeA[6-5] + codeC[6-6] + codeC[6-1] + "." + codeB[6-3] + codeB[6-6] + codeA[6-2] + codeC[6-5] + codeB[6-1];

            let resLat2 = "" + codeB[6-6] + codeA[6-1] + "." + codeA[6-4] + codeC[6-6] + codeC[6-3] + codeC[6-2] + codeA[6-6];
            // B2 C1 A2  A5 B3 B1 C5 B5
            let resLon2 = "" + codeB[6-2] + codeC[6-1] + codeA[6-2] + "." + codeA[6-5] + codeB[6-3] + codeB[6-1] + codeC[6-5] + codeB[6-5];
            console.debug( resLat1 + " " + resLon1 + "\n OR \n" + resLat2 + " " + resLon2 );
            return resLat1 + " " + resLon1 + " <br>OR<br> " + resLat2 + " " + resLon2;
    }
    // A3 B4 C4 = ALWAYS ignore

}



function readCodes() {
    mm = minmaxLatLon2DDM( minmaxRange() );

    xdialog.open({
        title: 'Reverse Wherigo Solver',
        body: '\
        <style>\
            .demo4-items {display:flex;flex-direction:column;align-items:center;}\
            .demo4-item {display:block;height:70px;width:50%;margin:3px;padding:1px;}\
            .demo4-button {display:block;height:30px;width:30%;margin:3px;padding:1px;}\
            .demo4-results {display:flex;flex-direction:column;align-items:center;}\
        </style>\
        <div id="demo4-header" class="demo4-items">\
        <div class="demo4-items">\
        <p>Min/max coords: '
        + mm.minLat + " " + mm.minLon + " " + mm.maxLat + " " + mm.maxLon +
        '<p>Enter reversewherigo codes below</p>\
            <textarea id="rwigo-codes-input" class="demo4-item"></textarea>\
        </div>\
        <div id="rwigo-codes-result" class="demo4-results"></div>\
        ',
        // buttons: ['ok', 'delete', 'cancel'],
        buttons: {
            delete: "run",
            ok: "Close"
        },
        style: 'width: 400px;left: 30%;',
        listenEnterKey: false,
        beforeshow: function(param) {
            [].slice.call(param.element.querySelectorAll('.xd-body *')).forEach(function(el) {
                let border = false;

                if (el instanceof HTMLInputElement) {
                    border = true;
                }

                if (['BUTTON', 'SELECT', 'TEXTAREA'].indexOf(el.tagName) >= 0) {
                    border = true;
                }

                if (border) {
                    el.setAttribute('style', 'border: 2px solid green;');
                }
            });
        },
        ondelete: function(param) {
            let codesText = param.element.querySelector('#rwigo-codes-input');
            console.log(codesText);
            let parsedCodes = parseCodes( codesText.value );
            if( parsedCodes !== null && parsedCodes.length >= 3 ) {
                console.debug("Match: " + parsedCodes[0] + "," + parsedCodes[1] + "," + parsedCodes[2] );
                let reverseRes = evalPartialReverseWigo( parsedCodes[0], parsedCodes[1], parsedCodes[2] );
                let res = param.element.querySelector('#rwigo-codes-result');
                let resp = document.createElement('p');
                resp.textContent = reverseRes;
                res.appendChild(resp);
            }
            return false;
        }
    });
}
// input: string
// Return: array (match results)
function parseCodes( myText ) {
    let re2 = /[0-9A-Za-z]{6}/g;
    console.log("Trying to find codes from user dialog:");
    let text = myText;
    let codes = text.match( re2 );
    console.debug( JSON.stringify( codes ) );
    return codes;
}

// parse reverse wherigo codes from note
function parseCodesFromNote() {
    let re1 = /RWIGO:/g;
    let re2 = /[0-9A-Za-z]{6}/g;
    console.log("Trying to find codes from user note:");
    let text = document.getElementById("viewCacheNote").innerText;
    if( text.match( re1 ) ) {
        console.debug("Match found (RWIGO:)");
        let codes = text.match( re2 );
        console.debug( JSON.stringify( codes ) );
        return codes;
    }
    else {
        console.debug("No codes found from Personal Note");
        return null;
    }
}

// parse codes from cache description
function parseCodesFromDescription() {
    let re = /[0-9]{6}/g;
    let codes = document.getElementsByClassName("UserSuppliedContent")[1].innerText.match( re );
    return codes;
}


  (/* async */ function() {
    'use strict';
    const my_css = GM_getResourceText("IMPORTED_CSS");
    GM_addStyle(my_css);

    let from, result;

    let cacheType = document.getElementsByClassName("cacheImage")[0].title;

    createShowSourceButton();

    if( GM_config.get('addBogusDDToPage') ) {
        addBogusDDToPage();
    }
    if( GM_config.get('addMinMaxLatLonToPage') ) {
        addMinMaxLatLonToPage();
    }

    if( cacheType == "Wherigo Cache" ) {
        createShowSolveREReverseButton();
        createDialog();

        let codes = parseCodesFromNote();
        // Codes found from Personal Note
        if( codes !== null && codes.length >= 3 ) {
            console.debug("Match: " + codes[0] + "," + codes[1] + "," + codes[2] );

            let reverseRes = evalPartialReverseWigo( codes[0], codes[1], codes[2] );
            addHTMLtoPage("<br><strong>Codes with variables converted to coordinates: <br>"+reverseRes+"</strong>" );

            // TODO: how to check if code 2 coords conversion is valid => only show if it is
            let ddlatlon = /* await */ code2LatLon( codes[0], codes[1], codes[2] );
            let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
            from = codes[0] + " " + codes[1] + " " + codes[2] + " => ";
            let resultDD = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6);
            let resultDMD = dmlatlon.lat + " " + dmlatlon.lon;
            result = resultDMD;
            console.log( from + " " + resultDD + " = " + resultDMD );

            addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>" );
            if( result != "S NaN° NaN W NaN° NaN")
                updateCoordinates( resultDMD );
        }
        // No codes in Personal Note - trying to retrieve directly from description
        else {
            console.log("Trying to find codes from UserSuppliedContent");
            codes = parseCodesFromDescription();
            if( codes !== null && (codes.length == 3 || codes.length == 6 || codes.length == 9) ) {
                console.debug("Parsed codes from UserSuppliedContent: " + JSON.stringify(codes) );

                let reverseRes = evalPartialReverseWigo( codes[0], codes[1], codes[2] );
                addHTMLtoPage("<br><strong>Reverse results = "+reverseRes+"</strong>", false);

                let ddlatlon = /* await */ code2LatLon( codes[0], codes[1], codes[2] );
                let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                from = codes[0] + " " + codes[1] + " " + codes[2] + " => ";
                let resultDD = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6);
                let resultDMD = dmlatlon.lat + " " + dmlatlon.lon;
                result = resultDMD;
                console.debug( from + " " + resultDD + " = " + resultDMD );

                addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>" );
                if( result != "S NaN° NaN W NaN° NaN") {
                    GM_setClipboard( result, "text" );
                    updateCoordinates( resultDMD );
                }
            }
            else {
                result = "Error: Reverse wherigo codes not found from description/note. <br>Enter codes to Personal Note as shown below, save and reload. <br>RWIGO:<br>123ABC<br>23D45G<br>423444";
                console.log( result );
                addHTMLtoPage("<br><strong>" + result + " </strong>", false);
            }
        }
    }
})();