const chalk = require('chalk');
const fs = require('fs');
const ora = require('ora');
const YAML = require('json2yaml');
const Path = require('path');
const { js_beautify: beautify } = require('js-beautify');
const requireFromUrl = require('require-from-url/sync');
require('require-yaml');

/**
 * Sort an object by its keys
 *
 * @since 0.8.0
 * @public
 *
 * @param  {Object} object
 * @return {Object}
 */
function sortObject(object) {
    return Object.keys(object)
        .sort()
        .reduce((result, key) => {
            result[key] = object[key];

            return result;
        }, {});
}

/**
* Print a task name in a custom format
*
* @since 0.5.0
* @public
*
* @param  {string} name The name of the task
*/// istanbul ignore next
function printTask(isQuiet, name) {
    if (isQuiet) {
        return;
    }

    process.stdout.write(chalk.blue(`\n🤖  - ${name}:\n===================================\n`));
}

/**
* Outputs the task status
*
* @since 0.5.0
* @public
*
* @param  {string} taskName The task name
*
* @return {Function}          The function to be fired when is loaded
*/// istanbul ignore next
function task(gren, taskName) {
    if (gren.options.quiet) {
        gren.tasks[taskName] = {};

        return noop;
    }
    const spinner = ora(taskName);
    gren.tasks[taskName] = spinner;

    spinner.start();

    return message => {
        spinner.succeed(message);
    };
}

/**
 * Clears all the tasks that are still running
 *
 * @since 0.6.0
 * @public
 *
 * @param  {GithubReleaseNotes} gren
 */// istanbul ignore next
function clearTasks(gren) {
    if (!Object.keys(gren.tasks.length)) {
        return;
    }

    Object.keys(gren.tasks).forEach((taskName) => {
        gren.tasks[taskName].stop();
    });

    process.stdout.write(chalk.red('\nTask(s) stopped because of the following error:\n'));

    gren.tasks = [];
}

/**
* Check if e value is between a min and a max
*
* @since 0.5.0
* @public
*
* @param  {number}  value
* @param  {number}  min
* @param  {number}  max
*
* @return {Boolean}
*/
function isInRange(value, min, max) {
    return !Math.floor((value - min) / (max - min));
}

/**
* Transforms a dasherize string into a camel case one.
*
* @since 0.3.2
* @public
*
* @param  {string} value The dasherize string
*
* @return {string}       The camel case string
*/
function dashToCamelCase(value) {
    return value
        .toLowerCase()
        .replace(/-([a-z])/g, (match) => match[1].toUpperCase());
}

/**
 * Converts an array like string to an actual Array,
 * converting also underscores to spaces
 *
 * @since 0.6.0
 * @public
 *
 * @param  {string} arrayLike The string of items
 * e.g.
 * "wont_fix, duplicate, bug"
 *
 * @return {Array}  The items with spaces instead of underscores.
 */
function convertStringToArray(arrayLike) {
    if (!arrayLike) {
        return [];
    }

    if (typeof arrayLike === 'object') {
        return Object.keys(arrayLike).map((itemKey) => arrayLike[itemKey]);
    }

    return arrayLike
        .replace(/\s/g, '')
        .split(',')
        .map((itemName) => itemName.replace(/_/g, ' ', itemName));
}

/**
* Format a date into a string
*
* @since 0.5.0
* @public
*
* @param  {Date} date
* @return {string}
*/
function formatDate(date) {
    return ('0' + date.getDate()).slice(-2) + '/' + ('0' + (date.getMonth() + 1)).slice(-2) + '/' + date.getFullYear();
}

/**
 * Gets the content from a filepath a returns an object
 *
 * @since  0.6.0
 * @public
 *
 * @param  {string} filepath
 * @return {Object|boolean}
 */
function requireConfig(filepath) {
    if (!fs.existsSync(filepath)) {
        return false;
    }

    process.stdout.write(chalk.cyan(`Getting gren config from local file ${filepath}\n`));

    if (getFileNameFromPath(filepath).match(/\./g).length === 1) {
        return JSON.parse(fs.readFileSync(filepath, 'utf8'));
    }

    return require(filepath);
}

/**
 * Get configuration from the one of the config files
 *
 * @since 0.6.0
 * @private
 *
 * @param  {string} path Path where to look for config files
 * @return {Object} The configuration from the first found file or empty object
 */
function getConfigFromFile(path, customFilename = null) {
    if (customFilename) {
        const config = requireConfig(Path.join(path, customFilename));
        if (!config) {
            throw chalk.red(`Could not find custom config file: ${customFilename}`);
        }
        return config;
    }

    return getFileTypes()
        .reduce((carry, filename) => carry || requireConfig(Path.join(path, filename)), false) || {};
}

/**
 * Return the extension of a filename
 *
 * @param  {string} filename
 *
 * @return {string}
 */
function getFileExtension(filename) {
    return filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1);
}

/**
 * Create the content for a configuratio file, based on extension and data
 *
 * @param  {string} path
 * @param  {Object} data
 *
 * @return {string} File content
 */// istanbul ignore next
function writeConfigToFile(path, data) {
    const extension = getFileExtension(getFileNameFromPath(path));
    const dataType = {
        yml: content => YAML.stringify(content),
        yaml: content => YAML.stringify(content),
        json: content => beautify(JSON.stringify(content)),
        none: content => beautify(JSON.stringify(content)),
        js: content => beautify(`module.exports = ${JSON.stringify(content)}`)
    };

    return dataType[extension || 'none'](data);
}

/**
 * Get the filename from a path
 *
 * @since  0.10.0
 * @private
 *
 * @param  {string} path
 *
 * @return {string}
 */
function getFileNameFromPath(path = '') {
    return path.replace(/^.*[\\/]/, '');
}

/**
 * Get the file types for the configuration
 *
 * @since  0.13.0
 *
 * @return {Array}
 */
function getFileTypes() {
    return [
        '.grenrc.yml',
        '.grenrc.json',
        '.grenrc.yaml',
        '.grenrc.js',
        '.grenrc'
    ];
}

/**
 * Remove all the configuration files
 *
 * @since  0.13.0
 *
 * @param {Boolean} confirm     Necessary to force the function.
 */
function cleanConfig(confirm, path = process.cwd()) {
    if (confirm !== true) {
        return;
    }

    getFileTypes().forEach(fileName => {
        const file = `${path}/${fileName}`;
        if (!fs.existsSync(file)) {
            return false;
        }

        fs.unlinkSync(file);

        return file;
    });
}

/**
 * judge whether to get config from remote uri
 * @since  0.18.0
 * @private
 *
 * @param {string} path
 *
 * @returns {string}
 */
function getRemoteUrl(path) {
    const pkgPath = Path.join(path, 'package.json');
    const pkgExist = fs.existsSync(pkgPath);
    const { gren = '' } = pkgExist ? require(pkgPath) : {};
    return gren;
}

/**
 * get config from remote
 * @since 0.18.0
 * @private
 *
 * @param  {string} path Path where to look for config files
 * @return {Object} The configuration from the first found file or empty object
*/
function getConfigFromRemote(url) {
    if (!url) return null;

    process.stdout.write(chalk.cyan(`Fetching gren config from remote: ${url}\n`));

    let config = null;
    try {
        config = requireFromUrl(url);
    } catch (error) {
        // console.error(error);
        process.stdout.write(chalk.cyan(`Fetched remote config fail: ${url}\n`));
        throw new Error(error);
    }

    process.stdout.write(chalk.cyan(`Fetched remote config succeed`));

    return config;
}

/**
 * combine getConfigFromRemote & getGrenConfig
 * @since 0.18.0
 * @public
 *
 * @param  {string} path Path where to look for config files
 * @return {Object} The configuration from the first found file or empty object
*/
function getGrenConfig(path, ...args) {
    const remoteUrl = getRemoteUrl(path);
    let config;
    if (remoteUrl) {
        config = getConfigFromRemote(remoteUrl);
    }

    if (!config) {
        config = getConfigFromFile(path, ...args);
    }

    return config;
}

/**
 * Just a noop function
 */
function noop() {}

// Allow nodeunit to work. Has to be fixed.
module.exports = {
    cleanConfig,
    clearTasks,
    convertStringToArray,
    dashToCamelCase,
    formatDate,
    getConfigFromFile,
    getRemoteUrl,
    getConfigFromRemote,
    getGrenConfig,
    getFileExtension,
    getFileNameFromPath,
    getFileTypes,
    isInRange,
    noop,
    printTask,
    requireConfig,
    sortObject,
    task,
    writeConfigToFile
};
