var fs = require('fs');
var path = require('path');
var esprima = require('esprima');
var escodegen = require('escodegen');
var util = require('./util');
var logIfErr = util.logIfErr;
var makeArray = util.makeArray;

var scooper = (function(){

    function makeRequireDetector(detector){
        var defineFuncDetector = [
            ['expression', 'ExpressionStatement', [[['expression', 'callee', 'name'], 'define']]],
            ['arguments', 'CallExpression'],
            ['body', 'FunctionExpression'],
            ['body', 'BlockStatement']];

        return defineFuncDetector.concat(makeIIFEDetector(detector));
    }

    function makeIIFEDetector(detector){
        var IIFEFuncDetector = [
            ['declarations', 'VariableDeclaration'],
            ['init', 'VariableDeclarator'],
            ['callee', 'CallExpression'],
            ['body', 'FunctionExpression'],
            ['body', 'BlockStatement']];

        return IIFEFuncDetector.concat(detector);
    }

    function scoop(filePath, scoopType, sendFunc){
        var parseTree;
        var returnObject;
        var propertiesJSON;
        var scoopOJasmine;
        var scoopedNodes
        var returnNodes;

        fs.readFile(filePath, 'utf8', function(err, contents){
            if (err !== undefined && err !== null){
                console.dir(err);
                console.dir('sending empty response');
                sendFunc(new Buffer(''));
            }
            else{
                parseTree = esprima.parse(contents);

                scoopedNodes = scoopNodes(parseTree, scoopType, [[
                    ['id', 'FunctionDeclaration'],
                    ['name', 'Identifier']
                ],[
                    ['declarations', 'VariableDeclaration'],
                    ['id', 'VariableDeclarator'],
                    ['name', 'Identifier']
                ]]);

                returnNodes = scoopNodes(parseTree, scoopType, [[
                    ['argument', 'ReturnStatement']
                ]]);

                if( returnNodes.length === 1 ){
                    propertiesJSON = getScoopPropertiesAsJSON(scoopedNodes);
                    returnObject = returnNodes[0];
                    returnObject.properties.push(propertiesJSON);
                    scoopOJasmine = escodegen.generate(parseTree);
                    sendFunc(new Buffer(String(scoopOJasmine)));
                }
                else{
                    if ( returnNodes.length > 1 ){
                        console.dir('"' + path.basename(filePath) + '" not scooped: Detected more than one scoopable module');
                    }
                    else{
                        console.dir('"' + path.basename(filePath) + '" not scooped: No scoopable module');
                    }
                    sendFunc(contents);
                }
            }
        });
    }

    function getScoopPropertiesAsJSON(scoopedNodes){
        var scoopString;
        var parseTree;
        var properties;
        var __scoop__ = {};
        var scoopString;

        for(var i in scoopedNodes){
            __scoop__[scoopedNodes[i]] = scoopedNodes[i];
        }

        scoopString = JSON.stringify(__scoop__);
        scoopString = scoopString.replace(/"/g, '');
        scoopString = 'var wrapper = { __scoop__: ' + scoopString + '};';
        parseTree = esprima.parse(scoopString);
        properties = scoopNodes(parseTree, 'none', [[
            ['declarations', 'VariableDeclaration'],
            ['init', 'VariableDeclarator'],
            ['properties', 'ObjectExpression']
        ]]);

        return properties[0];
    }

    function scoopNodes(parseTree, scoopType, detectors){
        var nodesFound;
        var detector;
        var requireDetectors = [];
        var IIFEDetectors = [];
        var scoopedNodes = [];
        var validScoopTypes = ['require', 'IIFE', 'none'];

        if(validScoopTypes.indexOf(scoopType) == -1)
            util.exitError('Invalid scoopType: ' + scoopType + '\n Valid scoopTypes: ' + validScoopTypes);

        for(var i in detectors){
            if (scoopType == 'IIFE')
                IIFEDetectors.push(makeIIFEDetector(detectors[i]));
            if (scoopType == 'require')
                requireDetectors.push(makeRequireDetector(detectors[i]));
        }
        if(scoopType != 'none')
            detectors = IIFEDetectors.concat(requireDetectors);

        for(var i in detectors){
            nodesFound = parseTree.body;
            detector = detectors[i];
            for(var j in detector){
                nodesFound = findNodes(nodesFound, detector[j][0], detector[j][1], detector[j][2]);
            }
            scoopedNodes = scoopedNodes.concat(nodesFound);
        }

        return scoopedNodes;
    }

    function findNodes(nodesFound, nodeToGetIfType, type, conditions){
        var newNodesFound = [];
        var conditionsMet;
        var propertyArray;
        var property;

        nodesFound = makeArray(nodesFound);
        for(var i in nodesFound){
            if(nodesFound[i].type === type){
                conditionsMet = true;
                if(conditions !== undefined){
                    for(var j in conditions){
                        propertyArray = conditions[j][0];
                        property = getNestedProperty(nodesFound[i], propertyArray);
                        if(property !== conditions[j][1]){
                            conditionsMet = false;
                        }
                    }
                }
                if(conditionsMet){
                    newNodesFound = newNodesFound.concat(makeArray(nodesFound[i][nodeToGetIfType]));
                }
            }
        }

        return newNodesFound;
    }

    function getNestedProperty(object, propertyArray){
        var childObject = object;

        for(var i in propertyArray){
            childObject = childObject[propertyArray[i]];
            if(childObject === undefined){
                return undefined;
            }
        }

        return childObject;
    }

    return {
        scoop: scoop
    }
}());

module.exports = scooper;