import path = require("path");
import tl = require("vsts-task-lib/task");
import fs = require("fs");
import Q = require('q');

var Zip = require('jszip');

var workDir = tl.getVariable("System.DefaultWorkingDirectory");

export function checkAndFixFilePath(filePath: string, continueOnError: boolean ): string {
    if (filePath) {
        if (arePathEqual(filePath, workDir)) {
            // Path points to the source root, ignore it
            filePath = null;
        } else {
            if (continueOnError) {
                if (!tl.exist(filePath)) {
                    tl.warning(tl.loc("FailedToFindFile", "symbolsPath", filePath));
                    filePath = null;
                }
            } else {
                // will error and fail task if it doesn't exist.
                tl.checkPath(filePath, "symbolsPath");
            }
        }
    }

    return filePath;
}

export function getArchivePath(symbolsRoot: string): string {
    // If symbol paths do not have anything common, e.g /a/b/c and /x/y/z,
    // let's use some default name for the archive.
    let zipName = symbolsRoot ? path.basename(symbolsRoot) : "symbols";
    tl.debug(`---- Zip file name=${zipName}`); 

    let zipPath = path.join(workDir, `${zipName}.zip`); 
    tl.debug(`.... Zip file path=${zipPath}`); 

    return zipPath;
}

function arePathEqual(p1: string, p2: string): boolean {
    if (!p1 && !p2) return true;
    else if (!p1 || !p2) return false;
    else return path.normalize(p1 || "") === path.normalize(p2 || "");
}

function getAllFiles(rootPath: string, recursive: boolean): string[] {
    let files: string[] = [];

    let folders: string[] = [];
    folders.push(rootPath);

    while (folders.length > 0) {
        let folderPath = folders.shift();

        let children = fs.readdirSync(folderPath);
        for (let i = 0; i < children.length; i++) {
            let childPath = [folderPath, children[i]].join( "/");
            if (fs.statSync(childPath).isDirectory()) {
                if (recursive) {
                    folders.push(childPath);
                }
            } else {
                files.push(childPath);
            }
        }
    }

    return files;
}

export function createZipStream(symbolsPaths: string[], symbolsRoot: string): NodeJS.ReadableStream {
    tl.debug("---- Creating Zip stream");
    let zip = new Zip();

    symbolsPaths.forEach(rootPath => {
        let filePaths = getAllFiles(rootPath, /*recursive=*/ true);
        tl.debug(`------ Adding files: ${filePaths}`);

        for (let i = 0; i < filePaths.length; i++) {
            let filePath = filePaths[i];

            let relativePath: string = null;
            if (symbolsRoot) {
                relativePath = path.relative(symbolsRoot, filePath);
            } else {
                // If symbol paths do not have anything common, 
                // e.g "/a/b/c" and "/x/y/z", or "C:/u/v/w" and "D:/u/v/w",
                // let's use "a/b/c" and "x/y/z", or "C/u/v/w" and "D/u/v/w"
                // as relative paths in the archive.
                relativePath = filePath.replace(/^\/+/,"").replace(":", "");
            }

            tl.debug(`...... zip-entry: ${relativePath}`);
            zip.file(relativePath, fs.createReadStream(filePath), { compression: 'DEFLATE' });
        }
    });

    let currentFile = null;
    let zipStream = zip.generateNodeStream({
        base64: false,
        compression: 'DEFLATE',
        type: 'nodebuffer',
        streamFiles: true
    }, function (chunk) {
        if (chunk.currentFile != currentFile) {
            currentFile = chunk.currentFile;
            tl.debug(chunk.currentFile ? "Deflating file: " + chunk.currentFile + ", progress %" + chunk.percent : "done");
        }
    });

    return zipStream;
}

export function createZipFile(zipStream: NodeJS.ReadableStream, filename: string): Q.Promise<string> {
    var defer = Q.defer<string>();

    zipStream.pipe(fs.createWriteStream(filename))
        .on('finish', function () {
            defer.resolve();
        })
        .on('error', function (err) {
            defer.reject(tl.loc("FailedToCreateFile", filename, err));
        });

    return defer.promise;
}

export function resolveSinglePath(pattern: string, continueOnError?: boolean, packParentFolder?: boolean): string {
    tl.debug("---- Resolving a single path");

    let matches = resolvePaths(pattern, continueOnError, packParentFolder);

    if (matches && matches.length > 0) {
        if (matches.length != 1) {
            if (continueOnError) {
                tl.warning(tl.loc("FoundMultipleFiles", pattern));
            } else {
                throw new Error(tl.loc("FoundMultipleFiles", pattern));
            }
        }

        return matches[0];
    }

    return null;
}

export function resolvePaths(pattern: string, continueOnError?: boolean, packParentFolder?: boolean): string[] {
    tl.debug("------- Resolving multiple paths");
    tl.debug("....... path pattern: " + (pattern || ""));

    if (pattern) {
        let matches = tl.findMatch(null, pattern);

        if (!matches || matches.length === 0) {
            if (continueOnError) {
                tl.warning(tl.loc("CannotFindAnyFile", pattern));
                return null;
            } else {
                throw new Error(tl.loc("CannotFindAnyFile", pattern));
            }
        }

        let selectedPaths = matches.map(v => packParentFolder ? path.dirname(v) : v);
        tl.debug("....... selectedPaths: " + selectedPaths);

        let uniquePaths = removeDuplicates(selectedPaths);
        tl.debug("....... uniquePaths:   " + uniquePaths);

        return uniquePaths;
    }

    return null;
}

export function removeDuplicates(list: string[]): string[] {

    interface IStringDictionary { [name: string]: number };
    let unique: IStringDictionary = {};

    list.forEach(s => {
        unique[s] = 0;
    });

    return Object.keys(unique);
}

export function findCommonParent(list: string[]): string {
    tl.debug("---- Detecting the common parent of all symbols paths to define the archive's internal folder structure.")

    function cutTail(list: string[], n: number) {
        while (n-- > 0) {
            list.pop();
        }
    }

    if (!list) {
        return null;
    }

    let commonSegments: string[] = [];
    let parentPath: string = null;

    list.forEach((nextPath, idx) => {
        tl.debug(`------ next path[${idx}]\t ${nextPath}`);

        if (idx === 0) {
            // Take the first path as the common parent candidate
            commonSegments = nextPath.split("/");
        } else if (commonSegments.length === 0) { 
            // We've already detected that the paths do not have a common parent.
            // No sense to check the rest of paths.
            return null; 
        } else {
            let pathSegmants: string[] = nextPath.split("/");

            // If the current path contains less segments than the common path calculated so far,
            // the trailing segmants in the latter cannot be a part of the resulting common path.
            cutTail(commonSegments, commonSegments.length - pathSegmants.length);

            for (let i = 0; i < pathSegmants.length; i++) {
                if (pathSegmants[i] !== commonSegments[i]) {
                    // Segments i, i+1, etc. cannot be a part of the resulting common path.
                    cutTail(commonSegments, commonSegments.length - i);
                    break;
                }
            }
        }

        parentPath = commonSegments.join("/");
        tl.debug(`...... parent path  \t ${parentPath}`);
    })

    return parentPath;
}


