var gulp = require('gulp'),
    fs = require('fs'),
    through = require('through'),
    rename = require('gulp-rename'),
    path = require('path'),
    slash = require('gulp-slash'),
    clipEmptyFiles = require('gulp-clip-empty-files'),
    gutil = require('gulp-util'),
    flatten = require('gulp-flatten'),
    npmPath = require('path'),
    File = gutil.File,
    exec = require('child_process').exec,
    license = '' +
        '// (C) Copyright 2015 Martin Dougiamas\n' +
        '//\n' +
        '// Licensed under the Apache License, Version 2.0 (the "License");\n' +
        '// you may not use this file except in compliance with the License.\n' +
        '// You may obtain a copy of the License at\n' +
        '//\n' +
        '//     http://www.apache.org/licenses/LICENSE-2.0\n' +
        '//\n' +
        '// Unless required by applicable law or agreed to in writing, software\n' +
        '// distributed under the License is distributed on an "AS IS" BASIS,\n' +
        '// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' +
        '// See the License for the specific language governing permissions and\n' +
        '// limitations under the License.\n\n';

/**
 * Copy a property from one object to another, adding a prefix to the key if needed.
 * @param {Object} target Object to copy the properties to.
 * @param {Object} source Object to copy the properties from.
 * @param {String} prefix Prefix to add to the keys.
 */
function addProperties(target, source, prefix) {
    for (var property in source) {
        target[prefix + property] = source[property];
    }
}

/**
 * Treats a file to merge JSONs. This function is based on gulp-jsoncombine module.
 * https://github.com/reflog/gulp-jsoncombine
 * @param  {Object} file File treated.
 */
function treatFile(file, data) {
    if (file.isNull() || file.isStream()) {
        return; // ignore
    }
    try {
        var path = file.path.substr(file.path.lastIndexOf('/src/') + 5);
        data[path] = JSON.parse(file.contents.toString());
    } catch (err) {
        console.log('Error parsing JSON: ' + err);
    }
}

/**
 * Treats the merged JSON data, adding prefixes depending on the component. Used in lang tasks.
 *
 * @param  {Object} data Merged data.
 * @return {Buffer}      Buffer with the treated data.
 */
function treatMergedData(data) {
    var merged = {};
    var mergedOrdered = {};

    for (var filepath in data) {
        var pathSplit = filepath.split('/'),
            prefix;

        pathSplit.pop();

        switch (pathSplit[0]) {
            case 'lang':
                prefix = 'core';
                break;
            case 'core':
                if (pathSplit[1] == 'lang') {
                    // Not used right now.
                    prefix = 'core';
                } else {
                    prefix = 'core.' + pathSplit[1];
                }
                break;
            case 'addon':
                // Remove final item 'lang'.
                pathSplit.pop();
                // Remove first item 'addon'.
                pathSplit.shift();

                // For subplugins. We'll use plugin_subfolder_subfolder2_...
                // E.g. 'mod_assign_feedback_comments'.
                prefix = 'addon.' + pathSplit.join('_');
                break;
            case 'assets':
                prefix = 'assets.' + pathSplit[1];
                break;
        }

        if (prefix) {
            addProperties(merged, data[filepath], prefix + '.');
        }
    }

    // Force ordering by string key.
    Object.keys(merged).sort().forEach(function(k){
        mergedOrdered[k] = merged[k];
    });

    return new Buffer(JSON.stringify(mergedOrdered, null, 4));
}

/**
 * Build lang file.
 *
 * @param  {String} language    Language to translate.
 * @param  {String[]} langPaths Paths to the possible language files.
 * @param  {String}   buildDest Path where to leave the built files.
 * @param  {Function} done      Function to call when done.
 * @return {Void}
 */
function buildLang(language, langPaths, buildDest, done) {
    var filename = language + '.json',
        data = {},
        firstFile = null;

    var paths = langPaths.map(function(path) {
        if (path.slice(-1) != '/') {
            path = path + '/';
        }
        return path + language + '.json';
    });

    gulp.src(paths, { allowEmpty: true })
        .pipe(slash())
        .pipe(clipEmptyFiles())
        .pipe(through(function(file) {
            if (!firstFile) {
                firstFile = file;
            }
            return treatFile(file, data);
        }, function() {
            /* This implementation is based on gulp-jsoncombine module.
             * https://github.com/reflog/gulp-jsoncombine */
            if (firstFile) {
                var joinedPath = path.join(firstFile.base, language+'.json');

                var joinedFile = new File({
                    cwd: firstFile.cwd,
                    base: firstFile.base,
                    path: joinedPath,
                    contents: treatMergedData(data)
                });

                this.emit('data', joinedFile);
            }
            this.emit('end');
        }))
        .pipe(gulp.dest(buildDest))
        .on('end', done);
}

// Delete a folder and all its contents.
function deleteFolderRecursive(path) {
  if (fs.existsSync(path)) {
    fs.readdirSync(path).forEach(function(file) {
      var curPath = npmPath.join(path, file);
      if (fs.lstatSync(curPath).isDirectory()) {
        deleteFolderRecursive(curPath);
      } else {
        fs.unlinkSync(curPath);
      }
    });

    fs.rmdirSync(path);
  }
}

// List of app lang files. To be used only if cannot get it from filesystem.
var paths = {
        src: './src',
        assets: './src/assets',
        lang: [
            './src/lang/',
            './src/core/**/lang/',
            './src/addon/**/lang/',
            './src/assets/countries/',
            './src/assets/mimetypes/'
        ],
        config: './src/config.json',
    };

// Build the language files into a single file per language.
gulp.task('lang', function(done) {
    buildLang('en', paths.lang, path.join(paths.assets, 'lang'), done);
});

// Convert config.json into a TypeScript class.
gulp.task('config', function(done) {
    // Get the last commit.
    exec('git log -1 --pretty=format:"%H"', function (err, commit, stderr) {
        if (err) {
            console.error('An error occurred while getting the last commit: ' + err);
        } else if (stderr) {
            console.error('An error occurred while getting the last commit: ' + stderr);
        }

        gulp.src(paths.config)
            .pipe(through(function(file) {
                // Convert the contents of the file into a TypeScript class.
                // Disable the rule variable-name in the file.
                var config = JSON.parse(file.contents.toString()),
                    contents = license + '// tslint:disable: variable-name\n' + 'export class CoreConfigConstants {\n',
                    that = this;

                for (var key in config) {
                    var value = config[key];
                    if (typeof value == 'string') {
                        // Wrap the string in ' and scape them.
                        value = "'" + value.replace(/([^\\])'/g, "$1\\'") + "'";
                    } else if (typeof value != 'number' && typeof value != 'boolean') {
                        // Stringify with 4 spaces of indentation, and then add 4 more spaces in each line.
                        value = JSON.stringify(value, null, 4).replace(/^(?:    )/gm, '        ').replace(/^(?:})/gm, '    }');
                        // Replace " by ' in values.
                        value = value.replace(/: "([^"]*)"/g, ": '$1'");

                        // Check if the keys have "-" in it.
                        var matches = value.match(/"([^"]*\-[^"]*)":/g);
                        if (matches) {
                            // Replace " by ' in keys. We cannot remove them because keys have chars like '-'.
                            value = value.replace(/"([^"]*)":/g, "'$1':");
                        } else {
                            // Remove ' in keys.
                            value = value.replace(/"([^"]*)":/g, "$1:");
                        }

                        // Add type any to the key.
                        key = key + ': any';
                    }

                    // If key has quotation marks, remove them.
                    if (key[0] == '"') {
                        key = key.substr(1, key.length - 2);
                    }
                    contents += '    static ' + key + ' = ' + value + ';\n';
                }

                // Add compilation info.
                contents += '    static compilationtime = ' + Date.now() + ';\n';
                contents += '    static lastcommit = \'' + commit + '\';\n';

                contents += '}\n';

                file.contents = new Buffer(contents);
                this.emit('data', file);
            }))
            .pipe(rename('configconstants.ts'))
            .pipe(gulp.dest(paths.src))
            .on('end', done);
    });
});

gulp.task('default', gulp.parallel('lang', 'config'));

gulp.task('watch', function() {
    var langsPaths = paths.lang.map(function(path) {
        return path + 'en.json';
    });
    gulp.watch(langsPaths, { interval: 500 }, gulp.parallel('lang'));
    gulp.watch(paths.config, { interval: 500 }, gulp.parallel('config'));
});

var templatesSrc = [
        './src/components/**/*.html',
        './src/core/**/components/**/*.html',
        './src/core/**/component/**/*.html',
        // Copy all addon components because any component can be injected using extraImports.
        './src/addon/**/components/**/*.html',
        './src/addon/**/component/**/*.html'
    ],
    templatesDest = './www/templates';

// Copy component templates to www to make compile-html work in AOT.
gulp.task('copy-component-templates', function(done) {
    deleteFolderRecursive(templatesDest);

    gulp.src(templatesSrc, { allowEmpty: true })
        .pipe(flatten())
        .pipe(gulp.dest(templatesDest))
        .on('end', done);
});