var browserify = require('../');
var path = require('path');
var spawn = require('child_process').spawn;
var parseShell = require('shell-quote').parse;
var insertGlobals = require('insert-module-globals');
var duplexer = require('duplexer2');
var subarg = require('subarg');
var glob = require('glob');
var Readable = require('readable-stream').Readable;
var xtend = require('xtend');

module.exports = function (args, opts) {
    var argv = subarg(args, {
        'boolean': [
            'deps', 'pack', 'ig', 'dg', 'im', 'd', 'list', 'builtins',
            'commondir', 'bare', 'full-paths', 'bundle-external', 'bf',
            'node'
        ],
        string: [ 's', 'r', 'u', 'x', 't', 'i', 'o', 'e', 'c', 'it' ],
        alias: {
            ig: [ 'insert-globals', 'fast' ],
            dg: [ 'detect-globals', 'detectGlobals', 'dg' ],
            bf: [ 'browser-field', 'browserField' ],
            im: 'ignore-missing',
            it: 'ignore-transform',
            igv: 'insert-global-vars',
            d: 'debug',
            s: 'standalone',
            noParse: [ 'noparse' ],
            'full-paths': [ 'fullpaths', 'fullPaths' ],
            r: 'require',
            u: 'exclude',
            x: 'external',
            t: 'transform',
            i: 'ignore',
            o: 'outfile',
            e: 'entry',
            c: 'command',
            bare: 'bear'
        },
        'default': {
            ig: false,
            im: false,
            dg: true,
            d: false,
            builtins: true,
            commondir: true,
            'bundle-external': true,
            bf: true,
            node: false
        }
    });
    
    var entries = argv._.concat(argv.entry)
    .filter(Boolean).map(function (entry) {
        if (entry === '-') {
            var s = process.stdin;
            if (typeof s.read === 'function') return s;
            // only needed for 0.8, remove at some point later:
            var rs = Readable().wrap(s);
            s.resume();
            return rs;
        }
        return entry;
    });
    
    if (argv.node) {
        argv.bare = true;
        argv.browserField = false;
    }
    if (argv.bare) {
        argv.builtins = false;
        argv.commondir = false;
        argv.detectGlobals = false;
        if (argv.igv === undefined) {
            argv.igv = '__filename,__dirname';
        }
    }

    var ignoreTransform = argv['ignore-transform'] || argv.it;
    var b = browserify(xtend({
        noParse: Array.isArray(argv.noParse) ? argv.noParse : [argv.noParse],
        extensions: [].concat(argv.extension).filter(Boolean),
        ignoreTransform: [].concat(ignoreTransform).filter(Boolean),
        entries: entries,
        fullPaths: argv['full-paths'],
        builtins: argv.builtins === false ? false : undefined,
        commondir: argv.commondir === false ? false : undefined,
        bundleExternal: argv['bundle-external'],
        basedir: argv.basedir,
        browserField: argv.browserField,
        
        detectGlobals: argv.detectGlobals,
        insertGlobals: argv['insert-globals'] || argv.ig,
        insertGlobalVars: insertGlobalVars,
        ignoreMissing: argv['ignore-missing'] || argv.im,
        debug: argv['debug'] || argv.d,
        standalone: argv['standalone'] || argv.s
    }, opts));
    function error (msg) {
        var e = new Error(msg);
        process.nextTick(function () { b.emit('error', e) });
    }
    b.argv = argv;
    [].concat(argv.p).concat(argv.plugin).filter(Boolean)
        .forEach(function (p) {
            var pf = p, pOpts = {};
            if (typeof p === 'object') {
                pf = p._.shift(), pOpts = p;
            }
            b.plugin(pf, pOpts);
        })
    ;
    
    [].concat(argv.ignore).filter(Boolean)
        .forEach(function (i) {
            b._pending ++;
            glob(i, function (err, files) {
                if (err) return b.emit('error', err);
                if (files.length === 0) {
                  b.ignore(i);
                }
                else {
                  files.forEach(function (file) { b.ignore(file) });
                }
                if (--b._pending === 0) b.emit('_ready');
            });
        })
    ;
    
    [].concat(argv.exclude).filter(Boolean)
        .forEach(function (u) {
            b.exclude(u);
            
            b._pending ++;
            glob(u, function (err, files) {
                if (err) return b.emit('error', err);
                files.forEach(function (file) { b.exclude(file) });
                if (--b._pending === 0) b.emit('_ready');
            });
        })
    ;

    [].concat(argv.require).filter(Boolean)
        .forEach(function (r) {
            var xs = splitOnColon(r);
            b.require(xs[0], { expose: xs.length === 1 ? xs[0] : xs[1] })
        })
    ;
    
    // resolve any external files and add them to the bundle as externals
    [].concat(argv.external).filter(Boolean)
        .forEach(function (x) {
            var xs = splitOnColon(x);
            if (xs.length === 2) {
                add(xs[0], { expose: xs[1] });
            }
            else if (/\*/.test(x)) {
                b.external(x);
                glob(x, function (err, files) {
                    files.forEach(function (file) {
                        add(file, {});
                    });
                });
            }
            else add(x, {});
            
            function add (x, opts) {
                if (/^[\/.]/.test(x)) b.external(path.resolve(x), opts)
                else b.external(x, opts)
            }
        })
    ;
    
    [].concat(argv.transform)
        .filter(Boolean)
        .forEach(function (t) { addTransform(t) })
    ;
    
    [].concat(argv.g).concat(argv['global-transform'])
        .filter(Boolean)
        .forEach(function (t) {
            addTransform(t, { global: true });
        })
    ;
    
    function addTransform (t, opts) {
        if (typeof t === 'string' || typeof t === 'function') {
            b.transform(opts, t);
        }
        else if (t && typeof t === 'object') {
            if (!t._[0] || typeof t._[0] !== 'string') {
                return error(
                    'expected first parameter to be a transform string'
                );
            }
            if (opts) Object.keys(opts).forEach(function (key) {
                t[key] = opts[key];
            });
            b.transform(t, t._.shift());
        }
        else error('unexpected transform of type ' + typeof t);
    }
    
    [].concat(argv.command).filter(Boolean)
        .forEach(function (c) {
            var cmd = parseShell(c);
            b.transform(function (file) {
                var env = Object.keys(process.env).reduce(function (acc, key) {
                    acc[key] = process.env[key];
                    return acc;
                }, {});
                env.FILENAME = file;
                var ps = spawn(cmd[0], cmd.slice(1), { env: env });
                var error = '';
                ps.stderr.on('data', function (buf) { error += buf });
                
                ps.on('exit', function (code) {
                    if (code === 0) return;
                    console.error([
                        'error running source transform command: ' + c,
                        error.split('\n').join('\n  '),
                        ''
                    ].join('\n'));
                    process.exit(1);
                });
                return duplexer(ps.stdin, ps.stdout);
            });
        })
    ;
    
    if (argv.standalone === '') {
        error('--standalone requires an export name argument');
        return b;
    }
    
    var insertGlobalVars;
    if (argv.igv) {
        insertGlobalVars = argv.igv.split(',').reduce(function (vars, x) {
            vars[x] = insertGlobals.vars[x];
            return vars;
        }, {});
    }
    
    return b;
};

function splitOnColon (f) {
    var pos = f.lastIndexOf(':');
    if (pos == -1) {
        return [f]; // No colon
    } else {
        if ((/[a-zA-Z]:[\\/]/.test(f)) && (pos == 1)){
            return [f]; // Windows path and colon is part of drive name
        } else {
            return [f.substr(0, pos), f.substr(pos + 1)];
        }
    }
}
