define(function(require, exports, module) {
    "use strict";
    
    main.consumes = ["cdn.build"];
    return main;

    function main(options, imports, register) {
        var build = imports["cdn.build"];
        
        var fs = require("fs");
        var path = require("path");
        var mkdirp = require("mkdirp");
        var async = require("async");
        
        var moduleDeps = require("architect-build/module-deps");

        var root = path.join(build.cacheDir, build.version);
        
        var cache = {};
        build.setCache(cache);

        build.getPathConfig(build.version, function(err, pathConfig) {
            if (err) return done(err);
            var pending = 1;
            
            if (options.skin && options.config) {
                build.buildSkin(options.config, options.skin, pathConfig, save(["skin", options.config, options.skin + ".css"]));
            } else if (options.config) {
                var seen = Object.create(null);
                var configs = options.config.split(/,\s*/).filter(function(x) {
                    if (seen[x]) return false;
                    return seen[x] = true;
                });
                var configCache = options.skipDuplicates && { duplicates: []};
                var usedPlugins = options.copyStaticResources && Object.create(null);
                
                (function buildConfig() {
                    var config = configs.shift();
                    if (!config) {
                        var series = [];
                        if (usedPlugins)
                            series.push(copyStaticResources.bind(null, usedPlugins, pathConfig));
                        if (configCache && configCache.duplicates.length)
                            series.push(fs.writeFile.bind(fs, path.join(root, "config", "duplicates"), configCache.duplicates.join(" "), "utf8"));
                        series.push(done.bind(null, null));
                        return async.series(series);
                    }
                    var skins = options.withSkins;
                    if (skins === true || skins === "all")
                        skins = ["dark", "light", "dark-gray", "light-gray", "flat-light", "flat-dark", "jett-dark"];
                    else
                        skins = skins ? skins.split(/,\s*/) : [];
                    
                    build.buildConfig(config, pathConfig, function(err, result) {
                        save(["config", path.basename(config) + ".js"], function next(err) {
                            if (err) return done(err);
                            var skin = skins.pop();
                            if (!skin) return buildConfig();
                            build.buildSkin({
                                sources: result.sources,
                                config: [],
                            }, skin, pathConfig, save(["skin", config, skin + ".css"], next));
                        })(err, result);
                    }, function(configName, data) {
                        var pluginPaths = data.map(function(p) {
                            return typeof p == "string" ? p : p.packagePath;
                        }).filter(Boolean).sort();
                        
                        if (usedPlugins) {
                            pluginPaths.forEach(function(p) {
                                usedPlugins[p] = true;
                            });
                        }
                        
                        if (!configCache) return;
                        
                        if (!configCache.defaultConfig) {
                            configCache.defaultConfig = Object.create(null);
                            pluginPaths.forEach(function(p) {
                                configCache.defaultConfig[p] = true;
                            });
                            return false;
                        }
                        
                        var isDuplicate = pluginPaths.every(function(p) {
                            return configCache.defaultConfig[p];
                        });
                        
                        if (!isDuplicate) return false;
                        
                        configCache.duplicates.push(configName);
                        process.nextTick(buildConfig);
                        return true;
                    });
                })();
                
            } else if (options.worker) {
                build.buildWorker(options.worker, pathConfig, save(["worker", options.worker + ".js"]));
            } else if (options.module) {
                if (options.module === "ace") {
                    listAceModules(pathConfig, function(module, next) {
                        if (next) pending++;
                        build.buildAce(module, pathConfig, function(err, result) {
                            save(["modules", module + ".js"], function(err) {
                                warn(err);
                                done();
                            })(err, result);
                            if (next) process.nextTick(next); // start building next without waiting for write
                        });
                    });
                } else if (options.module.match(/^ace\//)) {
                    build.buildAce(options.module, pathConfig, save(["modules", options.module + ".js"]));
                } else {
                    build.buildModule(options.module, pathConfig, save(["modules", options.module + ".js"]));
                }
            } else {
                done(new Error("unknown action"));
            }
            
            function warn(err) {
                if (err) console.warn(err);
            }
            
            function save(nameParts, cb) {
                return function(err, result) {
                    if (err) return cb(err);
                    var code = result.code;
                    nameParts.unshift(root);

                    writeFile(nameParts, code, cb || done);

                    if (result.sources && options.compressOutputDirPrefix) {
                        // compress and save in parallel
                        pending++;
                        var compress = require("architect-build/compress");
                        code = compress.withCache(result.sources, {
                            cache: cache,
                            obfuscate: options.obfuscate,
                            exclude: /(jsoniq|xquery)\.js$/
                        }).code;
                        nameParts[0] += "/" + options.compressOutputDirPrefix;
                        writeFile(nameParts, code, done);
                    }
                    
                    if (result.sources) {
                        pending++;
                        async.forEach(result.sources, function(pkg, next) {
                            var deps = moduleDeps.getSubmodules(pkg.source, pkg.id);
                            if (pkg.submodules)
                                deps = deps.concat(pkg.submodules);
                            if (!deps.length)
                                return next();
                            
                            async.forEach(deps, function(moduleName, next) {
                                console.log(moduleName, pkg.id);
                                build.buildModule(moduleName, pathConfig, save(["modules", moduleName + ".js"], next));
                            }, next);
                        }, done);
                    }
                };
            }
            
            function writeFile(filename, code, cb) {
                if (typeof filename != "string")
                    filename = path.join.apply(path, filename);
                mkdirp(path.dirname(filename), function(err) {
                    if (err) return cb(err);
                    fs.writeFile(filename, code, "utf8", function(err) {
                        if (err) return cb(err);
                        console.log("file saved at", filename);
                        cb && cb();
                    });
                });
            }
            
            function done(err) {
                if (err) {
                    console.error(err, err.stack);
                    console.trace();
                    process.exit(1);
                }
                pending--;
                if (pending <= 0)
                    process.exit();
            }
        });
        
        function listAceModules(pathConfig, cb) {
            var result = [
                "plugins/c9.ide.ace.keymaps/vim/keymap",
                "plugins/c9.ide.ace.keymaps/emacs/keymap",
                "plugins/c9.ide.ace.keymaps/sublime/keymap",
                "plugins/c9.ide.theme.jett/ace.themes/jett"
            ];
            
            // FIXME: this could be resolved via pathConfig:
            var pathMap = {
                "ace": __dirname + "/../node_modules/ace/lib/ace",
                "plugins": __dirname + "/../../plugins",
                "plugins/salesforce.language": __dirname + "/../../node_modules/salesforce.language",
                "plugins/salesforce.sync": __dirname + "/../../node_modules/salesforce.sync"
            };
            
            var packages = [
                "ace",
                "plugins/salesforce.language",
                "plugins/salesforce.sync",
            ];

            function toFsPath(id) {
                var testPath = id, tail = "";
                while (testPath) {
                    if (pathMap[testPath])
                        return pathMap[testPath] + tail;
                    var i = testPath.lastIndexOf("/");
                    if (i === -1) break;
                    tail = testPath.substr(i) + tail;
                    testPath = testPath.slice(0, i);
                }
                throw new Error("Cannot map path " + id);
            }
            
            function readPackage(name, type, excludePattern) {
                if (!excludePattern)
                    excludePattern = /_test/;
                
                var targetPath = name + "/" + type;
                var sourcePath = toFsPath(targetPath);
                
                try {
                    var files = fs.readdirSync(sourcePath);
                } catch (e) {
                    if (e.code === "ENOENT") return;
                    else throw e;
                }
                
                files = files.filter(function(p) {
                    return !excludePattern.test(p)
                        && !/[\s#]/.test(p)
                        && /\.js$/.test(p);
                });
                
                files.forEach(function(p) {
                    result.push(targetPath + "/" + p.replace(/.js$/, ""));
                });
            }
            
            packages.forEach(function(name) {
                var isAce = (name === "ace");
                var modesExcludePattern = /_highlight_rules|_test|_worker|xml_util|_outdent|behaviour|completions/;
                
                readPackage(name, (isAce ? "mode" : "modes"), modesExcludePattern);
                readPackage(name, (isAce ? "theme" : "themes"));
                readPackage(name, "ext");
                readPackage(name, "snippets");
            });
            
            function take() {
                var p = result.pop();
                console.log("building ", p, result.length);
                cb(p, result.length && take);
            }
            take();
        }
        
        function copyStaticResources(usedPlugins, pathConfig, next) {
            var moduleDeps = require("architect-build/module-deps");
            var copy = require('architect-build/copy');
            // todo: use usedPlugins to filter out some of unneeded deps
            var roots = pathConfig.pathMap;
            Object.keys(roots).filter(function(p) {
                return !/(c9.docker|docs|smith|vfs-socket|engine.io|msgpack.js)$/.test(p);
            }).forEach(function(p) {
                var absPath = moduleDeps.resolveModulePath(p, pathConfig.pathMap);
                if (/^\/lib\/ace/.test(p)) 
                    return;
                if (!cache.files) {
                    console.error("Config is empty");
                    process.exit(1);
                }
                copy(absPath, root + "/static/" + p, {
                    include: /^(remarkable.min.js|runners_list.js|builders_list.js|rusha.min.js|bootstrap.js)$/,
                    exclude: function(name, dir) {
                        if (/\.css$/.test(name)) {
                            if (!cache.files[dir + "/" + name]) {
                                console.log("\x1b [1A\x1b[0K", "adding file ", dir + "/" + name);
                                return false;
                            }
                        }
                        if (name == "node_modules" && /plugins\/[^\/\\]+/.test(dir))
                            return true;
                        if (/^(LICENSE|README.md)$/.test(name))
                            return true;
                        return /\.(jsx?|css|less|xml|ejs|prv|pub|sh)$|(^|[/])^(mock|example|\.[^/]*|package.json)[/]?$/.test(name);
                    },
                    onDir: function(e) { console.log("\x1b [1A\x1b[0K" + e); }
                });
            });
            next();
        }
    }
});
