/*
 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
///<reference path='../ts-declarations/node.d.ts' />
///<reference path='../ts-declarations/jalangi.d.ts' />
///<reference path='../ts-declarations/express.d.ts' />
var path = require('path');
var express = require('express');
var timeAnalysis = require('./timeLine');
var accessPathApi = require("./lifetimeAnalysisAPI");
var fs = require("fs");
var Q = require("q");
var lastUseTree = require("./lastUseTree");
var issueFinder = require('./issueFinder');
var ejs = require("ejs");
var argparse = require('argparse');
var parser = new argparse.ArgumentParser({
    addHelp: true,
    description: "GUI server for memory profiler"
});
parser.addArgument(['-P', '--port'], { help: "Port to serve on" });
parser.addArgument(['traceFile'], { help: "trace for app" });
parser.addArgument(['stalenessInfo'], { help: "JSON file containing staleness info" });
var args = parser.parseArgs();
var instOptions = {
    iidMap: true,
    serialize: true,
    relative: true
};
var file = args.traceFile;
var file2 = args.stalenessInfo;
var traceDirectory = path.dirname(path.resolve(file));
var port = args.port === null ? 9000 : parseInt(parser.port);
var trace;
var sizePromise;
var recordPromise = null;
if (file === undefined) {
    console.log("Must specify trace");
    process.exit(1);
}
if (file.indexOf(".json") !== -1) {
    console.log("Must specify trace and then the staleness .json file");
    process.exit(1);
}
trace = file;
if (file2.indexOf(".json") !== -1) {
    console.log("\nReading Size and Staleness JSON file " + file2);
    sizePromise = Q.fcall(function () {

        console.time("Read JSON");
        var buf = fs.readFileSync(file2);

        var json = JSON.parse(buf.toString());
        console.timeEnd("Read JSON");
        return { result: json };
    });
}
else {
    console.log("Expecting a .json file in the second argument");
    process.exit(1);
}
/**
 * parsed representation of site information from JSON file
 */
var objectSet;
/**
 * trace of function calls
 */
var ft;
var maxTime;
var origJson = undefined;
sizePromise.then(function (r) {
    //console.time("parse");
    //console.log("Populating JSON objects");
    objectSet = timeAnalysis.populateObjectsFromJSON(r.result);
    //console.log("...done");
    //console.log("Populating function trace");
    ft = timeAnalysis.populateFunctionTraceFromJSON(r.result);

    //timeAnalysis.decorateObjectSet(objectSet, ft);
    // timeAnalysis.testFuncTimeLine(t);
    var max = 0;
    for (var i in ft) {
        //console.log(i + ", " + ft[i].time);
        max = i;
    }
    maxTime = ft[max].time;
    origJson = r.result;
	console.log("Populating JSON objects & function trace...done");
    //console.log("... done");
    //console.timeEnd("parse");
    return;
}).done();
function makeSummary() {
    if (recordPromise !== null) {
        return recordPromise.then(function (r) {
            return { name: r.traceFile };
        });
    }
    else {
        return Q.fcall(function () {
            return { name: file };
        });
    }
}
var app = express();
// setting this option makes generation of JSON more space-efficient
app.set('json spaces', undefined);
app.configure(function () {
    app.use(express.static(path.join(__dirname, '..', 'newGUI')));
});
var curObjectSet;
var enhOutputPromise = issueFinder.getEnhancedTraceOutput(path.join(traceDirectory, 'enhanced-trace'));
function extend(dst, src) {
    Object.keys(src).forEach(function (prop) {
        dst[prop] = src[prop];
    });
}
function joinAndComputeMetrics(timelineOutput, enhOutput) {
    var result = [];
    var objInfo = enhOutput.objectInfo;
    var timelineObjData = timelineOutput.summaryData;
    Object.keys(timelineObjData).forEach(function (site) {
        var curResult = {};
        var curTimelineOutput = timelineObjData[site];
        var curEnhOutput = objInfo[site];
        if (curTimelineOutput) {
            extend(curResult, curTimelineOutput);
        }
        if (curEnhOutput) {
            extend(curResult, curEnhOutput);
        }
        // TODO add back abilty to report leaking DOM nodes
        curResult.leakiness = (curResult.isLeakingDefinitely && curResult.kind !== "DOM") ? curResult.aggregateMoment / timelineOutput.totalHeapMoment : 0;
        if (curResult.consistentlyPointedBy !== undefined && curResult.kind === "OBJECT") {
            curResult.inlineBenefit = curResult.count / timelineOutput.totalAllocations;
        }
        else {
            curResult.inlineBenefit = 0;
        }
        curResult.stackAllocBenefit = (curResult.isNonEscaping && curResult.kind === "OBJECT") ? curResult.count / timelineOutput.totalAllocations : 0;
        curResult.relativeStaleness = curResult.aggregateStaleness / timelineOutput.totalStaleness;
        result.push(curResult);
    });
    return result;
}
app.get("/summary", function (req, res) {
    //console.time("Site summary data ");
    var timeOutput = timeAnalysis.computeSiteSummaryData(objectSet);
    //console.timeEnd("Site summary data ");
    curObjectSet = objectSet;
    enhOutputPromise.then(function (enhOutput) {
        //console.time("Computing metrics ");
        var merged = joinAndComputeMetrics(timeOutput, enhOutput);
        //console.timeEnd("Computing metrics ");
        var result = res.json(merged);
        console.log("Serving summary data & metrics");
        return result;
    });
});
var json;
app.get("/timeline/:site", function (req, res) {
    var siteos = {};
    var site = req.params.site;
    //console.log(req.params.site);
    if (site === "*") {
        if (!curObjectSet) {
            curObjectSet = objectSet;
        }
        siteos = curObjectSet;
    }
    else if (site == "DOM") {
        siteos = timeAnalysis.filterObjects(timeAnalysis.mkDOMFilter(), curObjectSet);
    }
    else {
        if (!curObjectSet) {
            curObjectSet = objectSet;
        }
        site = decodeURIComponent(site);
        siteos[site] = curObjectSet[site];
    }
    var ts = timeAnalysis.computeSampledTimeLine(siteos, ft, 0, maxTime); /* need to pass in start and end times */
    console.log("Sending timeline data for " + 0 + " to " + maxTime);
    json = siteos; // remember what object set we used, in case someone asks for size details
    return res.json(ts);
});
app.get("/sizedetails/:time/:staleOnly", function (req, res) {
    console.log("Asked for details at time " + req.params.time + " and stale? " + req.params.staleOnly);
    var time = req.params.time;
    var staleOnly = req.params.staleOnly === "true";
    if (staleOnly) {
        var ssd = timeAnalysis.computeSiteSummaryData(timeAnalysis.filterObjects(timeAnalysis.mkStaleFilter(time), json));
        return res.json(ssd);
    }
    else {
        var ssd = timeAnalysis.computeSiteSummaryData(timeAnalysis.filterObjects(timeAnalysis.mkTimeFilter(time), json));
        return res.json(ssd);
    }
});
app.get("/callingcontexts/:site", function (req, res) {
    var site = req.params.site;
    console.log("Asking for calling context tree on site " + site);
    var siteos = {};
    siteos[site] = objectSet[site];
    var ssd = timeAnalysis.computeSiteSummaryData(siteos);
    var b = ssd.summaryData[site].allocTree;
    return res.json(b);
});
app.get("/accesspaths", function (req, res) {
    var site = req.param("site");
    var time = req.param("time");
    console.log("Asked for access paths for site: " + site + " and time " + time);
    var objects = origJson["objectInfo"][site];
    objects = objects.filter(function (obj) {
        // for now, we filter out PROTOTYPE objects
        // TODO handle them properly
        return obj.creationTime <= time && obj.type !== 'PROTOTYPE';
    }).map(function (obj) { return obj.objectId; });
    //console.log(objects)
    console.log("Number of objects: " + objects.length);
    var pathsPromise = accessPathApi.getAccessPaths(objects, parseInt(time), trace);
    pathsPromise.then(function (paths) {
        var b = timeAnalysis.analyzePaths(paths);
        return res.json(b);
    });
});
// just returns source code of file in JSON
// we assume the file location is given relative to the trace directory
app.get("/srcloc/:site", function (req, res) {
    var iid = decodeURIComponent(req.params.site);
    var filename = iid.split(':')[0];
    var file = path.join(traceDirectory, filename);
    console.log("Sending source file: " + file);
    fs.readFile(file, function (err, data) {
        if (err) {
            res.send(404, err);
            return;
        }
        res.json({ src: String(data) });
    });
});
function getAllocPageTemplate() {
    return String(fs.readFileSync(path.join("lib", "newGUI", "allocpage.template")));
}
function getProblemsTemplate() {
    return String(fs.readFileSync(path.join("lib", "newGUI", "problemspage.template")));
}
app.get("/allocpage/:site", function (req, res) {
    res.set('Content-Type', 'text/html');
    res.send(ejs.render(getAllocPageTemplate(), { site: decodeURIComponent(req.params.site) }));
});
app.get("/lastusetree", function (req, res) {
    var site = req.param("site");
    var time = parseInt(req.param("time"));
    sizePromise.then(function (r) {
        res.json(lastUseTree.computeTree(r.result, site, time));
    });
});
app.get("/problemslist", function (req, res) {
    enhOutputPromise.then(function (output) {
        res.json(issueFinder.computeIssues(output));
    }).done();
});
console.log("Serving on : http://localhost:" + port);
app.listen(port);
//# sourceMappingURL=guiServer.js.map
