/*
 * 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" />
//import fs = require ("fs");
var types = require('./dataAnalysisTypes');
var jalangi = require('jalangi/src/js/jalangi');
var path = require('path');
var tree = require('./tree');
var Thresholds;
(function (Thresholds) {
    Thresholds[Thresholds["VeryStale"] = 200000] = "VeryStale";
    Thresholds[Thresholds["NumBars"] = 500] = "NumBars";
})(Thresholds || (Thresholds = {}));
;
var FunEvents;
(function (FunEvents) {
    FunEvents[FunEvents["Enter"] = 1] = "Enter";
    FunEvents[FunEvents["Exit"] = -1] = "Exit";
})(FunEvents || (FunEvents = {}));
;
// TODO this should be passed as a parameter!
var inputFile = process.argv[2];
console.log("Input File : " + inputFile);
//var buf : NodeBuffer = fs.readFileSync(inputFile);
//var inp : string = buf.toString();
//var objectSet : any = populateObjects(inp);
//var ctl : any = computeCumulativeTimeLine(objectSet);
//var ssd : any = computeSiteSummaryData(filterObjects(mkTimeFilter(ctl.timeAtMaxAlloc), objectSet));
//printSiteSummaryData(ssd);
var refCountAnalysis = path.resolve("lib/SizeAndStalenessEngine.js");
var replayPromise = jalangi.replay(inputFile, refCountAnalysis, {});
var objectSetPromise = replayPromise.then(function (r) {
    return populateObjectsFromJSON(r.result);
});
var siteSummaryDataPromiseMaker = function (osP) {
    // assume it is objectSetPromise
    return osP.then(function (r) {
        var ssd = computeSiteSummaryData(r);
        return filterSites(hasManyStales, ssd);
    });
};
var timeLinePromiseMaker = function (osP) {
    // assume it is objectSetPromise
    return osP.then(function (r) {
        return computeCumulativeTimeLine(r);
    });
};
var objectsAtSiteAtTimePromiseMaker = function (osP, time, site) {
    // assume it is objectSetPromise
    return osP.then(function (r) {
        var result = [];
        var objs = filterObjects(mkTimeFilter(time), r);
        if (objs[site] != undefined) {
            result = objs[site];
        }
        return result;
    });
};
/* --- "DRIVERS" ---(pick one) - */
// siteSummaryDataPromiseMaker(objectSetPromise).done(function (r) { printSiteSummaryData(r);});
/*timeLinePromiseMaker(objectSetPromise).done(
    function (r) {
        printCumulativeTimeLine(r);
    }
);*/
/*---------- OBJECTSET ------------ */
/*
 Object set is a map
   site -> array of ObjectRecord
   The site information in the object records for a site contains the site identity (redundantly?)
 */
function populateObjectsFromJSON(jsonObject) {
    var objectSet = {};
    for (var e in jsonObject["objectInfo"]) {
        //console.log(e);
        var ks = jsonObject["objectInfo"][e];
        objectSet[e] = [];
        for (var k in ks) {
            var or = new types.ObjectRecord(e.toString(), ks[k].type, ks[k].creationTime, ks[k].unreachableTime, ks[k].shallowSize, ks[k].staleness, ks[k].unreachableTime - ks[k].staleness, [], ks[k].creationCallStack);
            objectSet[e].push(or);
        }
    }
    return objectSet;
}
exports.populateObjectsFromJSON = populateObjectsFromJSON;
;
function populateFunctionTraceFromJSON(jsonObject) {
    var functionTrace = [];
    for (var e in jsonObject["functionTrace"]) {
        var rec = jsonObject["functionTrace"][e];
        // console.log(rec);
        var fr = { "kind": parseInt(rec[0]), "time": parseInt(rec[1]), "IID": parseInt(rec[2]) };
        functionTrace.push(fr);
    }
    return functionTrace;
}
exports.populateFunctionTraceFromJSON = populateFunctionTraceFromJSON;
function populateObjects(inp) {
    return populateObjectsFromJSON(JSON.parse(inp));
}
function filterObjects(filter, objectSet) {
    var ret = {};
    var skipped = 0;
    var accepted = 0;
    for (var e in objectSet) {
        var ks = objectSet[e];
        var objects_in_e = [];
        var empty = true;
        for (var k in ks) {
            if (filter(ks[k])) {
                objects_in_e.push(ks[k]);
                empty = false;
                accepted = accepted + 1;
            }
            else {
                skipped = skipped + 1;
            }
        }
        if (!empty) {
            ret[e] = objects_in_e;
        }
    }
    console.log("Filter skipped " + skipped + ", accepted " + accepted);
    return ret;
}
exports.filterObjects = filterObjects;
function mkDOMFilter() {
    return function (o) {
        return (o.kind == "DOM");
    };
}
exports.mkDOMFilter = mkDOMFilter;
function mkTimeFilter(t) {
    return function (o) {
        /* is the object reachable at that time */
        return (o.creationTime <= t && t <= o.unreachableTime);
    };
}
exports.mkTimeFilter = mkTimeFilter;
function mkStaleFilter(t) {
    return function (o) {
        /* is the object stale at that time */
        return (o.lastUseTime <= t && t <= o.unreachableTime);
    };
}
exports.mkStaleFilter = mkStaleFilter;
function printObjects(objectSet) {
    for (var e in objectSet) {
        var ks = objectSet[e];
        for (var k in ks) {
            console.log(e + ", " + ks[k].creationTime + ", " + ks[k].lastUseTime + ", " + ks[k].unreachableTime);
        }
    }
}
exports.printObjects = printObjects;
function timeToUnreachability(from, objectCreationTime, objectSet) {
    for (var e in objectSet) {
        var ks = objectSet[e];
        for (var k in ks) {
            if (ks[k].creationTime == objectCreationTime) {
                return ks[k].unreachableTime - from;
            }
        }
    }
    console.log("Object created at time " + objectCreationTime + " not found.");
    return 0; /* exceptional value -- should we actually throw an exception */
}
exports.timeToUnreachability = timeToUnreachability;
/*------- TIME LINE ---------*/
/*
timeline is a map from timestamps to {alloc, stale}
{timeline, maxAllocTime, stalenessAtMaxAlloc}
 */
function computeTimeLine(objectSet) {
    var timeLine = [];
    for (var e in objectSet) {
        var ks = objectSet[e];
        for (var k in ks) {
            var cT = ks[k].creationTime;
            var uT = ks[k].unreachableTime;
            var s = ks[k].staleness;
            var luT = uT - s;
            var sz = ks[k].shallowSize;
            sz = 1; // Assume unit size for now
            // cT <= luT < uT
            if (timeLine[cT] == undefined) {
                timeLine[cT] = new types.Pair(sz, 0);
            }
            else {
                timeLine[cT].alloc += sz;
            }
            if (timeLine[luT] == undefined) {
                timeLine[luT] = new types.Pair(0, sz);
            }
            else {
                timeLine[luT].stale += sz;
            }
            if (timeLine[uT] == undefined) {
                timeLine[uT] = new types.Pair(-1 * sz, -1 * sz);
            }
            else {
                timeLine[uT].alloc += -1 * sz;
                timeLine[uT].stale += -1 * sz;
            }
        }
    }
    return timeLine;
}
function printTimeLine(timeLine) {
    console.log("time, alloc, stale");
    for (var k in timeLine) {
        console.log(k + ", " + timeLine[k].alloc + ", " + timeLine[k].stale);
    }
}
exports.printTimeLine = printTimeLine;
function computeFunctionTimeLine(functionTrace) {
    var funcTimeLine = [];
    for (var k in functionTrace) {
        var kind = functionTrace[k].kind;
        var time = functionTrace[k].time;
        var iid = functionTrace[k].IID;
        var fkind = (kind == 0) ? 1 /* Enter */ : -1 /* Exit */;
        // time stamps in function trace will be unique
        funcTimeLine[time] = new types.FunRec(iid, fkind);
    }
    return funcTimeLine;
}
function computeCumulativeFuncTimeLine(functionTrace) {
    var funcTimeLine = computeFunctionTimeLine(functionTrace);
    var cumFuncTimeLine = [];
    var stackDepth = 0;
    for (var k in funcTimeLine) {
        stackDepth += funcTimeLine[k].kind;
        cumFuncTimeLine[k] = stackDepth; // TODO: need to package in the execution context too
    }
    return cumFuncTimeLine;
}
function printCumulativeFuncTimeLine(cumFuncTimeLine) {
    for (var k in cumFuncTimeLine) {
        console.log(k + ", " + cumFuncTimeLine[k]);
    }
}
function testFuncTimeLine(functionTrace) {
    var f = computeCumulativeFuncTimeLine(functionTrace);
    printCumulativeFuncTimeLine(f);
}
exports.testFuncTimeLine = testFuncTimeLine;
function computeCumulativeTimeLine(objectSet) {
    var timeLine = computeTimeLine(objectSet);
    var cumTimeLine = [];
    var sumAlloc = 0;
    var sumStale = 0;
    var maxAlloc = 0;
    var staleAtMaxAlloc = 0;
    var timeAtMaxAlloc = 0;
    for (var k in timeLine) {
        sumAlloc += timeLine[k].alloc;
        sumStale += timeLine[k].stale;
        if (sumAlloc > maxAlloc) {
            maxAlloc = sumAlloc;
            timeAtMaxAlloc = parseInt(k);
            staleAtMaxAlloc = sumStale;
        }
        cumTimeLine[k] = new types.Pair(sumAlloc, sumStale);
    }
    return { "maxAlloc": maxAlloc, "staleAtMaxAlloc": staleAtMaxAlloc, "timeAtMaxAlloc": timeAtMaxAlloc, "timeLine": cumTimeLine };
}
function printCumulativeTimeLine(timeLineRec) {
    console.log("time, alloc, stale");
    for (var k in timeLineRec["timeLine"]) {
        console.log(k + ", " + timeLineRec["timeLine"][k].alloc + ", " + timeLineRec["timeLine"][k].stale);
    }
    console.log("MaxAlloc: " + timeLineRec["maxAlloc"]);
    console.log("StalenessAtMax: " + timeLineRec["staleAtMaxAlloc"]);
    console.log("TimeAtMaxAlloc: " + timeLineRec["timeAtMaxAlloc"]);
}
function computeSampledTimeLine(objectSet, functionTrace, minTime, maxTime) {
    var cumulative = computeCumulativeTimeLine(objectSet);
    //printCumulativeTimeLine(cumulative);
    var funcCumTimeLine = computeCumulativeFuncTimeLine(functionTrace);
    var timeLine = cumulative["timeLine"];
    var sampledTimeLine = [];
    var timeStep = Math.floor((maxTime - minTime) / 500 /* NumBars */);
    //var timeStep = Math.floor(timeLine.length / Thresholds.NumBars);
    var i = 0;
    while (i < 500 /* NumBars */) {
        sampledTimeLine[i] = {};
        var upLim = timeStep * (i + 1) - 1;
        var loLim = timeStep * i;
        var j = upLim;
        var found = false;
        while ((j >= loLim)) {
            if (timeLine[j] !== undefined && !found) {
                sampledTimeLine[i].time = j;
                sampledTimeLine[i].sd = findSD(funcCumTimeLine, j);
                sampledTimeLine[i].alloc = timeLine[j].alloc;
                sampledTimeLine[i].stale = timeLine[j].stale;
                sampledTimeLine[i].lastalloc = sampledTimeLine[i].alloc;
                sampledTimeLine[i].laststale = sampledTimeLine[i].stale;
                found = true;
            }
            else if (timeLine[j] != undefined && found) {
                if (sampledTimeLine[i].alloc < timeLine[j].alloc) {
                    sampledTimeLine[i].time = j;
                    sampledTimeLine[i].sd = findSD(funcCumTimeLine, j);
                    sampledTimeLine[i].alloc = timeLine[j].alloc;
                    sampledTimeLine[i].stale = timeLine[j].stale;
                }
            }
            j = j - 1;
        }
        if (!found) {
            if (i == 0) {
                sampledTimeLine[0] = { time: 0, sd: 0, alloc: 0, stale: 0, lastalloc: 0, laststale: 0 };
            }
            else {
                sampledTimeLine[i].time = i * timeStep;
                sampledTimeLine[i].sd = findSD(funcCumTimeLine, j);
                sampledTimeLine[i].alloc = sampledTimeLine[i - 1].lastalloc;
                sampledTimeLine[i].stale = sampledTimeLine[i - 1].laststale;
                sampledTimeLine[i].lastalloc = sampledTimeLine[i].alloc;
                sampledTimeLine[i].laststale = sampledTimeLine[i].stale;
            }
        }
        i = i + 1;
    }
    return sampledTimeLine;
}
exports.computeSampledTimeLine = computeSampledTimeLine;
function findSD(cumFuncTimeLine, time) {
    var loLim = 0;
    var j = time;
    var found = false;
    var sd = 0;
    while (j >= loLim && !found) {
        var t = cumFuncTimeLine[j];
        if (t !== undefined) {
            sd = t;
            found = true;
        }
        j--;
    }
    if (!found) {
        console.log("Warning: no function call found on stack");
    }
    return sd;
}
function computeSiteSummaryData(objectSet) {
    var ssd = {};
    var totalHeapMoment = 0;
    var totalStaleness = 0;
    var totalAllocations = 0;
    for (var e in objectSet) {
        var ks = objectSet[e];
        var kind = "";
        var count = 0;
        var maxStaleness = 0;
        var veryStale = 0;
        var aggStaleness = 0;
        var aggSize = 0;
        var aggMoment = 0;
        var tnsAccum = [];
        for (var k in ks) {
            var curKind = ks[k].kind;
            if (curKind !== 'PROTOTYPE') {
                // for now, skip function prototype objects
                // TODO we still want to report leaks of these objects
                kind = curKind;
            }
            var cT = ks[k].creationTime;
            var uT = ks[k].unreachableTime;
            var s = ks[k].staleness;
            var sz = ks[k].shallowSize;
            sz = 1; // Assume unit size for now
            var moment = sz * (uT - cT);
            count = count + 1;
            if (s > 200000 /* VeryStale */) {
                veryStale = veryStale + 1;
            }
            if (s > maxStaleness) {
                maxStaleness = s;
            }
            aggSize = aggSize + sz;
            aggMoment = aggMoment + moment;
            aggStaleness += s;
            var tns = tree.list2TreeNodes(ks[k].creationCallStack, 0);
            tree.foldInto(tns, 0, tnsAccum);
        }
        totalHeapMoment += aggMoment;
        totalAllocations += count;
        totalStaleness += aggStaleness;
        var tnsfin = new tree.TreeNode(e, "", count, tnsAccum);
        /*
        console.log("TNSFIN = " + e + " " + count + " " + tnsfin.children.length);
        var i : number = 0;
        while (i < tnsAccum.length) {
            console.log("  " + tnsAccum[i].root + " " + tnsAccum[i].count + " " + tnsAccum[i].children.length);
            var j : number = 0;
            while (j < tnsAccum[i].children.length) {
                console.log("   " + tnsAccum[i].children[j].root + " " + tnsAccum[i].children[j].count + " " + tnsAccum[i].children[j].children.length);
                j++;
            }
            i++;
        }
        */
        ssd[e] = new types.SiteSummaryData(e, kind, count, veryStale, maxStaleness, aggSize, aggMoment, aggStaleness, tnsfin);
    }
    return { summaryData: ssd, totalHeapMoment: totalHeapMoment, totalAllocations: totalAllocations, totalStaleness: totalStaleness };
}
exports.computeSiteSummaryData = computeSiteSummaryData;
function printSiteSummaryData(ssd) {
    console.log("Site, Count, MaxStaleness, VeryStale, Size, Moment");
    for (var e in ssd) {
        console.log(e + ", " + ssd[e].count + ", " + ssd[e].maxStaleness + ", " + ssd[e].veryStale + ", " + ssd[e].aggregateSize + ", " + ssd[e].aggregateMoment);
    }
}
exports.printSiteSummaryData = printSiteSummaryData;
function filterSites(filter, ssd) {
    var ret = {};
    for (var e in ssd) {
        if (filter(ssd[e])) {
            ret[e] = ssd[e];
        }
    }
    return ret;
}
function hasManyStales(d) {
    return (d.count > 1 && d.veryStale > 0); /* || ((d.count >= 100) && (d.maxStaleness >= 100)); */
}
//
//export function computeInterestingSitesData(objectSet: any) : any {
//    for (var e in objectSet) {
//        var ks = objectSet[e];
//
//        var siteos = {};
//        siteos[e] = ks;
//
//        console.log("Site:" + e + ", count :" + ks.length);
//
//        var cumulative = computeCumulativeTimeLine(siteos);
//        var tL = cumulative["timeLine"];
//
//        // find points of minima
//
//        var previous = 0;
//        var preprevious = 0;
//        var current = 0;
//        var previous_min = 0;
//
//        for (var k in tL) {
//            // assumes that tL elements are visited in growing order
//            current = k;
//
//            if ((tL[previous] != undefined) && (tL[preprevious] != undefined)) {
//                if (((tL[previous].alloc < tL[current].alloc) && (tL[previous].alloc <= tL[preprevious].alloc)) ||
//                    ((tL[previous].alloc <= tL[current].alloc) && (tL[previous].alloc < tL[preprevious].alloc))) {
//                    console.log("Local min at time: " + previous + ", alloc = " + tL[previous].alloc);
//                    console.log("   before time = " + preprevious + " alloc = " + tL[preprevious].alloc);
//                    console.log("   after  time = " + current + " alloc = " + tL[current].alloc);
//                    if (previous_min == 0) {
//                        previous_min = previous;
//                    } else if ((tL[previous].alloc > tL[previous_min].alloc)) {
//                        previous_min = previous;
//                    }
//                }
//            }
//            preprevious = previous;
//            previous = current;
//        }
//    }
//}
function createSiteSummaryTableForGUI(objectSet) {
    var busySites = [];
    var ssd = computeSiteSummaryData(objectSet).summaryData;
    var toSort = [];
    for (var s in ssd) {
        toSort.push(ssd[s]);
    }
    // sort in a particular way
    toSort.sort(function (current, next) {
        return current.count <= next.count ? 1 : -1;
    });
    var i = 0;
    while (i < toSort.length) {
        busySites.push(toSort[i]);
        i++;
    }
    return busySites;
}
exports.createSiteSummaryTableForGUI = createSiteSummaryTableForGUI;
function splitByCaller(objectSet) {
    var newObjectSet = {};
    for (var e in objectSet) {
        //console.log("In splitByCaller, site " + e);
        var ks = objectSet[e];
        for (var k in ks) {
            var cs = ks[k].creationCallStack;
            if (cs[1] != undefined) {
                var s = e + "##" + cs[1];
                if (newObjectSet[s] == undefined) {
                    newObjectSet[s] = [];
                }
                newObjectSet[s].push(ks[k]);
            }
        }
    }
    return newObjectSet;
}
exports.splitByCaller = splitByCaller;
function analyzePaths(paths) {
    // data format: map from object id to
    // { accessPaths: Array of { path: Array of { object: string, property: string }, target: string }}
    // We will convert each access path to a TreeNode.
    var tnsAccum = [];
    var target;
    for (var i in paths) {
        var or = paths[i];
        for (var j in or.accessPaths) {
            var tns = tree.accessPath2TreeNodes(or.accessPaths[j].path.reverse(), 0);
            target = or.accessPaths[j].target; // TODO is this always the same value or not?
            tree.foldInto(tns, 0, tnsAccum);
        }
    }
    // tack on the real target as the root
    var r = new tree.TreeNode(target, "", 1, tnsAccum);
    return r;
}
exports.analyzePaths = analyzePaths;
//# sourceMappingURL=timeLine.js.map