#!/usr/bin/env node

var path = require('path'),

fs = require('../lib/less-node/fs'),
os = require("os"),
errno,
mkdirp;

try {

errno = require('errno');

} catch (err) {

errno = null;

}

var less = require('../lib/less-node'),

pluginLoader = new less.PluginLoader(less),
plugin,
plugins = [];

var args = process.argv.slice(1); var silent = false,

verbose = false,
options = {
depends: false,
compress: false,
max_line_len: -1,
lint: false,
paths: [],
color: true,
strictImports: false,
insecure: false,
rootpath: '',
relativeUrls: false,
ieCompat: true,
strictMath: false,
strictUnits: false,
globalVars: null,
modifyVars: null,
urlArgs: '',
plugins: plugins

}; var sourceMapOptions = {}; var continueProcessing = true,

currentErrorcode;

// calling process.exit does not flush stdout always // so use this to set the exit code process.on('exit', function() { process.reallyExit(currentErrorcode); });

var checkArgFunc = function(arg, option) {

if (!option) {
    console.error(arg + " option requires a parameter");
    continueProcessing = false;
    currentErrorcode = 1;
    return false;
}
return true;

};

var checkBooleanArg = function(arg) {

var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
if (!onOff) {
    console.error(" unable to parse " + arg + " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
    continueProcessing = false;
    currentErrorcode = 1;
    return false;
}
return Boolean(onOff[2]);

};

var parseVariableOption = function(option, variables) {

var parts = option.split('=', 2);
variables[parts[0]] = parts[1];

};

var sourceMapFileInline = false;

function printUsage() {

less.lesscHelper.printUsage();
pluginLoader.printUsage(plugins);
continueProcessing = false;

}

// self executing function so we can return (function() {

args = args.filter(function (arg) {
    var match;

    match = arg.match(/^-I(.+)$/);
    if (match) {
        options.paths.push(match[1]);
        return false;
    }

    match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
    if (match) {
        arg = match[1];
    } else {
        return arg;
    }

    switch (arg) {
        case 'v':
        case 'version':
            console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]");
            continueProcessing = false;
            break;
        case 'verbose':
            verbose = true;
            break;
        case 's':
        case 'silent':
            silent = true;
            break;
        case 'l':
        case 'lint':
            options.lint = true;
            break;
        case 'strict-imports':
            options.strictImports = true;
            break;
        case 'h':
        case 'help':
            printUsage();
            break;
        case 'x':
        case 'compress':
            options.compress = true;
            break;
        case 'insecure':
            options.insecure = true;
            break;
        case 'M':
        case 'depends':
            options.depends = true;
            break;
        case 'max-line-len':
            if (checkArgFunc(arg, match[2])) {
                options.maxLineLen = parseInt(match[2], 10);
                if (options.maxLineLen <= 0) {
                    options.maxLineLen = -1;
                }
            }
            break;
        case 'no-color':
            options.color = false;
            break;
        case 'no-ie-compat':
            options.ieCompat = false;
            break;
        case 'no-js':
            options.javascriptEnabled = false;
            break;
        case 'include-path':
            if (checkArgFunc(arg, match[2])) {
                // ; supported on windows.
                // : supported on windows and linux, excluding a drive letter like C:\ so C:\file:D:\file parses to 2
                options.paths = match[2]
                    .split(os.type().match(/Windows/) ? /:(?!\\)|;/ : ':')
                    .map(function(p) {
                        if (p) {
                            return path.resolve(process.cwd(), p);
                        }
                    });
            }
            break;
        case 'line-numbers':
            if (checkArgFunc(arg, match[2])) {
                options.dumpLineNumbers = match[2];
            }
            break;
        case 'source-map':
            options.sourceMap = true;
            if (match[2]) {
                sourceMapOptions.sourceMapFullFilename = match[2];
            }
            break;
        case 'source-map-rootpath':
            if (checkArgFunc(arg, match[2])) {
                sourceMapOptions.sourceMapRootpath = match[2];
            }
            break;
        case 'source-map-basepath':
            if (checkArgFunc(arg, match[2])) {
                sourceMapOptions.sourceMapBasepath = match[2];
            }
            break;
        case 'source-map-map-inline':
            sourceMapFileInline = true;
            options.sourceMap = true;
            break;
        case 'source-map-less-inline':
            sourceMapOptions.outputSourceFiles = true;
            break;
        case 'source-map-url':
            if (checkArgFunc(arg, match[2])) {
                sourceMapOptions.sourceMapURL = match[2];
            }
            break;
        case 'rp':
        case 'rootpath':
            if (checkArgFunc(arg, match[2])) {
                options.rootpath = match[2].replace(/\\/g, '/');
            }
            break;
        case "ru":
        case "relative-urls":
            options.relativeUrls = true;
            break;
        case "sm":
        case "strict-math":
            if (checkArgFunc(arg, match[2])) {
                options.strictMath = checkBooleanArg(match[2]);
            }
            break;
        case "su":
        case "strict-units":
            if (checkArgFunc(arg, match[2])) {
                options.strictUnits = checkBooleanArg(match[2]);
            }
            break;
        case "global-var":
            if (checkArgFunc(arg, match[2])) {
                if (!options.globalVars) {
                    options.globalVars = {};
                }
                parseVariableOption(match[2], options.globalVars);
            }
            break;
        case "modify-var":
            if (checkArgFunc(arg, match[2])) {
                if (!options.modifyVars) {
                    options.modifyVars = {};
                }

                parseVariableOption(match[2], options.modifyVars);
            }
            break;
        case 'url-args':
            if (checkArgFunc(arg, match[2])) {
                options.urlArgs = match[2];
            }
            break;
        case 'plugin':
            var splitupArg = match[2].match(/^([^=]+)(=(.*))?/),
                name = splitupArg[1],
                pluginOptions = splitupArg[3];

            plugin = pluginLoader.tryLoadPlugin(name, pluginOptions);
            if (plugin) {
                plugins.push(plugin);
            } else {
                console.error("Unable to load plugin " + name +
                    " please make sure that it is installed under or at the same level as less");
                currentErrorcode = 1;
            }
            break;
        default:
            plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]);
            if (plugin) {
                plugins.push(plugin);
            } else {
                console.error("Unable to interpret argument " + arg +
                    " - if it is a plugin (less-plugin-" + arg + "), make sure that it is installed under or at" +
                    " the same level as less");
                currentErrorcode = 1;
            }
            break;
    }
});

if (!continueProcessing) {
    return;
}

var input = args[1];
if (input && input != '-') {
    input = path.resolve(process.cwd(), input);
}
var output = args[2];
var outputbase = args[2];
if (output) {
    output = path.resolve(process.cwd(), output);
}

if (options.sourceMap) {

    sourceMapOptions.sourceMapInputFilename = input;
    if (!sourceMapOptions.sourceMapFullFilename) {
        if (!output && !sourceMapFileInline) {
            console.log("the sourcemap option only has an optional filename if the css filename is given");
            console.log("consider adding --source-map-map-inline which embeds the sourcemap into the css");
            return;
        }
        // its in the same directory, so always just the basename
        sourceMapOptions.sourceMapOutputFilename = path.basename(output);
        sourceMapOptions.sourceMapFullFilename = output + ".map";
        // its in the same directory, so always just the basename
        sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
    } else if (options.sourceMap && !sourceMapFileInline) {
        var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename),
            mapDir = path.dirname(mapFilename),
            outputDir = path.dirname(output);
        // find the path from the map to the output file
        sourceMapOptions.sourceMapOutputFilename = path.join(
            path.relative(mapDir, outputDir), path.basename(output));

        // make the sourcemap filename point to the sourcemap relative to the css file output directory
        sourceMapOptions.sourceMapFilename = path.join(
            path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
    }
}

if (sourceMapOptions.sourceMapBasepath === undefined) {
    sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
}

if (sourceMapOptions.sourceMapRootpath === undefined) {
    var pathToMap = path.dirname(sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename),
        pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename);
    sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
}

if (! input) {
    console.error("lessc: no input files");
    console.error("");
    printUsage();
    currentErrorcode = 1;
    return;
}

var ensureDirectory = function (filepath) {
    var dir = path.dirname(filepath),
        cmd,
        existsSync = fs.existsSync || path.existsSync;
    if (!existsSync(dir)) {
        if (mkdirp === undefined) {
            try {mkdirp = require('mkdirp');}
            catch(e) { mkdirp = null; }
        }
        cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
        cmd(dir);
    }
};

if (options.depends) {
    if (!outputbase) {
        console.log("option --depends requires an output path to be specified");
        return;
    }
    process.stdout.write(outputbase + ": ");
}

if (!sourceMapFileInline) {
    var writeSourceMap = function(output, onDone) {
        output = output || "";
        var filename = sourceMapOptions.sourceMapFullFilename;
        ensureDirectory(filename);
        fs.writeFile(filename, output, 'utf8', function (err) {
            if (err) {
                var description = "Error: ";
                if (errno && errno.errno[err.errno]) {
                    description += errno.errno[err.errno].description;
                } else {
                    description += err.code + " " + err.message;
                }
                console.error('lessc: failed to create file ' + filename);
                console.error(description);
            } else {
                less.logger.info('lessc: wrote ' + filename);
            }
            onDone();
        });
    };
}

var writeSourceMapIfNeeded = function(output, onDone) {
    if (options.sourceMap && !sourceMapFileInline) {
        writeSourceMap(output, onDone);
    } else {
        onDone();
    }
};

var writeOutput = function(output, result, onSuccess) {
    if (output) {
        ensureDirectory(output);
        fs.writeFile(output, result.css, {encoding: 'utf8'}, function (err) {
            if (err) {
                var description = "Error: ";
                if (errno && errno.errno[err.errno]) {
                    description += errno.errno[err.errno].description;
                } else {
                    description += err.code + " " + err.message;
                }
                console.error('lessc: failed to create file ' + output);
                console.error(description);
            } else {
                less.logger.info('lessc: wrote ' + output);
                onSuccess();
            }
        });
    } else if (!options.depends) {
        process.stdout.write(result.css);
        onSuccess();
    }
};

var logDependencies = function(options, result) {
    if (options.depends) {
        var depends = "";
        for (var i = 0; i < result.imports.length; i++) {
            depends += result.imports[i] + " ";
        }
        console.log(depends);
    }
};

var parseLessFile = function (e, data) {
    if (e) {
        console.error("lessc: " + e.message);
        currentErrorcode = 1;
        return;
    }

    data = data.replace(/^\uFEFF/, '');

    options.paths = [path.dirname(input)].concat(options.paths);
    options.filename = input;

    if (options.lint) {
        options.sourceMap = false;
    }
    sourceMapOptions.sourceMapFileInline = sourceMapFileInline;

    if (options.sourceMap) {
        options.sourceMap = sourceMapOptions;
    }

    less.logger.addListener({
        info: function(msg) {
            if (verbose) {
                console.log(msg);
            }
        },
        warn: function(msg) {
            // do not show warning if the silent option is used
            if (!silent) {
                console.warn(msg);
            }
        },
        error: function(msg) {
            console.error(msg);
        }
    });

    less.render(data, options)
        .then(function(result) {
            if (!options.lint) {
                writeOutput(output, result, function() {
                    writeSourceMapIfNeeded(result.map, function() {
                        logDependencies(options, result);
                    });
                });
            }
        },
        function(err) {
            less.writeError(err, options);
            currentErrorcode = 1;
        });
};

if (input != '-') {
    fs.readFile(input, 'utf8', parseLessFile);
} else {
    process.stdin.resume();
    process.stdin.setEncoding('utf8');

    var buffer = '';
    process.stdin.on('data', function(data) {
        buffer += data;
    });

    process.stdin.on('end', function() {
        parseLessFile(false, buffer);
    });
}

})();