/**
 * ngTweet - Angular directives for better Twitter integration.
 *
 * @license
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Aru Sahni, http://arusahni.net
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
(function() {
'use strict';

angular
    .module('ngtweet', [])
    .value('ngTweetLogVerbose', true)
    .value('twitterWidgetURL', 'https://platform.twitter.com/widgets.js');
})();

(function() {
'use strict';

ngTweetLogger.$inject = ["$log", "ngTweetLogVerbose"];
angular
    .module('ngtweet')
    .factory('ngTweetLogger', ngTweetLogger);

function ngTweetLogger($log, ngTweetLogVerbose) {
    var noop = function() {};

    var verboseCall = function verboseCall(call) {
        if (ngTweetLogVerbose === true) {
            return call;
        }
        return noop;
    };

    return {
        'log': verboseCall($log.log),
        'debug': verboseCall($log.debug),
        'info': verboseCall($log.info),
        'warn': $log.warn,
        'error': $log.error
    };
}
})();

(function() {
'use strict';

TwitterTimeline.$inject = ["ngTweetLogger", "TwitterWidgetFactory"];
angular
    .module('ngtweet')
    .directive('twitterTimeline', TwitterTimeline);

function TwitterTimeline(ngTweetLogger, TwitterWidgetFactory) {
    /**
     * An error
     */
    function TimelineArgumentException(timelineType, message) {
        this.timelineType = timelineType;
        this.message = message;
    }

    var scopeParamsMap = {
        id: '=?twitterTimelineId',
        screenName: '=?twitterTimelineScreenName',
        sourceType: '@?twitterTimelineType',
        userId: '=?twitterTimelineUserId',
        ownerScreenName: '=?twitterTimelineOwnerScreenName',
        slug: '=?twitterTimelineSlug',
        url: '=?twitterTimelineUrl',
    };

    // Convert the scope params to a collection of bound args
    var scopeParams = Object.keys(scopeParamsMap).reduce(function(acc, keyName) {
        if (scopeParamsMap[keyName][0] === '=') { // if two-way bound
            acc.push(keyName);
        }
        return acc;
    }, []);

    // Rules for the given parameter
    var rules = {
        profile: [['screenName'], ['userId']],
        likes: [['screenName'], ['userId']],
        collection: [['id']],
        widget: [['id']],
        list: [['id'], ['ownerScreenName', 'slug']],
        url: [['url']],
    };

    /**
     * Diff two arrays.
     * @param {Array} arr1 - The first array
     * @param {Array} arr2 - The second array
     * @return {boolean} true if the arrays are different, false otherwise
     */
    function diffArrays(arr1, arr2) {
        if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
            return true;
        }
        if (arr1.length !== arr2.length) {
            return true;
        }
        for (var i = 0, len = arr1.length; i < len; i++) {
            if (arr1[i] !== arr2[i]) {
                return true;
            }
        }
        return false;
    }

    /**
     * Turn the rules for a sourceType into a human-readable string
     * @param {string[][]} sourceRule - The sourceType rules
     * @return {string} the readable rules
     */
    function getSourceRuleString(sourceRule) {
        function getRuleString(rule) {
            if (rule.length === 1) {
                return '"' + rule + '"';
            }
            return '("' + rule.join('" AND "') + '")';
        }

        return sourceRule.map(getRuleString).join(' OR ');
    }

    /**
     * Get the arguments for the timeline based on parameter rules
     * @param {Object} scope - The directive scope
     * @return {Object} Key-Value arg object
     */
    function getTimelineArgs(scope) {
        var timelineArgs = {sourceType: scope.sourceType};
        // if this is a valid sourceType...
        if (rules.hasOwnProperty(scope.sourceType)) {
            var sourceRules = rules[scope.sourceType];
            var valid = false;
            // Loop over the required args for the source
            for (var i = 0, len = sourceRules.length; i < len; i++) {
                var rule = sourceRules[i];
                var params = {};
                for (var j = 0, ruleLen = rule.length; j < ruleLen; j++) {
                    if (angular.isDefined(scope[rule[j]])) { // if the rule is present, add it to the params collection
                        params[rule[j]] = scope[rule[j]];
                    }
                }
                if (Object.keys(params).length === ruleLen) {
                    angular.merge(timelineArgs, params);
                    valid = true;
                    break;
                }
            }
            if (!valid) {
                throw new TimelineArgumentException(scope.sourceType, 'args: ' + getSourceRuleString(sourceRules));
            }
        } else {
            throw new TimelineArgumentException(scope.sourceType, 'unknown type');
        }

        return timelineArgs;
    }

    function link(scope, element, attrs) {
        ngTweetLogger.debug('Linking', scope, element, attrs);
        if (scope.id && !angular.isString(scope.id)) {
            ngTweetLogger.warn('twitterTimelineId should probably be a string due to loss of precision.');
        }
        try {
            scope.twitterTimelineOptions = JSON.parse(attrs.twitterTimelineOptions);
        } catch (e) {
            scope.$watch(function() {
                return scope.$parent.$eval(attrs.twitterTimelineOptions);
            }, function(newValue, oldValue) {
                scope.twitterTimelineOptions = newValue;
            });
        }
        if (angular.isUndefined(scope.twitterTimelineOptions)) {
            scope.twitterTimelineOptions = {};
        }
        if (scope.sourceType) { // new style embed
            scope.$watchGroup(scopeParams, function(newValue, oldValue) {
                if (diffArrays(oldValue, newValue)) { //replacing, clear node
                    angular.element(element[0]).empty();
                }
                if (newValue.every(function isEmpty(arg) { return !arg; })) {
                    ngTweetLogger.debug('Falsey args received. Not rendering the timeline.');
                    return;
                }
                var timelineArgs;
                try {
                    timelineArgs = getTimelineArgs(scope);
                } catch (e) {
                    ngTweetLogger.error('Could not create new timeline: bad args for type "' +
                                        e.timelineType + '". Reason: ' + e.message);
                    return;
                }
                TwitterWidgetFactory.createTimelineNew(timelineArgs, element[0], scope.twitterTimelineOptions)
                    .then(function success(embed) {
                    ngTweetLogger.debug('New Timeline Success!');
                }).catch(function creationError(message) {
                    ngTweetLogger.error('Could not create new timeline: ', message, element);
                });
            });
        } else if (angular.isDefined(scope.id) || angular.isString(scope.screenName)) { // old style
            scope.$watch('id', function(newValue, oldValue) {
                if (scope.id === null) {
                    return;
                }
                if (oldValue !== undefined && newValue !== oldValue) {
                    angular.element(element[0]).empty();
                }
                TwitterWidgetFactory.createTimeline(scope.id, scope.screenName, element[0],
                        scope.twitterTimelineOptions).then(function success(embed) {
                    ngTweetLogger.debug('Timeline Success!!!');
                }).catch(function creationError(message) {
                            ngTweetLogger.error('Could not create timeline: ', message, element);
                        });
            });
        } else {
            TwitterWidgetFactory.load(element[0]);
        }
    }

    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: scopeParamsMap,
        template: '

',
        link: link
    };
}
})();

(function() {
'use strict';

TwitterWidget.$inject = ["ngTweetLogger", "TwitterWidgetFactory"];
angular
    .module('ngtweet')
    .directive('twitterWidget', TwitterWidget);

function TwitterWidget(ngTweetLogger, TwitterWidgetFactory) {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {
            twitterWidgetId: '=',
            twitterWidgetOnRendered: '&',
            twitterWidgetOptions: '@'
        },
        template: '

',
        link: function(scope, element, attrs) {
            scope.$watch('twitterWidgetId', function(newValue, oldValue) {
                ngTweetLogger.debug('Linking', element, attrs);
                var twitterWidgetOptions = scope.$eval(attrs.twitterWidgetOptions);
                if (oldValue !== undefined && newValue !== oldValue) { //replacing, clear node.
                    angular.element(element[0]).empty();
                }
                if (!angular.isUndefined(scope.twitterWidgetId)) {
                    if (!angular.isString(scope.twitterWidgetId)) {
                        ngTweetLogger.warn('twitterWidgetId should probably be a string due to loss of precision.');
                    }
                    TwitterWidgetFactory.createTweet(scope.twitterWidgetId, element[0], twitterWidgetOptions).then(function success(embed) {
                        ngTweetLogger.debug('Created tweet widget: ', scope.twitterWidgetId, element);
                        scope.twitterWidgetOnRendered();
                    }).catch(function creationError(message) {
                        ngTweetLogger.error('Could not create widget: ', message, element);
                    });
                } else {
                    TwitterWidgetFactory.load(element[0]);
                }
            });
        }
    };
}
})();

(function() {
'use strict';

TwitterWidgetFactory.$inject = ["$document", "$http", "ngTweetLogger", "twitterWidgetURL", "$q", "$window"];
angular
    .module('ngtweet')
    .factory('TwitterWidgetFactory', TwitterWidgetFactory);

function TwitterWidgetFactory($document, $http, ngTweetLogger, twitterWidgetURL, $q, $window) {
    var deferred;
    var statusRe = /.*\/status\/(\d+)/;

    function startScriptLoad() {
        $window.twttr = (function(d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0],
                t = $window.twttr || {};
            if (d.getElementById(id)) { return; }
            js = d.createElement(s);
            js.id = id;
            js.src = twitterWidgetURL;
            fjs.parentNode.insertBefore(js, fjs);

            t._e = [];
            t.ready = function(f) {
                t._e.push(f);
            };

            return t;
        }($document[0], 'script', 'twitter-wjs'));
    }

    function isScriptLoaded() {
        return $window.twttr && $window.twttr.init;
    }

    function loadScript() {
        if (!angular.isUndefined(deferred)) {
            return deferred.promise;
        }
        deferred = $q.defer();
        if (isScriptLoaded()) {
            return deferred.resolve($window.twttr);
        }

        startScriptLoad();
        $window.twttr.ready(function onLoadTwitterScript(twttr) {
            ngTweetLogger.debug('Twitter script ready');
            twttr.events.bind('rendered', onTweetRendered);
            deferred.resolve(twttr);
        });
        return deferred.promise;
    }

    function onTweetRendered(event) {
        ngTweetLogger.debug('Tweet rendered', event.target.parentElement.attributes);
    }

    function createTweet(id, element, options) {
        return loadScript().then(function success(twttr) {
            ngTweetLogger.debug('Creating Tweet', twttr, id, element, options);
            return $q.when(twttr.widgets.createTweet(id, element, options));
        });
    }

    function createTimeline(id, screenName, element, options) {
        return loadScript().then(function success(twttr) {
            ngTweetLogger.debug('Creating Timeline', id, screenName, options, element);
            if (angular.isString(screenName) && screenName.length > 0) {
                options['screenName'] = screenName;
            }
            return $q.when(twttr.widgets.createTimeline(id, element, options));
        });
    }

    function createTimelineNew(timelineArgs, element, options) {
        return loadScript().then(function success(twttr) {
            ngTweetLogger.debug('Creating new Timeline', timelineArgs, options, element);
            return $q.when(twttr.widgets.createTimeline(timelineArgs, element, options));
        });
    }

    function wrapElement(element) {
        loadScript().then(function success(twttr) {
            ngTweetLogger.debug('Wrapping', twttr, element);
            twttr.widgets.load(element);
        }).catch(function errorWrapping(message) {
            ngTweetLogger.error('Could not wrap element: ', message, element);
        });
    }

    return {
        createTweet: createTweet,
        createTimeline: createTimeline,
        createTimelineNew: createTimelineNew,
        initialize: loadScript,
        load: wrapElement
    };
}
})();

(function() {
'use strict';

TwitterWidgetInitialize.$inject = ["ngTweetLogger", "TwitterWidgetFactory"];
angular
    .module('ngtweet')
    .directive('twitterWidgetInitialize', TwitterWidgetInitialize);

function TwitterWidgetInitialize(ngTweetLogger, TwitterWidgetFactory) {
    return {
        restrict: 'A',
        replace: false,
        scope: false,
        link: function(scope, element, attrs) {
            ngTweetLogger.debug('Initializing');
            TwitterWidgetFactory.initialize();
        }
    };
}
})();


/*
 * Class Model
 * @module   klass
 * @author   genify(caijf@corp.netease.com)
 */
var _util = require('./util.js');
// find method in klass prototype chain
var _doFindIn = function(method,klass){
    while(!!klass){
        var key = null,
            pro = klass.prototype;
        // find method in current klass
        Object.keys(pro).some(function(name){
            if (method===pro[name]){
                key = name;
                return !0;
            }
        });
        // method finded in klass
        if (key!=null){
            return {
                name:key,
                klass:klass
            };
        }
        klass = klass.supor;
    }
};
// create Class constructor
// eg.
// var Parser = require('/path/to/klass.js').create();
// var pro = Parser.extend(require('/path/to/abstract.js'));
// pro.init = function(){
//      // call super init
//      this._super();
//      // do something
// };
exports.create = function(){
    // Class constructor
    var Klass = function(){
        this.init.apply(this,arguments);
    };
    Klass.prototype.init = function(){};
    // Class inherit api
    Klass.extend = function(supor) {
        if (!_util.isFunction(supor)) {
            return;
        }
        // do inherit
        this.supor = supor;
        var Parent = function(){};
        Parent.prototype = supor.prototype;
        this.prototype = new Parent();
        this.prototype.constructor = this;
        // for super method call
        var stack = [], phash = {};
        var _doUpdateCache = function (method, klass) {
            var ret = _doFindIn(method, klass);
            if (!ret) return;
            // save state
            if (stack[stack.length - 1] != ret.name) {
                stack.push(ret.name);
            }
            phash[ret.name] = ret.klass.supor;
            return ret.name;
        };
        // protected method for call super method
        this.prototype._super = function () {
            var name = stack[stack.length - 1],
                method = arguments.callee.caller;
            if (!name) {
                name = _doUpdateCache(method, this.constructor);
            } else {
                var parent = phash[name].prototype;
                // switch caller name
                if (!parent.hasOwnProperty(method) ||
                    method != parent[name]) {
                    name = _doUpdateCache(method, this.constructor);
                } else {
                    phash[name] = phash[name].supor;
                }
            }
            // call parent method
            var method = phash[name].prototype[name],
                result = method.apply(this, arguments);
            // exit super
            if (name===stack[stack.length-1]){
                stack.pop();
                delete phash[name];
            }
            return result;
        };
        return this.prototype;
    };
    return Klass;
};


/*
 * exports toolkit
 * @author genify(caijf@corp.netease.com)
 */
var  fs   = require('fs'),
     exec = require('child_process').exec,
    _fs   = require('./util/file.js'),
    _util = require('./util/util.js'),
    _path = require('./util/path.js'),
    _logger = require('./util/logger.js').logger;
var FormData = require('form-data');
/**
 * dump files to temporary directory
 * @param  {Object} config - config object
 * @return {Void}
 */
var dumpFiles2Temp = function(config){
    _logger.info('begin dump files ...');
    var map = {};
    ['fileInclude','fileExclude'].forEach(function(name){
        var value = config[name];
        if (!!value){
            if (typeof value==='string'){
                var reg = new RegExp(value,'i');
                config[name] = function(file){
                    return reg.test(file);
                };
            }else if(!!value.test){
                config[name] = function(file){
                    return value.test(file);
                }
            }
        }
        if (!_util.isFunction(config[name])){
            var flag = name!=='fileExclude';
            config[name] = function(file){
                return flag;
            };
        }
    });
    (config.resRoot||'').split(',').forEach(function(dir){
        if (!dir){
            return;
        }
        var ret = _fs.lsfile(dir,function(name,file){
            return !config.fileExclude(file)&&
                config.fileInclude(file);
        });
        ret.forEach(function(v){
            map[v] = v.replace(config.webRoot,config.temp);
        });
    });
    _logger.debug('package file map -> %j',map);
    Object.keys(map).forEach(function(src){
        var dst = map[src];
        _fs.copy(src,dst,function(a){
            _logger.info('copy file %s',a);
        });
    });
};
/**
 * zip files package
 * @param  {Object} config - config object
 * @return {Void}
 */
var zipPackage = function(config){
    _logger.info('begin zip package ...');
    var cmd = [
        'java','-jar',
        JSON.stringify(config.zip),
        JSON.stringify(config.temp),
        JSON.stringify(config.output)
    ].join(' ');
    _logger.debug('do command: %s',cmd);
    exec(cmd,function(error,stdout,stderr){
        if (error){
            _logger.error('zip package error for reason:\n%s',error.stack);
            process.abort();
            return;
        }
        if (stdout){
            _logger.info(stdout);
        }
        if (stderr){
            _logger.error(stderr);
        }
        uploadToServer(config);
    });
};
/**
 * upload package to web cache server
 * @param  {Object} config - config object
 * @return {Void}
 */
var uploadToServer = function(config){
    if (!_fs.exist(config.output)){
        return abortProcess(
            config,'no package to be uploaded'
        );
    }
    _logger.info('begin build upload form ...');
    var form = new FormData();
    var ex = _util.merge(
        {
            version: '0.1',
            platform: 'ios&android'
        },
        config.extension
    );
    // build form
    Object.keys(ex).forEach(function(name){
        form.append(name,ex[name]);
    });
    form.append('token',config.token);
    form.append('resID',config.appid);
    form.append('appID',config.nativeId);
    form.append('userData',JSON.stringify({domains:config.domains}));
    form.append('zip',fs.createReadStream(config.output));
    // submit form
    _logger.info('begin upload package to web cache server ...');
    form.submit(config.api,function(err,res){
        if (err){
            return abortProcess(
                config,'upload failed for reason:\n%s',err.stack
            );
        }
        // chunk response data
        var arr = [];
        res.on('data',function(chunk){
            arr.push(chunk);
        });
        // parse response result
        res.on('end',function(){
            var ret = null,
                txt = arr.join('');
            try{
                ret = JSON.parse(txt);
            }catch(ex){
                // result error
                return abortProcess(
                    config,'[%s] %s\n%s',
                    res.statusCode,txt,ex.stack
                );
            }
            if (!!ret&&ret.code==0){
                clearTemp(config);
                _logger.info('package upload success');
                config.ondone();
            }else{
                return abortProcess(
                    config,'package upload failed for reason: [%s] %s',
                    res.statusCode,txt
                );
            }
        });
        res.resume();
    });
};
/**
 * clear temporary infomation
 * @param  {Object} config - config object
 * @return {Void}
 */
var clearTemp = function(config){
    _logger.info('clear temporary directory and files');
    _fs.rmdir(config.temp);
    _fs.rm(config.output);
};
/**
 * abort process when error
 * @param  {Object} config - config object
 * @return {Void}
 */
var abortProcess = function(config){
    var args = [].slice.call(arguments,0);
    clearTemp(args.shift());
    _logger.error.apply(_logger,args);
    process.abort();
};
/**
 * run zip and upload
 * @param {Object} config - config object
 * @param {String} config.api - upload api url
 * @param {String} config.appid - web cache for app identify
 * @param {String} config.token - web cache upload api token
 * @param {String} config.domains - domains used in project
 * @param {String} config.webRoot - web root path
 * @param {String} config.resRoot - resource root path
 * @param {String} config.staticRoot - static file (css/js/html) root path
 * @param {String|RegExp|Function} config.fileInclude - file include match
 * @param {String|RegExp|Function} config.fileExclude - file exclude match
 * @return {Void}
 */
exports.run = function(config){
    var name = +new Date,
        root = config.root||(process.cwd()+'/');
    config.zip = _path.absolute(
        './3rd/zip.jar',
        __dirname+'/'
    );
    config.temp = _path.absolute(
        './'+name+'/',root
    );
    config.output = _path.absolute(
        './'+name+'.zip',root
    );
    dumpFiles2Temp(config);
    zipPackage(config);
};


/*
 * NEJ Utility API
 * @module   script/nej/util
 * @author   genify(caijf@corp.netease.com)
 */
var vm      = require('vm'),
    util    = require('util'),
    path    = require('path'),
    query   = require('querystring'),
   _io      = require('../../util/io.js'),
   _fs      = require('../../util/file.js'),
   _dep     = require('../../util/dependency.js'),
   _path    = require('../../util/path.js'),
   _util    = require('../../util/util.js');
// nej config cache
// nejRoot          nej source root
// nejPlatform      export platform config
// nejPatchRoot     nej patch root
// nejInjector      nej injector funciton name
// nejProcessor     nej plugin processor
// params           parameters definition, eg. lib/pro ...
var _gCache = {
    params:{},
    nejInjector:'I$',
    nejProcessor:require('./processor.js')
};
// NEJ define.js replacement
exports.NEJ_DEFINE_FILE = _path.absolute(
    './define.js',
    path.dirname(__filename)+'/'
);
/**
 * cache nej config
 * @param  {String}   key   - config key
 * @param  {Variable} value - config value
 * @return {Void}
 */
exports.cacheConfig = function(key,value){
    if (typeof key==='string'){
        // can not be overwrited
        if (key==='nejRoot'||key==='nejPlatform'){
            var ret = _gCache[key];
            if (!ret){
                _gCache[key] = value;
                if (key==='nejRoot'){
                    _gCache.params.lib = value;
                    _gCache.nejPatchRoot = value+'/patched/';
                }
            }
            return;
        }
        // for params normalize path before merge all values
        if (key==='params'){
            value = value||{};
            delete (value).lib;
            Object.keys(value).forEach(function(name){
                _gCache.params[name] = _path.normalize(value[name]);
            });
            return;
        }
        // for nejProcessor merge all values
        if (key==='nejProcessor'){
            _gCache[key] = _util.merge(
                _gCache[key],value
            );
            return;
        }
        // one config item
        _gCache[key] = value;
    }else{
        // config map
        Object.keys(key||{}).forEach(
            function(it){
                this.cacheConfig(it,key[it]);
            },this
        );
    }
};
/**
 * get nej config
 * @return {Object} nej config object
 */
exports.getConfig = function(key){
    if (!!key){
        return _gCache[key];
    }
    return _gCache;
};
/**
 * complete nej injector uri
 * @param  {String} uri    - nej uri string
 * @param  {Object} config - path parameter config, all root config should be absolute path
 * @param  {String} config.pathRoot - file path for uri relative to
 * @param  {String} config.webRoot  - web root path
 * @return {Object} uri information after complete,eg. {uri:'',patch:'',plugin:'',deps:[]}
 */
exports.parseURI = function(uri,config){
    // split version
    uri = (uri||'').split('?')[0]||'';
    config = _util.merge(
        this.getConfig(),config
    );
    // fix url
    var dict = config.params||{},
        dic2 = config.nejDict||{};
    var fixURI = function (url) {
        return (url||'').replace(
            /\{(.*?)\}/gi, function($1,$2){
                return dic2[$2]||dict[$2]||$1;
            }
        );
    };
    // for json!/path/to/data.json
    var arr = uri.split('!'),
        ret = {
            uri:arr[0]||''
        };
    if (arr.length>1){
        ret.plugin = arr.shift().toLowerCase();
        ret.uri = arr.join('!');
    }
    // for {platform}hack.js
    if (ret.uri.indexOf('{platform}')===0){
        ret.uri = ret.uri.replace('{platform}','./platform/');
        ret.patch = ret.uri.replace(/(\.js)$/i,'.patch$1');
    }
    // complete patch uri
    if (!!ret.patch){
        ret.patch = this.parseURI(ret.patch,config).uri;
    }
    // for begin with ./ or ../ or /
    if (/^[./]/.test(ret.uri)){
        ret.uri = _path.absoluteAltRoot(
            fixURI(ret.uri),
            config.pathRoot,
            config.webRoot
        );
        return ret;
    }
    // for {patch}hack.js
    if (ret.uri.indexOf('{patch}')===0){
        var arr = dict.patch;
        if (typeof arr==='string'){
            // for string patch
            ret.uri = _path.normalize(
                ret.uri.replace('{patch}',arr)
            );
        }else{
            // for list patch
            ret.deps = [];
            var name = ret.uri.replace('{patch}','');
            arr.forEach(function(root){
                ret.deps.push(_path.normalize(root+name));
            });
            ret.uri = _path.normalize(
                ret.uri.replace('{patch}',config.nejPatchRoot)
            );
        }
        return ret;
    }
    // for begin with {abc}a.js
    if (ret.uri.indexOf('{')===0){
        ret.uri = _path.normalize(
            fixURI(ret.uri)
        );
        return ret;
    }
    // fix all {param}
    ret.uri = fixURI(ret.uri);
    // for http://a.b.com/x.js
    if (ret.uri.indexOf('://')>0){
        return ret;
    }
    // for lib/util/base or pro/a/b/c
    var brr = ret.uri.split('/'),
        root = dict[brr[0]],
        ext = (brr[brr.length-1]||'').indexOf('.')>=0?'':'.js';
    if (!!root){
        brr.shift();
    }else{
        root = config.nejRoot||'';
    }
    ret.uri = _path.normalize(
        root+brr.join('/')+ext
    );
    // try to fix file path
    if (!ext&&
        !_path.isURL(ret.uri)&&
        !_fs.exist(ret.uri)){
        var file = _path.normalize(
            root+brr.join('/')+'.js'
        );
        if (_fs.exist(file)){
            ret.uri = file;
        }
    }
    return ret;
};
/**
 * parse nej config from define.js
 * @param  {String} uri - absolute path of define.js
 * @param  {Object} config - config object
 * @param  {String} config.pathRoot - file path for uri relative to
 * @param  {String} config.webRoot  - web root path
 * @return {Object} result of nej config, eg. {deps:{},params:{},nejPlatform:'',nejRoot:''}
 */
exports.parseConfig = (function(){
    // dump dependency config
    var _doDumpDepConfig = function(content){
        var ret,
            handler = function(map){
                ret = map;
            },
            sandbox = {NEJ:{deps:handler, config:handler}};
        // detect dep config
        try{
            //eval(content);
            vm.createContext(sandbox);
            vm.runInContext(content,sandbox);
        }catch(ex){
            // ignore
        }
        return ret||null;
    };
    return function(uri,config){
        // 0 - http://a.b.com/nej/define.js
        // 1 - a=aaaa&b=bbbb&c=ccccc
        var ret = {},
            arr = (uri||'').split(/[?#]/),
            params = query.parse(arr[1]||'')||{};
        ret.params = params;
        // for c or d or p or g
        delete params.g;
        delete params.c;
        if (!!params.d){
            // dump dep config
            var file = _path.absoluteAltRoot(
                params.d,config.pathRoot,config.webRoot
            );
            ret.deps = _doDumpDepConfig(
                _fs.read(file,config.charset).join('\n')
            );
            delete params.d;
        }
        if (!!params.p){
            ret.nejPlatform = params.p;
            delete params.p;
        }
        // absolute param path
        Object.keys(params).forEach(
            function(key){
                params[key] = _path.absoluteAltRoot(
                    params[key],config.pathRoot,config.webRoot
                );
            },this
        );
        // check lib root
        ret.nejRoot = _path.absoluteAltRoot(
            path.dirname(arr[0])+'/',
            config.pathRoot,config.webRoot
        );
        // check default pro parameter
        if (!params.pro){
            params.pro = _path.absolute(
                './src/javascript/',
                config.webRoot
            );
        }
        // compatible for nej 20140721
        return ret;
    };
})();
/**
 * parse function arguments and return
 * @param  {String} content - function string content
 * @return {Object} function information, eg. {args:[],riturn:''}
 */
exports.parseFunction = function(content){
    var ret = { args:[], riturn:'' };
    // check args list
    if (/^\s*function\s*\(([\w\W]*?)\)/.test(content)){
        var args = RegExp.$1.trim();
        if (!!args){
            ret.args = args.split(/\s*,\s*/);
        }
    }
    // check return name
    if (/(return\s*[\w;\s$]+)\s*}\s*$/.test(content)){
        var name = RegExp.$1.trim();
        if (!!name){
            ret.riturn = name;
        }
    }
    return ret;
};
/**
 * parse nej patch
 * @param  {String} content - patch source content
 * @param  {Object} config  - config object
 * @return {Object} patch information, eg. {args:[],riturn:'',patches:[]}
 */
exports.parsePatch = (function(){
    // parse nej patch
    var _doPatch = function(patform,deps,func){
        var args = exports.formatARG.apply(
            exports,arguments
        );
        if (!this.patches){
            this.patches = [];
        }
        // illegal patch
        if (!args[0]){
            return;
        }
        // cache patch config
        this.patches.push({
            expression:args[0],
            dependency:args[1],
            source:(args[2]||'').toString()
        });
    };
    // eval content to dump nej patch
    var _doDumpNEJPatch = function(content){
        // emulate NEJ patch env
        var ret = {},
            sandbox = {
                NEJ:{
                    patch:_doPatch.bind(ret)
                }
            };
        // eval content for nej patch check
        try{
            //eval(util.format('(%s)();',content));
            vm.createContext(sandbox);
            vm.runInContext(util.format('(%s)();',content),sandbox);
        }catch(ex){
            // ignore
        }
        return ret.patches;
    };
    return function(content){
        var patches = _doDumpNEJPatch.call(
            this,content
        );
        if (!!patches){
            var ret = this.parseFunction(content);
            ret.patches = patches;
            return ret;
        }
    };
})();
/**
 * parse nej file
 * @param  {String} content - file content
 * @return {Object} nej file information, eg. {}
 */
exports.parseFileContent = (function(){
    // do nej define
    var _doDefine = function(uri,deps,func){
        var args = exports.formatARG.apply(
            exports,arguments
        );
        this.isNEJ = !0;
        this.dependency = args[1];
        this.source = (args[2]||'').toString();
    };
    return function(content){
        // emulate NEJ define env
        // void over write result
        var ret = {},
            func = _doDefine.bind(ret),
            sandbox = {
                NEJ:{
                    define:func
                },
                define:func
            };
        func.nej = !0;
        // eval content for nej check
        try{
            //eval(content);
            vm.createContext(sandbox);
            vm.runInContext(content,sandbox);
        }catch(ex){
            // ignore
        }
        if (ret.isNEJ){
            return _util.fetch({
                source:'',
                dependency:null
            },ret);
        }
        return null;
    };
})();
/**
 * parse platform string
 * result properties
 * - engines        platform supported
 * - lower          platform lower version
 * @param  {String} platform - platform config
 * @return {Object} result of platform config
 */
exports.parsePlatform = (function(){
    var _pMap = {
        td:'trident',wk:'webkit',gk:'gecko',
        android:'webkit',ios:'webkit',cef:'webkit',win:'trident'
    };
    return function(platform){
        platform = (platform||'td|wk|gk').toLowerCase();
        var ret = { engines:[] };
        // check engine
        platform.split('|').forEach(function(key){
            var arr = key.split('-'),
                eng = _pMap[arr[0]];
            if (ret.engines.indexOf(eng)<0){
                ret.engines.push(eng);
            }
        });
        // td-0 -> TR>=3.0 (ie>=7)
        // td-1 -> TR>=6.0 (ie>=10)
        if (platform.indexOf('td-0')>=0){
            ret.lower = '3.0';
        }
        if (platform==='win'||
            platform.indexOf('td-1')>=0){
            ret.lower = '6.0';
        }
        return ret;
    };
})();
/**
 * parse nej patch version expression
 * result properties
 * - engine     patch supported engine expression
 * - version    patch supported version expression
 * - lower      lower version config, eg. {value:'3.0',eq:true}
 * - middle     middle version config, eg. {value:'5.0',eq:true}
 * - upper      upper version config, eg. {value:'6.0',eq:true}
 * @param  {String} expression - version expression
 * @return {Object} result of expression
 */
exports.parseExpression = (function(){
    var _vMap = {r:'release',v:'version'},
        _cMap = {t:'trident',w:'webkit',g:'gecko',p:'presto'},
        _fMap = [
            // for 2.0a<= or 2.0a= or 2.0a>=
            function(result,exp){
                switch(exp.op){
                    case '<':
                    case '<=':
                        result.lower = exp;
                    break;
                    case '>':
                    case '>=':
                        result.upper = exp;
                    break;
                    case '=':
                    case '==':
                        result.midle = exp;
                    break;
                }
                delete exp.op;
            },
            // for <=2.0a or =2.0a or >=2.0a
            function(result,exp){
                switch(exp.op){
                    case '<':
                    case '<=':
                        // check over write upper
                        var upper = result.upper;
                        if (!upper||exp.value<=upper.value){
                            result.upper = exp;
                            if (!!upper){
                                exp.eq = upper.eq&&exp.eq;
                            }
                        }
                    break;
                    case '>':
                    case '>=':
                        // check over write lower
                        var lower = result.lower;
                        if (!lower||exp.value>=lower.value){
                            result.lower = exp;
                            if (!!lower){
                                exp.eq = lower.eq&&exp.eq;
                            }
                        }
                    break;
                    case '=':
                    case '==':
                        // check over write lower
                        var midle = result.midle;
                        if (!midle){
                            result.midle = exp;
                        }else if(midle.value!==exp.value){
                            result.dirty = !0;
                            delete result.midle;
                        }
                    break;
                }
                delete exp.op;
            }
        ];
    return function(expression){
        var exp = (expression||'').replace(/\s/g,'').toLowerCase();
        if (!/(tr|wr|gr|pr|tv|wv|gv|pv)/i.test(exp)){
            console.log(expression);
            return null;
        }
        // parse expresion to platfrom
        var ret = {exp:expression},
            eng = RegExp.$1.toLowerCase(),
            arr = eng.split('');
        ret.engine = _cMap[arr[0]]||'';
        // for TR or GR or WR
        if (eng===exp){
            return ret;
        }
        // for TR=3.0 or 2.0<=TR<=4.0
        ret.version = _vMap[arr[1]]||'release';
        // check version condition
        var ver = /([<>=]+)/;
        exp.split(eng).forEach(function(value,index){
            if (!ver.test(value)){
                return;
            }
            var op = RegExp.$1,
                vl = value.replace(op,'');
            _fMap[index](ret,{
                value:vl,op:op,
                eq:op.indexOf('=')>=0
            });
        });
        return !ret.dirty?ret:null;
    };
})();
/**
 * check expression fit to platform
 * @param  {Object}  exp - expression parse result
 * @param  {Object}  platform - platform parse result
 * @return {Boolean} is expression fit to platform
 */
exports.isExpFitPlatform = function(exp,platform){
    // check string exp and platform
    if (typeof exp==='string'){
        exp = this.parseExpression(exp);
    }
    if (!platform||(typeof platform==='string')){
        platform = this.parsePlatform(platform);
    }
    // check engine
    var engines = platform.engines,
        index = engines.indexOf(exp.engine);
    // engine not match
    if (index<0){
        return !1;
    }
    // check version
    var lower = platform.lower;
    if (lower==null){
        return !0;
    }
    // check upper
    var upper = exp.upper;
    if (!!upper){
        if (upper.value<lower||
            (upper.value==lower&&!upper.eq)){
            return !1;
        }
        return !0;
    }
    // check middle
    var midle = exp.midle;
    if (!!midle&&midle.value<lower){
        return !1;
    }
    return !0;
};
/**
 * stringify expression to condition
 * @param  {String} name - name of platform argument name
 * @param  {Object} exp - expression parse result
 * @return {String} condition expression
 */
exports.exp2condition = function(name,exp){
    if (typeof exp==='string'){
        exp = this.parseExpression(exp);
    }
    var ret = util.format(
            "%s._$KERNEL.engine==='%s'",
            name,exp.engine
        ),
        ver = util.format(
            '%s._$KERNEL.%s',
            name,exp.version||'release'
        ),
        arr = [];
    // check lower
    var lower = exp.lower;
    if (!!lower){
        arr.push(
            util.format(
                "%s>%s'%s'",
                ver,lower.eq?'=':'',
                lower.value
            )
        );
    }
    // check middle
    var midle = exp.midle;
    if (!!midle){
        arr.push(ver+"=='"+midle.value+"'");
    }
    // check upper
    var upper = exp.upper;
    if (!!upper){
        arr.push(
            util.format(
                "%s<%s'%s'",
                ver,upper.eq?'=':'',
                upper.value
            )
        );
    }
    // suffix
    var other = '';
    if (arr.length>0){
        other = '&&'+arr.join('&&');
    }
    return ret+other;
};
/**
 * dependency list to injector arguments list
 * @param  {Array} list - dependency list
 * @return {Array} injector arguments list
 */
exports.deps2injector = function(list){
    var ret = [];
    (list||[]).forEach(function(item){
        var key = _path.uri2key(
            this.formatURI(item)
        );
        ret.push(key);
    },this);
    return ret;
};
/**
 * format arguments, order by string,array,function
 * @param  {String}   uri - uri path
 * @param  {Array}    dep - dependency list
 * @param  {Function} func - callback
 * @return {Array}    result after formatted
 */
exports.formatARG = (function(){
    var _fList = [
        function(arg){return typeof(arg)=='string';},
        util.isArray,
        _util.isFunction
    ];
    return function(){
        var args = [null,null,null];
        for(var i=0,l=arguments.length,it;i<l;i++){
            it = arguments[i];
            _fList.some(function(func,index,list){
                if (func(it)){
                    args[index] = it;
                    return !0;
                }
            });
        }
        return args;
    };
})();
/**
 * uri result to string
 * @param  {String|Object} uri - uri information
 * @return {String} uri flag
 */
exports.formatURI = function(uri){
    if (typeof uri==='string'){
        return uri;
    }
    if (uri.patch){
        return uri.patch;
    }
    var plugin = uri.plugin||'';
    return plugin+(!plugin?'':':')+uri.uri;
};
/**
 * cache nej dependency list config
 * @param  {Object} map - dependency config
 * @param  {Object} config - config object
 * @return {Array}  dependency list
 */
exports.cacheDependencies = function(map,config){
    if (!map){
        return;
    }
    var ret = [];
    // complete uri
    Object.keys(map).forEach(
        function(file){
            var arr = map[file],
                file = this.parseURI(file,config).uri;
            arr.forEach(function(uri,index,list){
                list[index] = this.parseURI(uri,config).uri;
            },this);
            ret.push(file,arr);
            _dep.set(file,arr);
        },this
    );
    return _util.concat.apply(_util,ret);
};
/**
 * format nej dependency list
 * @param  {Object} config - config object
 * @return {Object} dependency list map
 */
exports.formatDependencies = (function(){
    // format patch
    var _doFormatPatch = function(name,config){
        var patch = config.patch;
        if (!patch){
            return;
        }
        // cache patch content if not x.patch.js
        var content = _io.getFromCache(patch);
        if (!content){
            var ret = [
                _path.uri2key(patch),
                'function(x){return x;}',
                _path.uri2key(config.uri)
            ];
            _io.cache(config.patch,util.format(
                '%s(%s);',name,ret.join(',')
            ));
        }
        // cache dependency list for x.patch.js
        if (!_dep.get(patch)){
            _dep.set(patch,[config.uri]);
        }
    };
    // format plugin
    var _doFormatPlugin = function(name,config,plugins){
        var plugin = config.plugin;
        if (!plugin){
            return;
        }
        // check plugin content
        var key = plugin+':'+config.uri;
        if (_io.getFromCache(key)!=null){
            return;
        }
        // update plugin content
        var content = _io.getFromCache(config.uri)||'';
        // check content
        if (typeof content!=='string'&&
            _util.isFunction(content.stringify)){
            content = content.stringify();
        }
        // check plugin precessor
        var func = plugins[plugin];
        if (!!func){
            if (typeof func=='string'){
                func = plugins[func];
            }
            if (_util.isFunction(func)){
                var ret = func({
                    file:config.uri,
                    content:content
                });
                if (ret!=null){
                    content = ret;
                }
            }
        }
        // cache content after process
        _io.cache(key,util.format(
            '%s(%s,%s);',name,
            _path.uri2key(key),content
        ));
    };
    return function(config){
        config = config||{};
        var map = _dep.dump();
        // check injectable
        //if (!_dep.injectable()){
        //    var root = this.getConfig('nejRoot')||config.nejRoot,
        //        global = _path.absolute('./base/global.js',root);
        //    _dep.set(global,[]);
        //}
        // format dependency patch and plugin
        var name = this.getConfig('nejInjector')||config.nejInjector,
            plugins = _util.merge(
                this.getConfig('nejProcessor'),
                config.nejProcessor
            );
        Object.keys(map).forEach(function(uri){
            var list = map[uri];
            list.forEach(function(uri,index,deps){
                if (typeof uri==='string'){
                    return;
                }
                // format nej uri object, eg. {uri:'',patch:'',plugin:''}
                _doFormatPatch.call(this,name,uri);
                // process plugin
                _doFormatPlugin.call(this,name,uri,plugins);
                // update dependency list
                deps[index] = this.formatURI(uri);
            },this);
        },this);
        return map;
    };
})();
/**
 * support before nej 20140721 version compatible
 * @return {Void}
 */
exports.supportNEJCompatible = (function(){
    var os = /(cef|ios|win|android)/,
        bw = {
            gk:'gecko',wk:'webkit',pt:'presto',
            td:['trident-1','trident','trident-0'], // for ie6+
           'td-0':['trident-1','trident'],          // for ie7+
           'td-1':'trident-1'                        // for ie10+
        };
    return function(){
        var params = this.getConfig('params'),
            libRoot = this.getConfig('nejRoot'),
            patRoot = this.getConfig('nejPatchRoot'),
            platform = this.getConfig('nejPlatform')||'td|wk|gk';
        // for mobile os
        if (os.test(platform)){
            var name = RegExp.$1;
            params.native = libRoot+'native/'+name+'/';
            params.patch = patRoot+(name==='win'?'trident-1':'webkit')+'/';
            return;
        }
        // for browser
        var ret = [];
        platform.split('|').forEach(function(name){
            ret.push(bw[name]);
        });
        ret = _util.concat.apply(_util,ret);
        ret.forEach(function(name,index,list){
            list[index] = patRoot+name+'/';
        });
        params.patch = ret;
    };
})();
/**
 * check whether nej define.js
 * @param  {String} uri - script uri
 * @return {Boolean} whether nej define.js
 */
exports.isNEJDefine = (function(){
    var reg = /\bdefine\.js\b/i;
    return function(uri){
        return reg.test(uri||'');
    };
})();
/**
 * check whether nej script code
 * @param  {String} code - script code
 * @return {Boolean} whether nej script code
 */
exports.isNEJScript = (function(){
    var reg = /(NEJ\.)?define\s*\(/;
    return function(code){
        return reg.test(code||'');
    };
})();


/*
 * Html Explorer, used to manage html list
 * @module   explorer/html
 * @author   genify(caijf@corp.netease.com)
 */
var path = require('path'),
    util = require('util'),
   _io   = require('../util/io.js'),
   _fs   = require('../util/file.js'),
   _dep  = require('../util/dependency.js'),
   _path = require('../util/path.js'),
   _util = require('../util/util.js');
// html explorer
var Html = module.exports =
    require('../util/klass.js').create();
var pro = Html.extend(require('./explorer.js'));
/**
 * parse resource item
 * @protected
 * @param  {Object} res - resource config
 * @return {Void}
 */
pro._parseResource = function(res){
    return new (require('../meta/html.js'))(res);
};
/**
 * dump nej module version map
 * @private
 * @param  {String} root - module root
 * @param  {Array}  list - output file list
 * @return {Object} version map
 */
pro._dumpModuleVersion = (function(){
    var verMap = {};
    return function(root,list,exclude){
        var map = {};
        list.forEach(function(file){
            if (file.indexOf(root)>=0&&
               (!exclude||file.indexOf(exclude)<0)){
                var ver = verMap[file];
                if (!ver){
                    ver = _util.md5(_io.getFromCache(file));
                    verMap[file] = ver;
                }
                map[file.replace(root,'')] = ver;
            }
        });
        return map;
    };
})();
/**
 * check resource in html file
 * @private
 * @param  {Object} options - resource check options
 * @param  {Boolean}options.lnkCheck    - check resource in script
 * @param  {String} options.webRoot     - webapp root
 * @param  {String} options.resRoot     - resource root
 * @param  {Boolean}options.versionStac - add version to resource
 * @param  {Array}  options.rsDomain    - resource domain
 * @return {Void}
 */
pro._checkResInHtmlFile = function(options){
    // check resource domain
    var domains = options.rsDomain;
    if (!options.lnkCheck||
        !domains||!domains.length){
        return;
    }
    // check all html file
    var handler = function(event){
        if (event.type=='script'){
            event.value = this._checkResInScript(
                event.file,
                event.content,
                options
            );
        }
    };
    // san all files
    this._list.forEach(
        function(parser){
            parser.scan(handler,this);
        },this
    );
};
/**
 * check resource in script
 * @param  {String} file    - script file path
 * @param  {String} content - script content
 * @param  {Object} options - resource check options
 * @param  {Boolean}options.lnkCheck    - check resource in script
 * @param  {Object} options.lnkOption   - resource check config options
 * @param  {String} options.webRoot     - webapp root
 * @param  {String} options.resRoot     - resource root
 * @param  {Boolean}options.versionStac - add version to resource
 * @param  {String} options.versionStacMode - static version mode
 * @param  {Array}  options.rsDomain    - resource domain * @private
 * @return {String} script content after resource merge
 */
pro._checkResInScript = (function(){
    var reg = /(['"])(\/.+?)(?=\1|\\)/g;
    return function(file,content,options){
        // check resource domain
        var domains = options.rsDomain;
        if (!options.lnkCheck||
            !domains||!domains.length){
            return content;
        }
        // check content
        var _this = this,
            lopt = options.lnkOption||{};
        reg.lastIndex = -1;
        return (content||'').replace(
            reg,function($1,$2,$3){
                // check resource uri
                var arr = $3.split('?');
                arr[0] = arr[0].replace('/',options.webRoot);
                var isdir = _fs.isdir(arr[0]);
                // for static resource
                if ((lopt.checkDIR||!isdir)&&
                    arr[0].indexOf(options.resRoot)===0){
                    // check resource exist
                    if (_fs.exist(arr[0])){
                        if (!isdir&&options.versionStac){
                            if (!!arr[1]){
                                _this.emit('warn',{
                                    data:[$3],
                                    message:'static resource %s with error version'
                                });
                            }else{
                                // check resource version with mode
                                arr = _io.adjustResource(
                                    arr[0],options,function(src,dst){
                                        _this.emit('debug',{
                                            data:[src,dst],
                                            message:'copy static resource %s to %s'
                                        });
                                    }
                                );
                            }
                        }
                        var domain = _util.randNext(domains);
                        arr[0] = arr[0].replace(options.webRoot,domain);
                        _this.emit('debug',{
                            data:[$3,file,arr[0]],
                            message:'find resource %s in script %s and adjust to %s'
                        });
                        return util.format('%s%s',$2,arr.join('?'));
                    }
                    // warning error resource
                    _this.emit('warn',{
                        data:[arr[0],file],
                        message:'not find resource %s in script %s'
                    });
                }
                return $1;
            }
        );
    };
})();
/**
 *  dump core resource list
 *  @param  {Object}  config - config object
 *  @param  {Number}  config.resFreq - core resource count
 *  @param  {String}  config.resType - core resource type, style or script
 *  @param  {Boolean} config.ignoreEntry - ignore entry for script
 *  @return {Array}   core list
 */
pro.getCoreList = function(config){
    var list  = [],
        test  = {},
        count = config.resFreq;
    this._list.forEach(function(parser){
        var ret = parser.getDependencies({
            checkCoreConfig:!0,
            resType:config.resType,
            ignoreEntry:config.ignoreEntry
        });
        this.emit('debug',{
            data:[config.resType,parser.getURI(),ret],
            message:'%s used in file %s is %j'
        });
        ret.forEach(function(uri){
            if (!test[uri]){
                test[uri] = 0;
            }
            test[uri]++;
            if (test[uri]===count){
                list.push(uri);
            }
        });
    },this);
    return list;
};
/**
 * resort html sort
 * @return {Void}
 */
pro.sort = function(){
    var ret = [],
        map = {};
    // calculate dependency list
    this._list.forEach(function(parser){
        var uri = parser.getURI(),
            list = parser.getTemplateDependencies();
        _dep.set(uri,list);
        map[uri] = parser;
        ret.push(uri);
    });
    // resort html sort
    ret = _dep.complete(ret);
    this.emit('debug',{
        data:[ret],
        message:'page list order by module dependency is %j'
    });
    // merge parser
    for(var i=ret.length- 1,uri,parser;i>=0;i--){
        uri = ret[i];
        parser = map[uri];
        if (!!parser){
            ret[i] = parser;
        }else{
            ret.splice(i,1);
            this.emit('warn',{
                data:[uri],
                message:'module %s not exist'
            });
        }
    }
    // overwrite parser list
    this._list = ret;
};
/**
 * merge resource
 * @param  {Object} config - config object
 * @param  {Array}  config.coreStyle  - core style list
 * @param  {Array}  config.coreScript - core script list
 * @return {Void}
 */
pro.merge = function(config){
    // cache core list
    _io.cache(
        'core.css',
        _io.fill(config.coreStyle)
    );
    _io.cache(
        'core.js',
        _io.fill(config.coreScript)
    );
    // merge resource
    var map = {
        'core.js':['core.js']
    };
    this._list.forEach(function(parser){
        parser.begMerge(config);
        parser.mergeStyle({
            core:config.coreStyle
        });
        var ret = parser.mergeScript({
            core:config.coreScript
        });
        ret.forEach(function(name){
            map[name] = [name];
        });
    },this);
    // console.log('%j',Object.keys(map));
    this._scripts = map;
};
/**
 * minify script
 * @param  {Object} config - config object
 * @param  {String} cnofig.bags        - name bags file path
 * @param  {String} cnofig.level       - obfuscate level
 * @param  {String} cnofig.sourcemap   - output source map
 * @param  {Object} cnofig.variables   - global variables
 * @param  {String} cnofig.compatible  - use nej compatible mode
 * @param  {String} cnofig.dropconsole - drop console code
 * @param  {String} config.outputRoot  - output root
 * @param  {String} config.codeWrap    - script code wrapper
 * @param  {Object} options - resource check options
 * @param  {String} options.webRoot     - webapp root
 * @param  {String} options.resRoot     - resource root
 * @param  {Boolean}options.versionStac - add version to resource
 * @param  {String} options.versionStacMode - static version mode
 * @param  {Array}  options.rsDomain    - resource domain
 * @return {Void}
 */
pro.minify = function(config,options){
    // check resource in html file
    this._checkResInHtmlFile(options);
    // format config
    config = config||{};
    // parse name bags
    var bagfile = config.bags;
    config.bags = _util.file2json(bagfile);
    // fill content
    var wrap = config.codeWrap||'%s',
        smap = this._scripts,
        rmap = {};
    Object.keys(smap).forEach(function(file){
        var content = util.format(wrap,_io.fill(smap[file]));
        rmap[file] = this._checkResInScript(
            file,content,options
        );
    },this);
    // obfuscate script
    var parser = new (require('../adapter/script.js'))(
        _util.merge(
            this.getLogger(),{
                map:rmap
            }
        )
    );
    // check source map config
    if (config.sourcemap){
        config.sourcemap = {
            root:config.outputRoot+'s/',
            uri:_path.wrapURI('sm','%s')
        };
    }else{
        delete config.sourcemap;
    }
    // do compress and obfuscate script
    parser.parse(config);
    var ret = parser.dump();
    // cache script content
    Object.keys(ret.code).forEach(function(name){
        _io.cache(name,ret.code[name]||'');
    });
    // output name bags
    this.emit('debug',{
        data:[bagfile],
        message:'output script name bags to %s'
    });
    _io.output(bagfile,JSON.stringify(ret.bags||{},null,4));
    // output source map
    if (!!ret.sourcemap){
        var root = config.sourcemap.root;
        Object.keys(ret.sourcemap).forEach(function(name){
            var content = ret.sourcemap[name]||'';
            if (!!content){
                this.emit('debug',{
                    data:[root,name],
                    message:'output sourcemap %s%s'
                });
                _io.output(root+name,content);
            }
        },this);
    }
};
/**
 * embed resource
 * @param  {Object} config - config object
 * @param  {String} config.resWrap      - inline resource wrapper
 * @param  {String} config.inCSWrap     - inline style template wrap
 * @param  {String} config.exCSWrap     - external style template wrap
 * @param  {String} config.inJSWrap     - inline script template wrap
 * @param  {String} config.exJSWrap     - external script template wrap
 * @param  {String} config.inTPWrap     - inline template wrap
 * @param  {String} config.inStyleWrap  - inline style wrap
 * @param  {String} config.exStyleWrap  - external style wrap
 * @param  {String} config.inScriptWrap - inline script wrap
 * @param  {String} config.exScriptWrap - external script wrap
 * @param  {Number} config.maxCSSize    - max style inline size
 * @param  {Number} config.maxJSSize    - max script inline size
 * @return {Void}
 */
pro.embed = function(config){
    // embed resource
    this._list.forEach(function(parser){
        parser.embedStyle({
            maxSize:config.maxCSSize,
            inlineWrap:config.inCSWrap,
            externalWrap:config.exCSWrap,
            inlineStyle:config.inStyleWrap,
            externalStyle:config.exStyleWrap
        });
        parser.embedScript({
            resWrap:config.resWrap,
            maxSize:config.maxJSSize,
            inlineWrap:config.inJSWrap,
            externalWrap:config.exJSWrap,
            inlineScript:config.inScriptWrap,
            externalScript:config.exScriptWrap
        });
        // parser.embedResource({
        //     resWrap:config.resWrap,
        //     maxSize:config.maxJSSize,
        //     inlineScript:config.inScriptWrap,
        //     externalScript:config.exScriptWrap
        // });
        parser.embedTemplate({
            resWrap:config.resWrap,
            inlineWrap:config.inTPWrap
        });
    });
};
/**
 * output manifest
 * @private
 * @return {Void}
 */
pro._outputManifest = function(list,config){
    // static version map
    var res = _io.dumpResource();
    if (res.manifested!==!0){
        return;
    }
    // html version map
    delete res.manifested;
    var exf = config.outHtmlRoot!==config.outTmplRoot
            ? config.outTmplRoot : '';
    var mdls = this._dumpModuleVersion(
        config.outHtmlRoot,list,exf
    );
    // check list
    var arr = [],
        ver = [],
        map = _util.merge(res,mdls),
        ret = Object.keys(map).sort(),
        filter = config.manFilter;
    ret.forEach(function(uri,index,list){
        var ruri = _path.normalize('/'+uri);
        if (!filter||!filter.test(ruri)){
            arr.push(ruri);
            ver.push(map[uri]);
        }
    });
    this.emit('debug',{
        data:[ver],
        message:'version for manifest %j'
    });
    this.emit('debug',{
        data:[arr],
        message:'resource list for manifest %j'
    });
    // merge manifest template
    var dict = {
        VERSION:_util.md5(ver.join(',')),
        CACHE_LIST:arr.join('\n')
    };
    var content = config.manTemplate.replace(
        /#<(.+?)>/g,function($1,$2){
            return dict[$2]||$1;
        }
    );
    // output manifest
    var file = config.manOutput;
    this.emit('debug',{
        data:[file],
        message:'output manifest %s'
    });
    _fs.mkdir(path.dirname(file));
    _io.output(file,content,config.charset);
};
/**
 * output content
 * @param  {Object} config - config object
 * @return {Void}
 */
pro.output = function(config){
    // dump file content
    var ret = [],
        tpl = [];
    this._list.forEach(function(parser){
        ret.push(parser.output(config));
        tpl.push(parser.isTemplate);
    });
    // update nej module version
    var mod = config.mdlVersion,
        exf = config.outHtmlRoot!==config.outTmplRoot
            ? config.outTmplRoot : '';
    // console.log('%j',ret);
    ret.forEach(function(file,index){
        var content = _path.unwrapVersion(
            _io.getFromCache(file),function(mpath){
                var map = this._dumpModuleVersion(
                    mpath.replace(
                        config.srcRoot,
                        config.outHtmlRoot
                    ),ret,exf
                );
                return JSON.stringify(map);
            }.bind(this)
        );
        // output file
        this.emit('debug',{
            data:[file],
            message:'output file %s'
        });
        _io.cache(file,content);
        _fs.mkdir(path.dirname(file));
        _io.output(file,content,config.charset);
        // output nej module file with path version
        if (mod===1&&!tpl[index]){
            file = file.replace(
                /(\.[^.\/]*?)$/,
                '_'+_util.md5(content)+'$1'
            );
            this.emit('debug',{
                data:[file],
                message:'output file %s'
            });
            _io.output(file,content,config.charset);
        }
    },this);
    // output manifest
    this._outputManifest(ret,config);
};


/*
 * Html Resource Class
 * @module   meta/html
 * @author   genify(caijf@corp.netease.com)
 */
var path   = require('path'),
    util   = require('util'),
    uglify = require('uglify-js'),
   _io     = require('../util/io.js'),
   _fs     = require('../util/file.js'),
   _util   = require('../util/util.js'),
   _path   = require('../util/path.js');
// resource url handler
var URLHANDLER = {
    // static resource
    rs:function(uri,config){
        // c:/a/b/c.png?a=1
        var arr = uri.split('?');
        var isdir = _fs.isdir(arr[0]);
        // check version only for file
        if (config.versionStac&&!isdir){
            if (!!arr[1]){
                this.emit('warn',{
                    data:[uri],
                    message:'static resource %s with error version'
                });
            }else{
                arr = _io.adjustResource(
                    arr[0],config,function(src,dst){
                        this.emit('debug',{
                            data:[src,dst],
                            message:'copy static resource %s to %s'
                        });
                    }.bind(this)
                );
            }
        }
        // update path
        arr[0] = this._formatURI(arr[0],{
            fromPage:config.fromPage,
            pathRoot:config.output,
            webRoot:config.webRoot,
            domain:config.rsDomain
        });
        // fix last / for dir
        if (isdir&&!/\/$/.test(arr[0])){
            arr[0] += '/';
        }
        return arr.join('?');
    },
    // sprite image file
    sp:(function(){
        var fMap = {
            image:function(uri,info){
                return util.format(
                    'url(%s)',uri
                );
            },
            position:function(uri,info,ratio){
                var arr = ratio.split(',');
                return util.format(
                    '-%spx -%spx',
                    Math.round(info.x*parseFloat(arr[0])),
                    Math.round(info.y*parseFloat(arr[1]))
                );
            },
            defoult:function(uri,info,ratio){
                var arr = ratio.split(',');
                return util.format(
                    'url(%s) -%spx -%spx',uri,
                    Math.round(info.x*parseFloat(arr[0])),
                    Math.round(info.y*parseFloat(arr[1]))
                );
            }
        };
        return function(uri,config){
            var arr = uri.split(/[?@]/),
                parser = _io.getFromCache('nej://sprite-background'),
                info = parser.getImageInfo(arr[0]);
            uri = this._formatURI(info.sprite,{
                fromPage:config.fromPage,
                pathRoot:config.output,
                webRoot:config.webRoot,
                domain:config.rsDomain
            });
            return (fMap[arr[1]]||fMap.defoult)(uri,info,arr[2]);
        }
    })(),
    // style resource
    cs:function(uri,config){
        config = _util.merge(config,{
            domain:config.csDomain
        });
        return this._formatRSURI(
            uri,'.css',config
        );
    },
    // script resource
    js:function(uri,config){
        config = _util.merge(config,{
            domain:config.jsDomain
        });
        return this._formatRSURI(
            uri,'.js',config
        );
    },
    // source map file path
    sm:function(uri,config){
        return this._formatURI(uri,{
            fromPage:config.fromPage,
            pathRoot:config.output,
            webRoot:config.webRoot
        });
    },
    // manifest file
    mf:function(uri,config){
        _io.resource('manifested',!0);
        return this._formatURI(
            config.manOutput,{
                pathRoot:config.output,
                webRoot:config.webRoot,
                domain:config.manRoot
            }
        );
    },
    // nej module root
    mdl:function(uri,config){
        uri = uri.replace(
            config.srcRoot,
            config.outHtmlRoot
        );
        return this._formatURI(uri,{
            pathRoot:config.output,
            webRoot:config.webRoot,
            domain:config.mdlRoot
        });
    },
    // nej module umi path
    umi:function(uri,config){
        return 'umi://'+uri.replace(config.mdlSource,'');
    },
    // html path adjust
    path:function(uri,config){
        return uri.replace(
            config.srcRoot,
            config.outHtmlRoot
        ).replace(
            config.webRoot,'/'
        );
    }
};
// html resource parser
// root             page input root
// isTemplate       is server template file
var Html = module.exports =
    require('../util/klass.js').create();
var pro = Html.extend(require('./resource.js'));
/**
 * class initialization
 * @param  {Object} config - config parameters
 * @return {Void}
 */
pro.init = function(config){
    this._super(config);
    // init config
    config = config||{};
    this._sfx = '.html';
    this._root = config.root;
    this.isTemplate = !!config.isTemplate;
};
/**
 * parse file content with config
 * @protected
 * @param  {String} file    - file path
 * @param  {String} content - file content
 * @param  {Object} config  - config object
 * @return {String} file content after parse
 */
pro._parseContent = function(file,content,config){
    this._super(file,content,config);
    // force adjust path in source input
    config = _util.merge(config,{
        exLinkAttributeFlag:config.
            exLinkAttributeFlag||this.isTemplate
    });
    // parse html
    var parser = new (require('../parser/html.js'))(
        _util.merge(config,{
            file:file,
            content:content
        })
    );
    parser.parse(config);
    // parse merge style content
    parser.mergeConfig.forEach(function(it){
        if (!!it.styles){
            it.styles.parse(config);
        }
    });
    return parser;
};
/**
 * dump resource dependency list
 * @param  {Object}  config - config object
 * @param  {String}  config.resType     - resouce type, style or script
 * @param  {Number}  config.listType    - list type for dependency, 0 - all, 1 - only resource list, 2 - resource and template list
 * @param  {Boolean} config.ignoreEntry - whether ignore entry resource
 * @param  {Boolean} config.checkCoreConfig - whether check core config
 * @return {Array}   dependency list
 */
pro.getDependencies = function(config){
    config = config||{};
    // check explorer
    var parser = _io.getFromCache(this._uri),
        resConfig = parser.scriptConfig,
        resExplorer = parser.scripts;
    if (config.resType==='style'){
        resConfig = parser.styleConfig;
        resExplorer = parser.styles;
    }
    if (!resConfig){
        console.error(this._uri);
    }
    // check no core merge
    if (config.checkCoreConfig&&resConfig.core===!1){
        return [];
    }
    var ret = [],
        type = parseInt(config.listType)||0;
    // dump dependency list from style list
    if (!!resExplorer&&(type===0||type===1)){
        ret.push(resExplorer.getDependencies(config));
    }
    // dump dependency list from tempalte list
    parser.nejTemplates.forEach(function(conf){
        var explorer = conf.explorer;
        if (!!explorer&&(type===0||type===2)){
            var list = explorer.getDependencies(config);
            if (type===2&&!!resExplorer){
                var res = resExplorer.getDependencies(config);
                list = _util.split(list,res);
            }
            ret.push(list);
        }
    });
    return _util.concat.apply(_util,ret);
};
/**
 * dump template dependency list
 * @param  {Object} config - config object
 * @return {Void}
 */
pro.getTemplateDependencies = function(config){
    var ret = [],
        parser = _io.getFromCache(this._uri),
        func = function(explorer){
            if (!!explorer){
                ret.push(explorer.getDependencies({
                    resType:'template'
                }));
            }
        };
    // dump dependencies
    func(parser.modules);
    parser.nejTemplates.forEach(function(conf){
        func(conf.explorer);
    });
    return _util.concat.apply(_util,ret);
};
/**
 * parse output style/script name
 * @private
 * @return {Void}
 */
pro._getOutputName = function(){
    // a/b/c.html
    var name = this._uri.replace(this._root,'');
    // a/b/c
    name = name.replace(/\.[^.\/\\]+?$/i,'');
    // a_b_c
    name = name.replace(/\//gi,'_');
    // t_a_b_c
    return util.format(
        '%s_%s',this.isTemplate?'t':'p',name
    );
};
/**
 * get inline source wrapper
 * @private
 * @param  {String} wrap - wrap expression
 * @return {String} wrap expression
 */
pro._getInlineWrapper = function(wrap){
    if (!this.isTemplate){
        return '%s';
    }
    return wrap||'%s';
};
/**
 * get wrapper regexp
 * @private
 * @param  {String} wrap - wrap expression
 * @return {RegExp} wrap expression
 */
pro._getWrapperRegExp = (function(){
    var reg = /<\/(.*?)>/;
    return function(wrap){
        if (reg.test(wrap||'')){
            return new RegExp('<\/('+RegExp.$1+')>','gi');
        }
    };
})();
/**
 * init merge
 * @param  {Object} config - config object
 * @return {Void}
 */
pro.begMerge = function(config){
    // save auto output filename
    var name = this._getOutputName(),
        parser = _io.getFromCache(this._uri);
    [
        parser.styleConfig,
        parser.scriptConfig
    ].forEach(function(conf){
        if (!!conf){
            conf.filename = name;
        }
    });
};
/**
 * merge resource
 * @private
 * @param  {Object} config - config object
 * @return {Array}  file name after merge
 */
pro._mergeResource = function(config){
    config = config||{};
    var parser = _io.getFromCache(this._uri),
        resConfig = parser[config.resConfig],
        res = this.getDependencies({
            resType:config.resType,
            listType:1
        }),
        tpl = this.getDependencies({
            resType:config.resType,
            listType:2
        });
    // split core if need
    if (resConfig.core!==!1){
        [res,tpl].forEach(function(list){
            var ret = _util.split(list,config.core);
            if (ret.length>0){
                resConfig.core = !0;
            }
        });
    }
    // cache css content
    var root = resConfig.filename+config.sufix,
        ret = [];
    if (res.length>0){
        var name = 'p'+root;
        ret.push(name);
        _io.cache(name,_io.fill(res));
        this.emit('debug',{
            data:[name,res],
            message:'%s -> %j'
        });
    }
    if (tpl.length>0){
        var name = 't'+root;
        ret.push(name);
        _io.cache(name,_io.fill(tpl));
        this.emit('debug',{
            data:[name,tpl],
            message:'%s -> %j'
        });
    }
    return ret;
};
/**
 * merge style
 * @param  {Object} config - config object
 * @param  {Array}  config.core - core css list
 * @return {String} css file path
 */
pro.mergeStyle = function(config){
    return this._mergeResource({
        sufix:'.css',
        core:config.core,
        resType:'style',
        resConfig:'styleConfig'
    });
};
/**
 * merge script
 * @param  {Object} config - config object
 * @param  {Array}  config.core - core css list
 * @return {String}
 */
pro.mergeScript = function(config){
    return this._mergeResource({
        sufix:'.js',
        core:config.core,
        resType:'script',
        resConfig:'scriptConfig'
    });
};
/**
 * parse resource before embed
 * @private
 * @param  {Array}  list - resource file list
 * @param  {Object} config - config object
 * @return {Array}  resource embedded list
 */
pro._parseEmbedResource = function(list,config){
    var ret = [],
        max = config.maxSize*1000;
    list.forEach(
        function(name){
            var content = _io.getFromCache(
                name+config.resSuffix
            );
            if (!content){
                return;
            }
            // check core inline
            var inlined = content.length<=max;
            if (name==='core'){
                inlined = !!config.coreInline;
            }
            // inline resource
            if (inlined){
                if (config.resRegexp){
                    // fix content entity
                    content = _util.fixContent(content).replace(
                        config.resRegexp,'</$1>'
                    );
                }
                ret.push(util.format(
                    config.resInline,content
                ));
                return;
            }
            // generate link code
            ret.push(util.format(
                config.resExternal,
                _path.wrapURI(config.resType,name)
            ));
        },this
    );
    return ret;
};
/**
 * embed resource to html file
 * @private
 * @param  {Object} config - config object
 * @return {Void}
 */
pro._embedResource = function(config){
    var parser = _io.getFromCache(this._uri),
        pointer = parser[config.resPoint];
    // check style insert point
    if (pointer<0){
        return;
    }
    // check style list
    var ret = [],
        resConfig = parser[
            config.resConfig
        ];
    // console.log('%j，%j',resConfig, config);
    if (resConfig.core){
        ret.push('core');
    }
    ret.push('p'+resConfig.filename);
    // embed style/script resource
    config = _util.merge(config,{
        coreInline:!!resConfig.inline
    });
    parser.buffer[pointer] = util.format(
        this._getInlineWrapper(config.resWrap),
        this._parseEmbedResource(ret,config).join('\n')
    );
    parser[config.resPoint] = -1;
};
/**
 * embed resource in template
 * @private
 * @param  {Object} config - config object
 * @return {Void}
 */
pro._embedTemplate = function(config){
    config.regRegexp = _util.wrap2reg(
        config.resInline
    );
    var parser = _io.getFromCache(this._uri),
        ret = this._parseEmbedResource(
            ['t'+parser[config.resConfig].filename],config
        );
    if (ret.length>0){
        parser.templateConfig[config.resName] = ret.join('\n');
    }
};
/**
 * format merge output name
 * @private
 * @param  {Object} it - merge config
 * @param  {Number} index - merge item index
 * @return {Void}
 */
pro._embedMergeOutName = function(it,index){
    var name = it.config.name;
    if (!name){
        name = (this._getOutputName()+'_m'+index);
        it.config.name = name;
    }
    return name;
};
/**
 * embed merge styles
 * @private
 * @param  {Object} config - config object
 * @return {Void}
 */
pro._embedMergeStyle = function(config){
    var parser = _io.getFromCache(this._uri);
    parser.mergeConfig.forEach(function(it,index){
        if (!it.styles){
            return;
        }
        var name = this._embedMergeOutName(it,index),
            list = it.styles.getDependencies({
                resType:'style'
            });
        _io.cache(
            name+config.resSuffix,
            _io.fill(list)
        );
        parser.buffer[it.pointer] += util.format(
            this._getInlineWrapper(),
            this._parseEmbedResource([name],config).join('\n')
        );
        this.emit('debug',{
            data:[list],
            message:'merge style -> %j'
        });
    },this);
};
/**
 * embed merge script
 * @private
 * @param  {Object} config - config object
 * @return {Void}
 */
pro._embedMergeScript = function(config){
    var parser = _io.getFromCache(this._uri);
    parser.mergeConfig.forEach(function(it,index){
        if (!it.scripts||!it.scripts.length){
            return;
        }
        var name = this._embedMergeOutName(it,index),
            content = _io.fill(it.scripts);
        if (it.config.minify){
            var ret = uglify.minify(content,{
                fromString:!0
            });
            content = ret.code;
        }
        _io.cache(name+config.resSuffix,content);
        parser.buffer[it.pointer] += util.format(
            this._getInlineWrapper(config.resWrap),
            this._parseEmbedResource([name],config).join('\n')
        );
        this.emit('debug',{
            data:[it.scripts],
            message:'merge script -> %j'
        });
    },this);
};
/**
 * embed style to html file
 * @param  {Object} config - config object
 * @param  {Number} config.maxSize       - max inline size
 * @param  {String} config.inlineWrap    - inline template wrapper
 * @param  {String} config.externalWrap  - external template wrapper
 * @param  {String} config.inlineStyle   - inline style wrapper
 * @param  {String} config.externalStyle - external style wrapper
 * @return {Void}
 */
pro.embedStyle = function(config){
    // embed merge style resource
    var opt = {
        maxSize:config.maxSize,
        resType:'cs',
        resSuffix:'.css',
        resInline:config.inlineStyle,
        resExternal:config.externalStyle
    };
    this._embedMergeStyle(opt);
    // embed common style resource
    opt = _util.merge(opt,{
        resPoint:'stylePoint',
        resConfig:'styleConfig'
    });
    this._embedResource(opt);
    // embed style template
    this._embedTemplate({
        maxSize:config.maxSize,
        resType:'cs',
        resName:'styles',
        resSuffix:'.css',
        resConfig:'styleConfig',
        resInline:config.inlineWrap,
        resExternal:config.externalWrap
    });
};
/**
 * embed style to html file
 * @param  {Object}  config - config object
 * @param  {Number} config.maxSize        - max inline size
 * @param  {String} config.resWrap        - source wrapper
 * @param  {String} config.inlineWrap     - inline template wrapper
 * @param  {String} config.externalWrap   - external template wrapper
 * @param  {String} config.inlineScript   - inline script wrapper
 * @param  {String} config.externalScript - external script wrapper
 * @return {Void}
 */
pro.embedScript = function(config){
    // embed merge script resource
    var opt = {
        resType:'js',
        resSuffix:'.js',
        resRegexp:/<\/(script)>/gi,
        resInline:config.inlineScript,
        resExternal:config.externalScript,
        resWrap:config.resWrap,
        maxSize:config.maxSize
    };
    this._embedMergeScript(opt);
    // embed common script resource
    opt = _util.merge(opt,{
        resPoint:'scriptPoint',
        resConfig:'scriptConfig'
    });
    this._embedResource(opt);
    // embed script template
    this._embedTemplate({
        maxSize:config.maxSize,
        resType:'js',
        resName:'scripts',
        resSuffix:'.js',
        resConfig:'scriptConfig',
        resInline:config.inlineWrap,
        resExternal:config.externalWrap,
        resRegexp:this._getWrapperRegExp(config.inlineWrap)
    });
};
/**
 * embed merge style/script
 * @param  {Object}  config - config object
 * @param  {Number} config.maxSize        - max inline size
 * @param  {String} config.resWrap        - source wrapper
 * @param  {String} config.inlineScript   - inline script wrapper
 * @param  {String} config.externalScript - external script wrapper
 * @return {Void}
 */
// pro.embedResource = function(config){
//
// };
/**
 * embed template to html file
 * @private
 * @param  {Object} config - config object
 * @param  {String} config.resWrap    - source wrapper
 * @param  {String} config.inlineWrap - inline wrapper
 * @return {Void}
 */
pro._inlineTemplate = function(config){
    var parser = _io.getFromCache(this._uri),
        list = parser.nejTemplates;
    // check templates embedded
    if (!list||!list.length){
        return;
    }
    // inline templates
    list.forEach(function(pos){
        var ret = [],
            explorer = pos.explorer;
        if (!explorer){
            return;
        }
        // embed text template
        var content = explorer.stringify({
            resType:'text',
            resWrap:config.inlineWrap
        });
        if (!!content){
            ret.push(content);
        }
        // embed external template
        var content = explorer.stringify({
            resType:'template'
        });
        if (!!content){
            ret.push(content);
        }
        parser.buffer[pos.pointer] = ret.join('\n');
    });
    // append style and script end
    // embed style template
    var arr = [],
        conf = parser.templateConfig;
    if (!!conf.styles){
        arr.push(conf.styles);
    }
    // embed script template
    if (!!conf.scripts){
        arr.push(conf.scripts);
    }

    // inline template
    var point = list[list.length-1].pointer;
    parser.buffer[point] += '\n'+arr.join('\n');
    //util.format(
    //    this._getInlineWrapper(config.resWrap),
    //    ret.join('\n')
    //);
    // lock template inline
    parser.nejTemplates = [];
};
/**
 * embed template to html file
 * @private
 * @param  {Object} config - config object
 * @param  {String} config.resWrap    - source wrapper
 * @param  {String} config.inlineWrap - inline wrapper
 * @return {Void}
 */
pro._inlineModule = function(config){
    var parser = _io.getFromCache(this._uri);
    if (parser.modulePoint<0){
        return;
    }
    var explorer = parser.modules;
    if (!!explorer){
        var content = explorer.stringify({
            resType:'template',
            resWrap:'<div style="display:none" id="%s">\n%s\n</div>'
        });
        if (!!content){
            parser.buffer[parser.modulePoint] = util.format(
                this._getInlineWrapper(config.resWrap),content
            );
        }
    }
    parser.modulePoint = -1;
};
/**
 * embed template to html file
 * @param  {Object} config - config object
 * @param  {String} config.resWrap    - source wrapper
 * @param  {String} config.inlineWrap - inline wrapper
 * @return {Void}
 */
pro.embedTemplate = function(config){
    this._inlineTemplate(config);
    this._inlineModule(config);
};
/**
 * format resource uri
 * @private
 * @param  {String} uri - uri path
 * @param  {Object}  config - config object
 * @param  {String}  config.pathRoot - file relative to
 * @param  {String}  config.webRoot  - web root path
 * @param  {Array}   config.domain   - domain list
 * @param  {Boolean} config.fromPage - uri from page
 * @return {String} uri after complete
 */
pro._formatURI = function(uri,config){
    // use path relative to webroot for server template
    var domain = config.domain;
    if ((!domain||!domain.length)&&
        config.fromPage!==!1&&
        this.isTemplate){
        domain = ['/'];
    }
    // next domain
    domain = _util.randNext(domain);
    if (!domain){
        // use relative path
        var root = path.dirname(config.pathRoot),
            ret = _path.normalize(
                path.relative(root,uri)
            );
        return (ret.indexOf('.')!==0?'./':'')+ret;
    }
    return uri.replace(
        config.webRoot,domain
    );
};
/**
 * format style/script uri
 * @private
 * @param  {String} name   - file name
 * @param  {String} sufix  - file suffix
 * @param  {String} config - config object
 * @return {String} uri after formatted
 */
pro._formatRSURI = function(name,sufix,config){
    // console.log('%s.%s', name, sufix);
    // format content
    var content = this._formatContent(
        _io.getFromCache(name+sufix),
        _util.merge(config,{
            output:config.outStacRoot+name+sufix,
            fromPage:!1
        })
    );
    // calculate file name and version
    var ver = _util.md5(content),
        ret = _util.version(
            config.versionMode,{
                RAND:_util.increment(),
                VERSION:ver,
                FILENAME:name
            }
        );
    // output file
    var file = config.outStacRoot+ret.file+sufix;
    // logger
    this.emit('debug',{
        data:[file],
        message:'output file %s'
    });
    // output
    _io.output(file,content,config.charset);
    _io.resource(file.replace(config.webRoot,'/'),ver);
    // format uri
    var uri = this._formatURI(file,{
        pathRoot:config.output,
        webRoot:config.webRoot,
        domain:config.domain
    });
    return uri+(!ret.version?'':'?')+ret.version;
};
/**
 * format content resource
 * @private
 * @param  {String} content - file content
 * @param  {Object} config  - config object
 * @return {String} content after format
 */
pro._formatContent = function(content,config){
    var self = this;
    return _path.unwrapURI(
        content,function(type,uri){
            var func = URLHANDLER[type];
            if (!!func){
                return func.call(self,uri,config);
            }
            return uri;
        }
    );
};
/**
 * scan buffer to do something with style/script
 * @param  {Function} func  - do something
 * @param  {Object}   scope - function scope
 * @return {Void}
 */
pro.scan = function(callback,scope){
    _io.getFromCache(this._uri).scan(
        callback,scope
    );
};
/**
 * output content
 * @param  {Object} config - config object
 * @param  {String} config.charset     - output file charset
 * @param  {Number} config.mdlVersion  - nej module version mode
 * @param  {String} config.outHtmlRoot - html output root
 * @param  {String} config.outTmplRoot - template output root
 * @return {Object}
 */
pro.output = function(config){
    var parser = _io.getFromCache(this._uri),
        name = this.isTemplate
             ? 'outTmplRoot' : 'outHtmlRoot',
        file = this._uri.replace(
            this._root,config[name]
        );
    config = _util.merge(config,{
        output:file,
        mdlSource:(parser.moduleConfig||{}).root
    });
    // merge resource path
    var content = this._formatContent(
        parser.stringify(),config
    );
    // merge nej module version mode
    content = content.replace(
        _util.MODULE_VERSION_HOLDER,
        config.mdlVersion
    );
    // cache content
    _io.cache(file,content);
    return file;
};


/*
 * exports toolkit
 * @author   genify(caijf@corp.netease.com)
 */
// klass exports map
var KLASS = {
    // base klass
    Event:'util/event',
    Logger:'util/logger#Logger',
    // resource meta
    RES_Text:'meta/text',
    RES_Html:'meta/html',
    RES_Style:'meta/style',
    RES_Script:'meta/script',
    RES_Template:'meta/template',
    RES_Resource:'meta/resource',
    // resource explorer
    EXP_Html:'explorer/html',
    EXP_Style:'explorer/style',
    EXP_Script:'explorer/script',
    EXP_Template:'explorer/template',
    EXP_Explorer:'explorer/explorer',
    // resource adapter
    ADP_Style:'adapter/style',
    ADP_Script:'adapter/script',
    // resource parser
    PRS_Tag:'parser/tag#Parser',
    PRS_Html:'parser/html',
    PRS_Config:'parser/config',
    PRS_Tokenizer:'parser/token',
    // file parser
    FLP_Parser:'script/parser',
    FLP_NEJParser:'script/nej#Parser',
    FLP_NEJPatcher:'script/nej/patcher',
    // entry parser
    Deployer:'deploy',
    Exporter:'export'
};
// api exports map
var API = {
    io:'util/io',
    rg:'util/args',
    fs:'util/file',
    ps:'util/path',
    ut:'util/util',
    ks:'util/klass',
    dp:'util/dependency',
    lg:'util/logger#level,logger,log',
    tag:'parser/tag#stringify',
    nej:'script/nej/util'
};
// export klass or api
function global(map){
    Object.keys(map).forEach(function(key){
        var file = map[key],
            arr = file.split('#'),
            mdl = require('./lib/'+arr[0]+'.js');
        // for util/logger#Logger
        if (!!arr[1]){
            // for util/logger#level,logger
            var brr = arr[1].split(',');
            if (brr.length>1){
                var ret = {};
                brr.forEach(function(name){
                    ret[name] = mdl[name];
                });
                mdl = ret;
            }else{
                mdl = mdl[brr[0]];
            }
        }
        exports[key] = mdl;
    });
};
// export constructor
// export api
global(KLASS);
global(API);

// bin api
var  path   = require('path'),
    _fs     = require('./lib/util/file.js'),
    _path   = require('./lib/util/path.js'),
    _util   = require('./lib/util/util.js'),
    _log    = require('./lib/util/logger.js'),
    _logger = _log.logger;
/**
 * init project deploy config
 * @param  {String} output - output path
 * @return {Void}
 */
exports.init = function(output){
    output = _path.absolute(
        output+'/',process.cwd()+'/'
    );
    var content = require('swig').renderFile(
        __dirname+'/template/release.conf',{
            comment:'#',
            DIR_WEBROOT:'../webapp/'
        }
    );
    _fs.write(output+'release.conf',content);
    _logger.info('output release.conf to %s',output);
};
/**
 * deploy project by config file
 * @param  {String}   file - config file path
 * @param  {Object}   config - config options
 * @param  {Function} callback - deploy done callback
 * @return {Void}
 */
exports.build = function(file,config,callback){
    file = _path.absolute(
        file,process.cwd()+'/'
    );
    var opt = _util.merge(config,{
        file:file,
        done:callback||function(){}
    });
    new (require('./lib/deploy.js'))(opt);
};
/**
 * export script list
 * @param  {Array}    list     - script list
 * @param  {Object}   config   - config object
 * @param  {String}   config.output - output file path
 * @param  {String}   config.bags   - output name bags
 * @param  {Function} callback - export callback
 * @return {Void}
 */
exports.export = function(list,config,callback){
    // check list
    callback = callback||function(){};
    if (!list||!list.length){
        _logger.warn('no input script files');
        callback();
        return;
    }
    // absolute output
    config = config||{};
    var cwd = process.cwd()+'/';
    config.output = _path.absolutePath(
        config.output||'./output.js',cwd
    );
    // absolute name bags
    if (!!config.bags){
        config.bags = _path.absolutePath(
            config.bags,cwd
        );
    }
    // do export
    new (require('./lib/export.js'))(
        _util.merge(config,{
            file:_path.absolutePath(
                './script.html',cwd
            ),
            list:list,
            done:callback||function(){}
        })
    );
};
/**
 * build web cache information
 * @param {String} file - config file path
 * @param {Object} options - build config object
 * @param {String} options.appid - web cache for app identify
 * @param {String} options.token - web cache upload api token
 * @param {Function} callback - after build callback
 */
exports.cache = function(file,options,callback){
    _logger.setLevel(options.level);
    var config = _path.absolute(
        file,process.cwd()+'/'
    );
    var root = path.dirname(config)+'/';
    _logger.info('run web cache build with config file %s',config);
    try{
        config = require(config);
        _logger.debug('config information -> %j',config);
    }catch(ex){
        _logger.error('cant get web cache config for reason:\n%s',ex.stack);
        process.abort();
    }
    config.root = root;
    config.ondone = callback||function(){
        // TODO
    };
    config.token = options.token||config.token||'';
    config.appid = options.appid||config.appid||'';
    require('./lib/cache.js').run(config);
};