// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

'use strict';

var fs = require('fs');
var fse = require('fs-extra');
var https = require('https');
var path = require('path');
var child_process = require('child_process');
var yaml = require('js-yaml');
var optimist = require('optimist');

var util = require('./util');

// constants
var DEFAULT_REPO_PATH = 'README.md';
var DEFAULT_VERSION_NAME = 'dev';
var DEFAULT_LANGUAGE_NAME = 'en';

var THIS_FILE = path.basename(__filename);
var WARNING_COMMENT = '<!-- WARNING: This file is generated. See ' + THIS_FILE + '. -->\n\n';

// helpers
function isPluginName (packageName) {
    return packageName.match(/cordova-plugin-.*/);
}

function getRepoFileURI (repoName, commit, filePath) {
    return 'https://raw.githubusercontent.com/' + repoName + '/' + commit + '/' + filePath;
}

function getRepoEditURI (repoName, commit, filePath) {
    return 'https://github.com/' + repoName + '/blob/' + commit + '/' + filePath;
}

function getLatestRelease (packageName) {
    var latestRelease = child_process.execSync('npm info ' + packageName + ' dist-tags.latest');
    return latestRelease.toString().trim();
}

function packageNameFromRepoName (repoName) {
    return repoName.split('/')[1];
}

function getFetchedFileConfig (entry) {
    // get entry components
    var srcConfig = entry.src;
    var destConfig = entry.dest;

    // validate entry
    if (!srcConfig) {
        console.error("entry '" + entry.toString() + "' missing 'src'");
        return;
    }

    if (!srcConfig.repoName) {
        console.error("entry '" + entry.toString() + "' missing 'repoName' in 'src'");
        return;
    }

    if (!srcConfig.repoName) {
        console.error("entry '" + entry.toString() + "' missing 'repoName' in 'src'");
        return;
    }

    if (!destConfig) {
        console.error("entry '" + entry.toString() + "' missing 'dest'");
        return;
    }

    if (!destConfig.path) {
        console.error("entry '" + entry.toString() + "' missing 'path' in 'dest'");
        return;
    }

    // complete src config
    if (!srcConfig.packageName) {
        srcConfig.packageName = packageNameFromRepoName(srcConfig.repoName);
    }

    if (!srcConfig.path) {
        srcConfig.path = DEFAULT_REPO_PATH;
    }

    if (!srcConfig.commit) {
        srcConfig.commit = getLatestRelease(srcConfig.packageName);
    }

    // make front matter
    var frontMatter = {
        edit_link: getRepoEditURI(srcConfig.repoName, srcConfig.commit, srcConfig.path),
        title: srcConfig.packageName
    };

    // set special front matter values for plugins
    if (isPluginName(srcConfig.packageName)) {
        frontMatter.plugin_name = srcConfig.packageName;
        frontMatter.plugin_version = srcConfig.commit;
    }

    // set returned values
    var fetchedFileConfig = {
        frontMatter: frontMatter,
        downloadURI: getRepoFileURI(srcConfig.repoName, srcConfig.commit, srcConfig.path),
        savePath: destConfig.path
    };

    return fetchedFileConfig;
}

function getFrontMatter (text) {
    var frontMatterString = util.getFrontMatterString(text);
    if (frontMatterString !== null) {
        return yaml.load(frontMatterString);
    }
    return {};
}

function setFrontMatter (text, frontMatter, options) {
    var frontMatterString = yaml.dump(frontMatter, options);
    return util.setFrontMatterString(text, frontMatterString);
}

function dumpEntries (downloadPrefix, entries) {
    entries.forEach(function (entry) {
        // validate entry's dest config
        if (!entry.dest) {
            console.error("entry '" + entry.toString() + "' missing 'dest'");
            return;
        }

        if (!entry.dest.path) {
            console.error("entry '" + entry.toString() + "' missing 'path' in 'dest'");
            return;
        }

        // print the save path for the entry
        if (entry.dest && entry.dest.path) {
            var filePath = path.join(downloadPrefix, entry.dest.path);
            console.log(filePath);

        // error out on invalid entries
        } else {
            console.error('Invalid dest: ' + entry);
            process.exit(1);
        }
    });
}

function downloadEntries (downloadPrefix, entries) {
    entries.forEach(function (entry) {
        // verify and process entry
        var fetchedFileConfig = getFetchedFileConfig(entry);
        if (!fetchedFileConfig) {
            process.exit(1);
        }

        // get info for fetching
        var fetchURI = fetchedFileConfig.downloadURI;
        var outFilePath = path.join(downloadPrefix, fetchedFileConfig.savePath);
        var outFileDir = path.dirname(outFilePath);

        // create directory for the file if it doesn't exist
        if (!fs.existsSync(outFileDir)) {
            fse.mkdirsSync(outFileDir);
        }

        console.log(fetchURI + ' -> ' + outFilePath);

        // open the file for writing
        var outFile = fs.createWriteStream(outFilePath);

        // open an HTTP request for the file
        https.get(fetchURI, function (response) {
            if (response.statusCode !== 200) {
                console.error('Failed to download ' + fetchURI + ': got ' + response.statusCode);
                process.exit(1);
            }

            // read in the response
            var fileContents = '';
            response.setEncoding('utf8');
            response.on('data', function (data) {
                fileContents += data;
            });

            // process the response when it finishes
            response.on('end', function () {
                // merge new front matter and file's own front matter (if it had any)
                //
                // NOTE:
                //      fileFrontMatter's properties should override those of newFrontMatter
                var newFrontMatter = fetchedFileConfig.frontMatter;
                var fileFrontMatter = getFrontMatter(fileContents);
                var mergedFrontMatter = util.mergeObjects(newFrontMatter, fileFrontMatter);

                // add a warning and set the merged file matter in the file
                var contentsOnly = util.stripFrontMatter(fileContents);
                contentsOnly = WARNING_COMMENT + contentsOnly;

                var augmentedContents = setFrontMatter(contentsOnly, mergedFrontMatter);

                // write out the file
                outFile.end(augmentedContents);
            }).on('error', function (e) {
                console.error(e);
            });
        }); // http request
    }); // entries
}

// main
function main () {
    // get args
    var argv = optimist
        .usage('Usage: $0 [options]')
        .demand('config')
        .demand('docsRoot')
        .string('version')
        .string('language')
        .boolean('dump')
        .describe('config', '.yml file listing fetched files')
        .describe('docsRoot', 'docs root directory')
        .describe('version', 'version in which to save the downloaded files').default('version', DEFAULT_VERSION_NAME)
        .describe('language', 'language in which to save the downloaded files').default('language', DEFAULT_LANGUAGE_NAME)
        .describe('dump', 'only print the downloaded files')
        .argv;

    var configFile = argv.config;
    var docsRoot = argv.docsRoot;
    var targetVersion = argv.version;
    var targetLanguage = argv.language;
    var printOnly = argv.dump;
    var downloadPrefix = path.join(docsRoot, targetLanguage, targetVersion);

    // validate args
    if (!fs.existsSync(configFile)) {
        console.error("Config file doesn't exist.");
        process.exit();
    }

    if (!fs.existsSync(docsRoot)) {
        console.error("Docs root doesn't exist.");
        process.exit();
    }

    // get config
    var fetchConfig = fs.readFileSync(configFile);
    var configEntries = yaml.load(fetchConfig);

    // just dump entries if --dump was passed
    if (printOnly === true) {
        dumpEntries(downloadPrefix, configEntries);

    // otherwise, fetch them
    } else {
        downloadEntries(downloadPrefix, configEntries);
    }
}

main();
