/**
 * Generate project structure.
 * @function ci.worker.generateStructure
 * @example
 *
 *      buildStructure({
 *          "lib": {
 *              "string": {
 *                  "_prototype.js": {
 *                      "_type": "file",
 *                      "__serializable": true,
 *                      "_tmpl": "/Users/okuni/WebstormProjects/apeman/sites/apeman.info/abstract/tmpl/js/_prototype.js.hbs",
 *                      "_data": {
 *                          "requireName": "prototype/lib/string",
 *                          "exportsName": "lib.string._prototype"
 *                      },
 *                      "_force": true,
 *                      "_mode": "444"
 *                  }
 *              },
 *           }
 *      }, function(err){
 *          console.log('done!');
 *      });
 *
 * @param {object} config - Configuration object.
 * @param {object} data - Structure data.
 * @param {function} callback - Callback when done.
 * @author Taka Okunishi
 *
 */

var path = require('path'),
    file = require('../../lib/file'),
    debug = require('./_debug'),
    loadHbsTmpl = file.loadHbsTmpl,
    relativeSymlink = file.relativeSymlink,
    _newRenderData = require('./_new_render_data'),
    _workerName = require('./_worker_name'),
    fs = require('fs');

exports = module.exports = function (config, callback) {
    var data = exports._flatten(config.data);
    require('async').series(
        data.map(function (data) {
            var create = exports._resolveCreator(data._type);
            return create(data);
        }),
        function (err) {
            callback(err);
        }
    );
};

/**
 * Flatten the structure data.
 * @param data
 * @returns {Array}
 * @private
 */
exports._flatten = function (data) {
    var __path = data.__path;
    var result = [],
        keys = Object.keys(data).filter(function (key) {
            return !key.match(/^__/);
        });
    if (keys.length) {
        keys.forEach(function (key) {
            var value = data[key];
            if (typeof(value) === 'undefined') return;
            if (value === null) return;
            value.__path = __path ? [__path, key].join('/') : key;
            if (value.__serializable) {
                result.push(value);
            } else {
                result = result.concat(exports._flatten(value));
            }
        });
    } else {
        result.push(data);
    }
    return result;
};

/**
 * Resolve a structure entry creator.
 * @param {string} type - Create type.
 * @returns {function} - Resolve creator.
 * @protected
 * @ignore
 */
exports._resolveCreator = function (type) {
    switch (type && type.toLowerCase()) {
        case 'file':
            return exports._createFile;
        case 'symlink':
            return exports._createSymlink;
        default:
            return exports._createDir;
    }
};

/**
 * Create a new file.
 * @param data
 * @returns {Function}
 * @private
 */
exports._createFile = function (data) {
    var filename = data.__path,
        mode = data._mode;

    return function (callback) {
        fs.exists(filename, function (exists) {
            var abort = exists && !data._force;
            if (abort) {
                callback();
                return;
            }
            file.forceUnlink(filename, function (err) {
                if (err) {
                    callback(err);
                    return;
                }
                var tmplName = data._tmpl;

                if (tmplName) {
                    loadHbsTmpl(tmplName, function (err, tmpl) {
                        if (err) {
                            callback(err);
                        } else {
                            if (typeof(data._data) === 'string') {
                                data._data = require(data._data);
                            }
                            var renderData = _newRenderData(data._data || {}),
                                basedir = path.resolve('.');
                            renderData.authorModule = renderData.authorModule || _workerName.full(__filename);
                            renderData.$relativePathToBaseDir$ = path.relative(path.dirname(filename), basedir);
                            renderData.$relativePathFromBaseDir$ = path.relative(basedir, filename);
                            var rendered = tmpl(renderData);
                            exports._createFile._write(filename, rendered, mode, callback);
                        }
                    });
                } else {
                    var src = data._src;
                    if (src) {
                        fs.readFile(src, function (err, buffer) {
                            if (err) {
                                callback(err);
                            } else {
                                exports._createFile._write(filename, buffer, mode, callback);
                            }
                        });
                    } else {
                        exports._createFile._write(filename, '', mode, callback);
                    }
                }
            });
        });
    }

};

exports._createFile._write = function (filename, content, mode, callback) {
    exports._writeFileP(filename, content, function (err) {
        if (err) {
            callback(err);
            return;
        }
        if (mode) {
            fs.chmod(filename, mode, function (err) {
                if (!err) {
                    debug.didCreateFile(filename);
                }
                callback(err);
            });
        } else {
            debug.didCreateFile(filename);
            callback(err);
        }
    });
};

/**
 * Create a symbolic link.
 * @param data
 * @private
 */
exports._createSymlink = function (data) {
    var linkName = data.__path;
    return function (callback) {
        file.forceUnlink(linkName, function (err) {
            if (err) {
                callback(err);
                return;
            }
            var src = data._src;
            file.mkdirP(path.dirname(linkName), function (err) {
                if (err) {
                    callback(err);
                    return;
                }
                relativeSymlink(src, linkName, function (err) {
                    if (!err) {
                        debug.didCreateLink(linkName, src);
                    }
                    callback(err);
                });
            });
        });
    };
};

/**
 * Create a directory.
 * @param {object} data - Directory data.
 * @returns {function} - Async function.
 * @protected
 * @ignore
 */
exports._createDir = function (data) {
    var dirname = data.__path;
    return function (callback) {
        fs.exists(dirname, function (exists) {
            if (exists) {
                callback(null);
                return;
            }
            file.mkdirP(dirname, function (err) {
                if (!err) {
                    debug.didCreateDir(dirname);
                }
                callback(err);
            });
        });
    }
};

/**
 * Write a file. If not the files parent directory exists, create then first.
 * @param {string} filename - Filename to write.
 * @param {string} content - Content to write.
 * @param {function} callback - Callback when done.
 * @protected
 * @ignore
 */
exports._writeFileP = function (filename, content, callback) {
    file.mkdirP(path.dirname(filename), function () {
        fs.writeFile(filename, content, callback);
    });
};

