// CREATE A GIF FOCUSED GIF OR PNG OVER SELECTED fit
// anim variable can be true(for gif) or a number for timestep, for png
// call from interactive-forecast-viewer folder 
// date currently does not work, and defaults to current day, or most recent data
// node create_model_loop_zoom_fit true monarch 20220808 od550_dust spain

const { Cluster } = require('puppeteer-cluster');
const util = require('util');
const path = require('path');

// USE THE CREATE_LOOP_DEV CONF DURING DEVELOPMENT
// const output_conf = path.relative('js/create_model_loop_zoom_fit.js', 'interactive-forecast-vie;er/conf/create_loop_prod.json')
const output_conf = path.relative('js/create_model_loop_zoom_fit.js', 'interactive-forecast-viewer/conf/create_loop_dev.json');
const setup_conf = path.relative('js/create_model_loop_zoom_fit.js', 'interactive-forecast-viewer/conf/create_loop_setup.json');
const selectors_conf = path.relative('js/create_model_loop_zoom_fit.js', 'interactive-forecast-viewer/conf/create_loop_selectors.json');
const coords_conf = path.relative('js/create_model_loop_zoom_fit.js', 'interactive-forecast-viewer/conf/coords.json');
const vars_conf = path.relative('js/create_model_loop_zoom_fit.js', 'interactive-forecast-viewer/conf/vars.json');

//REQUIRE CONFS
const conf = require(output_conf);
const vars = require(vars_conf);
const coords = require(coords_conf);
const setup = require(setup_conf);
const selectors = require(selectors_conf);

// ASSIGN THE URL THAT PUPPETEER SHOULD VISIT TO TAKE SCREENSHOTS
const url = conf.url;

// FUNCTION TO SET UP DELAYS WHEN NEEDED
function delay(time) {
    return new Promise(function(resolve) { 
	setTimeout(resolve, time)
    });
}

// START PUPPETEER CLUSTER (EXTERIOR VARIABLES MUST BE PASSED IN)
const RunCluster = async (anim, curmodel, seldate, variable, fit) => {
    const cluster = await Cluster.launch({
	concurrency: Cluster.CONCURRENCY_CONTEXT,
	puppeteerOptions: {
	    headless: true,
	    args: [
		'--no-sandbox',
		'--disable-setuid-sandbox',
	    ]
	},
	maxConcurrency: 4,
    });

    // SET SCREEN DIMENSIONS AND GOTO URL
    await cluster.task(async ({ page, data: args }) => {
	process.stdout.write("ARGS: " + args + "\n");
	var tstep = args[0];
	var curmodel = args[1];
	var seldate = args[2];
	var variable = args[3];
	var fit = args[4].toLowerCase();
	process.stdout.write("TSTEP: " + tstep + " -- MOD: " + curmodel + " -- DATE: " + seldate + " -- VAR: " + variable +' fit: ' + fit + "\n");
	var width_aspect = parseInt(coords[fit].width);
	var height_aspect = parseInt(coords[fit].height);
	await page.setViewport({ width: width_aspect, height: height_aspect});
	await page.goto(url, {
	    waitUntil: 'networkidle0',
	});
	await page.waitForSelector(selectors.graphs);

	// SELECT VARIABLE
	try {
	    const sel = await page.$(selectors.varDropdown);
	    await sel.click();
	    await page.waitForSelector(selectors.selectMenu);
	    const dropdown_variable = vars[variable.toUpperCase()].name_sidebar;
	    await page.evaluate(({dropdown_variable, selectedVar}) => {
		const options = [...document.querySelectorAll(selectedVar)];
		selected_variable = options.find(element => element.textContent === dropdown_variable);
		selected_variable.click();
	    }, {dropdown_variable: dropdown_variable, selectedVar: selectors.selectedVar});
	} catch (err) {
	    process.stdout.write("ERR0: " + err + "\n");
	}

	// SELECT ALL MODELS 
	if (curmodel === "all") {
	    try {            
		process.stdout.write("SELECT ALL MODELS\n")
		for (const model of await page.$$(selectors.selectedModels)) {
		    const checked = await model.evaluate(elem => elem.checked)
		    process.stdout.write("CHECKED BEFORE: " + checked + "\n");
		    if (!checked) {
			await model.click();
		    }
		}
	    } catch (err) {
		process.stdout.write("ERR1: " + err + "\n");
	    }
	}

	// ELSE SELECT ONLY ONE MODEL 
	else {
	    try {            
		process.stdout.write("SELECT MODEL: " + curmodel + "\n");
		for (const model of await page.$$(selectors.selectedModels)) {
		    const checked = await model.evaluate(elem => elem.checked);
		    process.stdout.write("CHECKED BEFORE: " + checked + "\n");
		    const value = await model.evaluate(elem => elem.value);
		    process.stdout.write("VALUE: " + value + "\n");
		    if (!checked && value === curmodel) {
			await model.click();
		    }
		    if (checked && value !== curmodel) {
			await model.click();
			await delay(500);
		    }
		}
	    } catch (err) {
		process.stdout.write("ERR2: " + err + "\n");
	    }
	}

	// CLICK APPLY BUTTON
	try {
	    for (const model of await page.$$(selectors.selectedModels)) {
		const checked = await model.evaluate(elem => elem.checked);
		process.stdout.write("CHECKED AFTER: " + checked + "\n");
	    }
	    process.stdout.write("CLICK APPLY BUTTON\n");
	    const btn = await page.$(selectors.modelsApply);
	    await btn.click();  // "button#models-apply");
	    await delay(500);
	} catch (err) {
	    process.stdout.write("ERR3: " + err + "\n");
	}

	// APPLY ZOOM AND COORDINATES
	try {
	    var zoom = coords[fit].zoom;
	    var lat = coords[fit].lat;
	    var lon = coords[fit].lon;
	    process.stdout.write("Zoom: " + zoom  + " Lat " + lat + " Lon " + lon +"\n");

	    // CHANGE HIDDEN INPUTS AND BUTTON TO VISIBLE
	    let zoomInput = await page.$(selectors.zoomLevel);
	    await zoomInput.evaluate((el) => el.style.display = setup.makeVisible);
	    let latInput = await page.$(selectors.lat);
	    await latInput.evaluate((el) => el.style.display = setup.makeVisible);
	    let lonInput = await page.$(selectors.lon);
	    await lonInput.evaluate((el) => el.style.display = setup.makeVisible);
	    let zoomButton = await page.$(selectors.focus);
	    await zoomButton.evaluate((el) => el.style.display = setup.makeVisible);

	    // ADD DATA AND CLICK 
	    await page.type(selectors.zoomLevel, zoom);
	    await page.type(selectors.lat, lat);
	    await page.type(selectors.lon, lon);
	    process.stdout.write("CLICK HIDDEN country BUTTON\n");
	    await page.click(selectors.focus);

	    // MAKE ELEMENTS INVISIBLE AGAIN
	    await zoomInput.evaluate((el) => el.style.display = setup.makeInvisible);
	    await latInput.evaluate((el) => el.style.display = setup.makeInvisible);
	    await lonInput.evaluate((el) => el.style.display = setup.makeInvisible);
	    await zoomButton.evaluate((el) => el.style.display = setup.makeInvisible);

	} catch (err) {
	    process.stdout.write("ERR4: " + err + "\n");
	}

	// GO THROUGH TIMESTEPS
	var num = "00";
	if (tstep === "false") {
	    process.stdout.write("CURRENT SELECTION: " + tstep + "\n");
	    num = "_curr";
	} else {
	    process.stdout.write("CURRENT TSTEP: " + tstep + "\n");
	    const steps = await page.$$(selectors.sliderDot);
	    await steps[tstep].click();
	    if (tstep<10) {
		num = "0"+tstep;
	    } else {
		num = tstep;
	    }
	}
	try {
	    // WAIT FOR ALERT DIV TO DISAPPEAR TO START GRABBING ELEMENTS 
	    await page.waitForSelector(selectors.alertForecast, {hidden: true});
	    await page.waitForSelector(selectors.graphs);
	    var graph = await page.$(selectors.graphs);
	    // CHANGE GRAPH HEIGHT TO FILL OUTPUT GIF
	    let graphHeight = await page.$(selectors.leafletContainer);
	    await graphHeight.evaluate((el, styledHeight) => el.style.height = styledHeight, 
	    setup.graphHeight);
	    // REMOVE TIMESLIDER 
	    await page.waitForSelector(selectors.navTimebar);
	    process.stdout.write("REMOVING NAVBAR" + "\n");
	    await page.evaluate((sel) => {
		let toRemove = document.querySelector(sel);
		toRemove.parentNode.removeChild(toRemove);
	    }, selectors.navTimebar);
	    // REMOVE ZOOM PANEL 
	    process.stdout.write("REMOVING ZOOM PANEL(S)" + "\n");
	    await page.waitForSelector(selectors.leafletBar);
	    await page.evaluate((sel) => {
		let toRemove = document.querySelectorAll(sel);
		for (let j=0; j<toRemove.length; j++) {
		    toRemove[j].parentNode.removeChild(toRemove[j]);
		}
	    }, selectors.leafletBar);
	} catch (err) {
	    console.log(err);
	    process.stdout.write("ERR5:" + err + "\n");
	}
	if (coords[fit].logos == true){
	    try {
		// REVEAL LOGOS 
		process.stdout.write("REVEALING LOGOS" + "\n");

		// STYLE LOGOS
		let logos = await page.$(selectors.logos);
		await logos.evaluate((el) => el.style.display = setup.logoDisplay);
		await logos.evaluate((el, margin) => {el.style.marginTop = margin}, coords[fit].marginTop);
		await logos.evaluate((el, padding) => {el.style.paddingRight = padding}, coords[fit].paddingRight);
	    } catch (err) {
		console.log(err);
		process.stdout.write("ERR6:" + err + "\n");
	    }
	};
	
	// MOVE DISCLAIMER INTO THE IMAGE FOR THE MONARCH_FIT
        let disclaimer = await page.$(selectors.disclaimer);
        await disclaimer.evaluate((el, disclaimerOffset) => el.style.right = disclaimerOffset,
	setup.disclaimerRight);
	
	// HANDLE OUTPUT AND TAKE SCREENSHOT
	// FILENAME SHOULDN'T INCLUDE FIT UNLESS IT IS A SPECIFIED COUNTRY
	var targetFolder = conf.targetFolder;
	if (fit == setup.specialFit || fit == setup.defaultFit) {
	    var outputFile = `${targetFolder}/${variable.toLowerCase()}/${seldate}_${curmodel}_${num}.png`;
	} else {
	    // CREATE A FILENAME THAT INCLUDES THE SPECIFIED COUNTRY FIT
	    var outputFile = `${targetFolder}/${variable.toLowerCase()}/${fit}/${seldate}_${curmodel}_${fit}_${num}.png`;
	}

	process.stdout.write("TAKE SCREENSHOT => " + outputFile + "\n");
	try {
	    await graph.screenshot({ path: outputFile });
	} catch(err) {
	    console.log(err);
	    process.stdout.write("ERR7:" + err + "\n");
	}

    });

    if (anim === "true") {
	for (let i = 0; i < setup.tstepCount; i++) {
	    try {
		process.stdout.write("QUEUEING step:" + i + "\n");
		await cluster.queue([i, curmodel, seldate, variable, fit]);
	    } catch (err) {
		console.log(err);
		process.stdout.write("ERR8:" + err + "\n");
	    }
	}
    } else {
	process.stdout.write("QUEUEING current:" + anim + "\n");
	cluster.queue([anim, curmodel, seldate, variable, fit]);
    }

    await cluster.idle();
    await cluster.close();
}

var anim = process.argv[2];      // default: false;
var curmodel = process.argv[3];  // default: "none";
var seldate = process.argv[4];   // default: "none";
var variable = process.argv[5];  // default: OD550_DUST
var fit = process.argv[6];	 

process.stdout.write("START -> ANIM: " + anim + " CURMODEL: " + curmodel + " DATE: " + seldate + " VAR: " +  variable + " fit: " + fit + "\n");
RunCluster(anim, curmodel, seldate, variable, fit);

