"use strict";
var task_utils_1 = require("./task.utils");
var fs_1 = require("fs");
var path_1 = require("path");
var fs_2 = require("fs");
var path_2 = require("path");
var fs_3 = require("fs");
var Rx = require('rx');
var path_3 = require("path");
var fs_4 = require("fs");
var crypto_1 = require("crypto");
var fs_5 = require("fs");
var hd = require('hash-dir');
var hashDirAsObservable = Rx.Observable.fromNodeCallback(hd);
var hashFileAsObservable = Rx.Observable.fromNodeCallback(hashFile);
var lstatAsObservable = Rx.Observable.fromNodeCallback(fs_5.lstat);
var _ = require('../lodash.custom');
// todo windows support for .bat files etc
var supportedTaskFileExtensions = ['.js', '.sh'];
/**
 * Try to auto-load configuration files
 * from the users CWD
 */
function retrieveDefaultInputFiles(config) {
    var defaultConfigFiles = ['crossbow.yaml', 'crossbow.js', 'crossbow.yml', 'crossbow.json'];
    return readInputFiles(defaultConfigFiles, config.cwd);
}
exports.retrieveDefaultInputFiles = retrieveDefaultInputFiles;
/**
 * Try to load cbfiles (like gulp) from the users
 * working directory
 * @param config
 * @returns {InputFiles}
 */
function retrieveCBFiles(config) {
    var defaultCBFiles = ['cbfile.js', 'crossbowfile.js'];
    var maybes = (function () {
        if (config.cbfile) {
            return [config.cbfile];
        }
        return defaultCBFiles;
    })();
    return readInputFiles(maybes, config.cwd);
}
exports.retrieveCBFiles = retrieveCBFiles;
/**
 * Try to retrieve input files from disk.
 * This is different from regular file reading as
 * we deliver errors with context
 */
function readInputFiles(paths, cwd) {
    /**
     * Get files that exist on disk
     * @type {ExternalFile[]}
     */
    var inputFiles = readFilesFromDisk(paths, cwd);
    /**
     * Add parsed input keys to them
     * @type {}
     */
    var inputs = inputFiles.map(function (inputFile) {
        /**
         * If the file does not exist, change the error to be an InputFileNotFound error
         * as this will allow more descriptive logging when needed
         */
        if (inputFile.errors.length) {
            return _.assign({}, inputFile, {
                // here there may be any types of file error,
                // but we only care that was an error, and normalise it
                // here for logging. We can added nice per-error messages later.
                errors: [{ type: task_utils_1.InputErrorTypes.InputFileNotFound }],
                input: undefined
            });
        }
        /**
         * If the input file was yaml, load it & translate to JS
         */
        if (inputFile.parsed.ext.match(/ya?ml$/i)) {
            var yml = require('js-yaml');
            try {
                return _.assign(inputFile, {
                    input: yml.safeLoad(fs_1.readFileSync(inputFile.resolved, 'utf8'))
                });
            }
            catch (e) {
                return _.assign(inputFile, {
                    input: undefined,
                    errors: [{ type: task_utils_1.InputErrorTypes.InvalidYaml, error: e }]
                });
            }
        }
        /**
         * Finally assume a JS/JSON file and 'require' it as normal
         */
        try {
            return _.assign({}, inputFile, {
                input: require(inputFile.resolved)
            });
        }
        catch (e) {
            return _.assign(inputFile, {
                input: undefined,
                errors: [{ type: task_utils_1.InputErrorTypes.InvalidInput, error: e }]
            });
        }
    });
    return {
        all: inputs,
        valid: inputs.filter(function (x) { return x.errors.length === 0; }),
        invalid: inputs.filter(function (x) { return x.errors.length > 0; })
    };
}
exports.readInputFiles = readInputFiles;
function readFilesFromDiskWithContent(paths, cwd) {
    var files = readFilesFromDisk(paths, cwd);
    return files
        .map(function (x) {
        if (x.errors.length)
            return x;
        x.content = fs_1.readFileSync(x.resolved, 'utf8');
        return x;
    });
}
exports.readFilesFromDiskWithContent = readFilesFromDiskWithContent;
function readFileContent(file) {
    return fs_1.readFileSync(file.resolved, 'utf8');
}
exports.readFileContent = readFileContent;
function writeFileToDisk(file, content) {
    var mkdirp = require('mkdirp').sync;
    mkdirp(path_3.dirname(file.resolved));
    fs_1.writeFileSync(file.resolved, content);
}
exports.writeFileToDisk = writeFileToDisk;
function getStubFileWithContent(path, cwd) {
    var file = getStubFile(path, cwd);
    file.content = '';
    return file;
}
exports.getStubFileWithContent = getStubFileWithContent;
function readOrCreateJsonFile(path, cwd) {
    var existing = readFilesFromDiskWithContent([path], cwd)[0];
    if (existing.errors.length) {
        if (existing.errors[0].type === task_utils_1.InputErrorTypes.FileNotFound) {
            var stub = getStubFileWithContent(path, cwd);
            stub.content = '{}';
            stub.data = JSON.parse(stub.content);
            return stub;
        }
    }
    else {
        try {
            existing.data = JSON.parse(existing.content);
        }
        catch (e) {
            existing.data = {};
        }
    }
    return existing;
}
exports.readOrCreateJsonFile = readOrCreateJsonFile;
function getStubFile(path, cwd) {
    var resolved = path_1.resolve(cwd, path);
    return {
        errors: [],
        rawInput: path,
        resolved: resolved,
        parsed: path_1.parse(resolved),
        relative: path_1.relative(cwd, resolved)
    };
}
exports.getStubFile = getStubFile;
/**
 * Take an array of paths and return file info + errors if they don't exist
 * @param paths
 * @param cwd
 * @returns {ExternalFile[]}
 */
function readFilesFromDisk(paths, cwd) {
    return paths
        .map(String)
        .map(function (x) { return getStubFile(x, cwd); })
        .map(function (incoming) {
        var resolved = incoming.resolved;
        /**
         * If the path does not exist, it's a FileNotFound error
         */
        if (!fs_1.existsSync(resolved)) {
            return _.assign(incoming, {
                errors: [{ type: task_utils_1.InputErrorTypes.FileNotFound }]
            });
        }
        /**
         * Not check it's a file & NOT a dir
         * @type {Stats}
         */
        var stat = fs_2.statSync(resolved);
        if (!stat.isFile()) {
            return _.assign(incoming, {
                errors: [{ type: task_utils_1.InputErrorTypes.NotAFile }],
            });
        }
        /**
         * At this point the file DOES exist
         */
        return incoming;
    });
}
exports.readFilesFromDisk = readFilesFromDisk;
/**
 * Attempt to use the LOCALLY installed crossbow version
 * first, this will ensure anything registered with .task etc
 * can be picked up by global installs too.
 * @param config
 * @returns {InputFiles}
 */
function getRequirePaths(config) {
    var local = path_2.join('node_modules', 'crossbow', 'dist', 'public', 'create.js');
    var global = path_2.join(__dirname, 'public', 'create.js');
    return readInputFiles([local, global], config.cwd);
}
exports.getRequirePaths = getRequirePaths;
function getExternalFiles(dirpaths, cwd) {
    return dirpaths
        .map(function (dirpath) {
        return path_1.resolve(cwd, dirpath);
    })
        .filter(fs_1.existsSync)
        .reduce(function (acc, dirPath) {
        return acc.concat(fs_3.readdirSync(dirPath).map(function (filepath) {
            var resolved = path_2.join(dirPath, filepath);
            var parsed = path_1.parse(resolved);
            var output = {
                rawInput: filepath,
                resolved: resolved,
                relative: path_1.relative(cwd, resolved),
                parsed: parsed,
                errors: []
            };
            return output;
        }));
    }, []);
}
exports.getExternalFiles = getExternalFiles;
function getPossibleTasksFromDirectories(dirpaths, cwd) {
    return getExternalFiles(dirpaths, cwd)
        .filter(function (x) { return supportedTaskFileExtensions.indexOf(x.parsed.ext) > -1; })
        .map(function (x) {
        return x.relative;
    });
}
exports.getPossibleTasksFromDirectories = getPossibleTasksFromDirectories;
(function (HashDirErrorTypes) {
    HashDirErrorTypes[HashDirErrorTypes["HashNotADirectory"] = "HashNotADirectory"] = "HashNotADirectory";
    HashDirErrorTypes[HashDirErrorTypes["HashPathNotFound"] = "HashPathNotFound"] = "HashPathNotFound";
})(exports.HashDirErrorTypes || (exports.HashDirErrorTypes = {}));
var HashDirErrorTypes = exports.HashDirErrorTypes;
function hashItems(dirs, cwd, existingHashes) {
    return Rx.Observable
        .from(dirs)
        .map(function (x) {
        return {
            userInput: x,
            pathObj: getStubFile(x, cwd)
        };
    })
        .distinct(function (x) { return x.pathObj.resolved; })
        .flatMap(hashFileOrDir)
        .toArray()
        .map(function (x) {
        return markHashes(x, existingHashes);
    });
}
exports.hashItems = hashItems;
function hashFile(filepath, fn) {
    var hash = crypto_1.createHash('sha256');
    fs_4.createReadStream(filepath)
        .on('data', function (chunk) {
        hash.update(chunk);
    })
        .on('end', function () {
        fn(null, hash.digest('hex'));
    })
        .on('error', fn);
}
function hashFileOrDir(input) {
    return lstatAsObservable(input.pathObj.resolved).flatMap(function (stats) {
        if (stats.isDirectory()) {
            return hashDirAsObservable(input.pathObj.resolved).map(function (tree) {
                return {
                    userInput: input.userInput,
                    resolved: input.pathObj.resolved,
                    hash: tree.hash
                };
            });
        }
        if (stats.isFile()) {
            return hashFileAsObservable(input.pathObj.resolved).map(function (hash) {
                return {
                    userInput: input.userInput,
                    resolved: input.pathObj.resolved,
                    hash: hash
                };
            });
        }
        return Rx.Observable.empty();
    });
}
function markHashes(newHashes, existingHashes) {
    var newHashPaths = newHashes.map(function (x) { return x.resolved; });
    var markedHashes = newHashes.map(function (newHash) {
        var match = existingHashes.filter(function (x) { return x.resolved === newHash.resolved; });
        newHash.changed = (function () {
            if (match.length) {
                return match[0].hash !== newHash.hash;
            }
            return true; // return true by default so that new entries always run
        })();
        return newHash;
    });
    var otherHashes = existingHashes.filter(function (hash) {
        return newHashPaths.indexOf(hash.resolved) === -1;
    });
    var output = otherHashes.concat(newHashes).filter(Boolean);
    return {
        output: output,
        markedHashes: markedHashes
    };
}
//# sourceMappingURL=file.utils.js.map