//
// Takes a screenshot of the given URL, uses named arguments passed in like so: phantomjs raster.js arg=value arg2=value2
//
// Arguments:
// - url                      - URL to screenshot
// - output                   - page to output (e.g. /tmp/output.png)
// - width         [optional] - default 1024 - viewport width
// - height        [optional] - viewport height (see note below on using height)
// - debug         [optional] - default false - whether to do some extra debugging
// - div           [optional] - a selector to use to screenshot to a specific element
// - resourceWait  [optional] - default 300 - the time to wait after the last resource has loaded in MS before taking the screenshot
// - maxRenderWait [optional] - default 10000 - the maximum time to wait before taking the screenshot, regardless of whether resources are waiting to be loaded
// - cutoffWait    [optional] - default null - the maximum time to wait before cutting everything off and failing...this helps if there is a page taking a long time to load
// - top, left, width, height [optional] - dimensions to use to screenshot a specific area of the screen
//
// == Important notice when providing height ==
//
// If you provide a height then we resize the html & body tags otherwise render() renders the entire page
// changing the viewport height does not affect this behaviour of render(), see https://github.com/ariya/phantomjs/issues/10619
//
var page           = new WebPage(),
    resourceWait   = 300,
    maxRenderWait  = 10000,
    cutoffWait     = null,
    args           = {},
    resourceCount  = 0,
    debug          = false,
    mask           = null,
    forcedRenderTimeout,
    renderTimeout,
    cutoffTimeout;

//
// Functions
//
function pickupNamedArguments() {
    var i, pair;
    for(i = 0; i < phantom.args.length; i++) {
        pair = phantom.args[i].split(/=(.*)/);
        args[pair[0]] = pair[1];
    }

    if(!args.width)        { args.width = 1024; }
    if(!args.dpi)          { args.dpi = 1; }
    if(args.url)           { args.url = decodeURIComponent(args.url); }
    if(args.debug)         { debug = true; }
    if(args.resourceWait)  { resourceWait = args.resourceWait; }
    if(args.maxRenderWait) { maxRenderWait = args.maxRenderWait; }
    if(args.cutoffWait)    { cutoffWait = args.cutoffWait; }
}

function setupMask() {
    // if given settings for an area to take create a mask for that
    if( args.top && args.left && args.width && args.height) {
        mask = {
            top:    args.top,
            left:   args.left,
            width:  args.width,
            height: args.height
        };
    }
}

function doRender() {
    clearTimeout(renderTimeout);
    clearTimeout(forcedRenderTimeout);
    clearTimeout(cutoffTimeout);
    page.render(args.output);
    phantom.exit();
}

// if the page is taking too long (set via cutoffWait) to load, just exit cleanly
function cutoff() {
  clearTimeout(renderTimeout);
  clearTimeout(forcedRenderTimeout);
  console.log('Unable to load: ' + args.url + '. Process exceeded cutoff timeout.');
  phantom.exit();
}

function delayScreenshotForResources() {
    forcedRenderTimeout = setTimeout(doRender, maxRenderWait);
}

function cutoffExecution() {
  if(cutoffWait != null) {
    cutoffTimeout = setTimeout(cutoff, cutoffWait);
  }
}

function evaluateWithArgs(func) {
    var args = [].slice.call(arguments, 1);
    var fn =  "function() { return (" + func.toString() + ").apply(this, " + JSON.stringify(args) + "); }";
    return page.evaluate(fn);
}

function takeScreenshot() {
  cutoffExecution();
   page.open(args.url, function(status) {
        if(status !== 'success') {
            console.log('Unable to load: ' + args.url);
            phantom.exit();
        } else {
            delayScreenshotForResources();

            page.includeJs(
                "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js",
                function() {

                    var foundDiv = true;
                    page.evaluate(function(){ jQuery.noConflict(); });

                    if(args.div) {
                        var clip = evaluateWithArgs(withinPage_GetDivDimensions, args.div);
                        foundDiv = clip;
                        page.clipRect = clip;
                    } else if(mask) {
                        page.clipRect = mask;
                    } else if(args.height) {
                        // have a height resize the html & body to workaround https://github.com/ariya/phantomjs/issues/10619
                        evaluateWithArgs(
                            function(w,h) {
                                jQuery('body, html').css({
                                    width: w + 'px',
                                    height: h + 'px',
                                    overflow: 'hidden'
                               });
                            },
                            page.viewportSize.width,
                            page.viewportSize.height / args.dpi
                        );
                    }

                    if (args.dpi !== 1) {
                    evaluateWithArgs(
                        function(dpi) {
                            document.body.style.webkitTransform = "scale(" + dpi + ")";
                            document.body.style.webkitTransformOrigin = "0% 0%";
                            document.body.style.width = (100 / dpi) + "%";
                        },
                        args.dpi
                    );
                    }

                    if(!foundDiv) {
                        phantom.exit();
                    }
                }
            );
        }
    });
}

//
// Functions evaluated within the page context
//
function withinPage_GetDivDimensions(div){
    var $el = jQuery(div);

    if($el.length === 0){
        console.log(div + ' was not found. exiting');
        return false;
    }

    var dims    = $el.offset();
    dims.height = $el.height();
    dims.width  = $el.width();
    return dims;
}

//
// Event handlers
//
page.onConsoleMessage = function(msg) {
    console.log('from page: ' + msg);
};

page.onResourceRequested = function(req) {
    resourceCount += 1;
    if(debug) { console.log('> ' + req.id + ' - ' + req.url); }
    clearTimeout(renderTimeout);
};

page.onResourceReceived = function(res) {
    if(!res.stage || res.stage == 'end') {
        resourceCount -= 1;
        if(debug) {
            console.log(res.id + ' ' + res.status + ' - ' + res.url);
            console.log(resourceCount + ' resources remaining');
        }
        if(resourceCount === 0) {
            // Once all resources are loaded, we wait a small amount of time
            // (resourceWait) in case these resources load other resources.
            clearTimeout(forcedRenderTimeout);
            renderTimeout = setTimeout(doRender, resourceWait);
        }
    }
};

//
// Do the processing
//
pickupNamedArguments();
setupMask();

console.log(JSON.stringify(args));

if( !args.url || !args.output ) {
    console.log('Usage: raster.js url=URL output=filename width=width[optional] height=height[optional] debug=true/false[optional] (div=div[optional] OR top=top left=left width=width height=height)');
    phantom.exit();
}

page.viewportSize = { width: args.width, height: args.height || 1024 };

takeScreenshot();
