let path = require('path')
let fs = require('fs');
let os = require('os');
const shell = require('electron').shell;
let fileExtensionToImage: object;

export enum ConfigPaths {
    ApplicationConfigName = "dashboard.json",
    UserConfigName = "user.json",
    FileExtensionsName = "file-extensions.json"
}

export module Configurator {

    export function getFileExtensionToImageMap(): Object {
        if (!fileExtensionToImage) {
            fileExtensionToImage = this.loadAppConfig(ConfigPaths.FileExtensionsName);
        }
        return fileExtensionToImage;
    }

    export function loadAppConfig(fileName: string): object {
        let filePath = FileUtils.getPathToConfig(fileName);
        let fileBuffer = fs.readFileSync(filePath);
        let content = fileBuffer.toString('utf8')
        return JSON.parse(content)
    }

    export function loadUserConfig(fileName: string): object {
        let userConfigPath = FileUtils.getPathToUserDir(fileName);
        if (!fs.existsSync(userConfigPath)) {
            return buildDefaultConfig();
        } else {
            let content = fs.readFileSync(userConfigPath);
            return JSON.parse(content);
        }
    }

    export function saveUserConfig(fileName: string, obj: any) {
        let userConfigPath = FileUtils.getPathToUserDir(fileName);
        let content = JSON.stringify(obj);
        fs.writeFile(userConfigPath, content, (err) => {
            if (err) {
                console.log(err);
                throw err;
            }
        });
    }

    function buildDefaultConfig(): object {
        let userConfig = {};
        userConfig['theme'] = "theme-dark";
        saveUserConfig(ConfigPaths.UserConfigName, userConfig)
        return userConfig
    }

}

export module FileUtils {
    export function getPathToView(templateName: string): string {
        return path.join(getPathToAssets(), "views", templateName + ".mustache")
    }

    export function getPathToPage(pageName: string): string {
        return path.join(getPathToAssets(), "pages", pageName)
    }

    export function getPathToImage(imageName: string): string {
        return path.join(getPathToAssets(), "images", imageName)
    }

    export function getPathToDocument(documentName: string): string {
        return path.join(getPathToAssets(), "documents", documentName)
    }

    export function getPathToConfig(documentName: string): string {
        return path.join(getPathToAssets(), "conf", documentName)
    }

    export function getPathToAssets(): string {
        return path.join(__dirname, "..", "assets");
    }

    export function getFileExtension(fileName: string): string {
        return path.extname(fileName);
    }

    export function getPathToUserDir(fileName: string): string {
        let dirPath = path.join(__dirname, "..", "user");
        let filePath = path.join(dirPath, fileName);
        touchDir(dirPath);
        return filePath;
    }

    export function touchDir(dirPath: string) {
        if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath);
        }
    }

    export function showFileInDir(resourcePath: string) {
        let localFile = copyFileToUserDocuments(resourcePath);
        shell.showItemInFolder(localFile);
    }

    /**
     * @param resourcePath pathlike of system resource
     * @private
     * @returns pathlike of local user resource
     */
    function copyFileToUserDocuments(resourcePath: string): string {
        let fileName = path.basename(resourcePath);
        let userFilePath = getPathToUserDocumentDir(fileName);
        if(!fs.existsSync(userFilePath)){
            fs.copyFileSync(resourcePath, userFilePath);
        }
        return userFilePath;
    }

    function getPathToUserDocumentDir(fileName: string): string {
        let homeDir = os.homedir();
        let parent =  path.join(homeDir, "Documents", "Employee Resources");
        if(!fs.existsSync(parent)){
            fs.mkdirSync(parent);
        }
        return path.join(parent, fileName);
    }
}


export class DocumentDirectory {
    /**
     * you *must* provide an absolute file path here
     */
    constructor(filePath: string) {
        this.root = new DirectoryNode(filePath, null);
    }

    root: DirectoryNode;

    getCards(): Map<string, object[]> {
        return DocumentDirectory.walkCards(this.root);
    }

    private static walkCards(d: DirectoryNode): Map<string, object[]> {
        let cardsByCategory = new Map<string, object[]>();
        for (let child of d.children) {
            if (child instanceof DirectoryNode) {
                let dir = child as DirectoryNode;
                if (dir.containsDirectory()) {
                    let childDirectories = dir.getDirectories();
                    for (let dir of childDirectories) {
                        let subCards = DocumentDirectory.walkCards(dir);
                        this.mergeMaps(cardsByCategory, subCards);
                    }
                }
                let sCards = dir.getDocuments();
                let category = dir.getCategory();
                for (let sCard of sCards) {
                    let cards = cardsByCategory.get(category);
                    if (cards === undefined || cards === null) {
                        cards = [];
                    }
                    cards.push(sCard.toCard());
                    cardsByCategory.set(category, cards);
                }
            } else {
                let category = (child.parent as DirectoryNode).getCategory();
                let cards = cardsByCategory.get(category);
                if (cards === undefined || cards === null) {
                    cards = [];
                }
                cards.push(child.toCard());
                cardsByCategory.set(category, cards);
            }
        }

        return cardsByCategory;
    }

    private static mergeMaps(a: Map<string, Object[]>, b: Map<string, Object[]>) {
        let keys = Object.keys(b);
        for (let key of keys) {
            let sa = a.get(key)
            if (sa === undefined || sa === null) {
                sa = [];
            }
            let sb = b.get(key)
            if (sb === undefined) {
                sb = [];
            }
            sa.push(...sb);
            a[key] = sa;
        }
    }

}

export class FileNode {

    filePath: string;
    parent: FileNode;

    constructor(filePath: string, parent: FileNode) {
        this.filePath = filePath;
        this.parent = parent;
    }

    static loadAltProps(path: string): object {
        let props = {};
        let jsonPath = path + ".json"
        if (fs.existsSync(jsonPath)) {
            let raw = fs.readFileSync(jsonPath);
            props = JSON.parse(raw.toString());
        }
        return props
    }

    private static getImagePathFromDocumentName(name: string): string {
        let ext = FileUtils.getFileExtension(name);
        return Configurator.getFileExtensionToImageMap()[ext];
    }

    open() {
        shell.openItem(this.filePath);
    }

    show() {
        shell.showItemInFolder(this.filePath);
    }

    toCard(): object {
        let altProps = FileNode.loadAltProps(this.filePath);
        let imageName = FileNode.getImagePathFromDocumentName(this.filePath);
        let cardObj = {
            "title": this.getTitle(),
            "description": "",
            "imagePath": imageName,
            "urlText": this.filePath,
            "altText": "",
            "fileCard": true,
            "open": this.open
        }

        let altKeys = Object.keys(altProps);
        for (let key of altKeys) {
            cardObj[key] = altProps[key]
        }

        return cardObj
    }

    getTitle(): string {
        let name = path.basename(this.filePath);
        let ext = path.extname(name);
        let cName = name.replace(ext, "");
        cName = cName.replace(/[\-._]/ig, " ")
        return `${cName} (${ext.substr(1).toUpperCase()})`
    }

    isDescriptor() {

    }

    static compare(a: FileNode, b: FileNode): number {
        return a.filePath.localeCompare(b.filePath);
    }
}

export class DirectoryNode extends FileNode {
    children: FileNode[] = [];

    constructor(filePath: string, parent: FileNode) {
        super(filePath, parent);
        if (fs.existsSync(filePath)) {
            let stats = fs.lstatSync(filePath);
            if (stats.isDirectory()) {
                let contents = fs.readdirSync(filePath);
                for (let fileName of contents) {
                    if (path.extname(fileName) === ".json") continue;
                    let childPath = path.join(filePath, fileName);
                    let childStats = fs.lstatSync(childPath);
                    if (childStats.isDirectory()) {
                        this.children.push(new DirectoryNode(childPath, this));
                    } else {
                        this.children.push(new DocumentNode(childPath, this));
                    }
                }
            } else {
                throw new DirectoryIsActuallyFileError(filePath);
            }
        } else {
            throw new DirectoryDoesNotExistError(filePath);
        }
    }

    getDocuments(): DocumentNode[] {
        return this.children.filter((x) => {
            return x instanceof DocumentNode
        }) as DocumentNode[]
    }

    getDirectories(): DirectoryNode[] {
        return this.children.filter((x) => {
            return x instanceof DirectoryNode
        }) as DirectoryNode[]
    }

    containsDirectory(): Boolean {
        for (let child of this.children) {
            if (child instanceof DirectoryNode) return true;
        }
        return false
    }

    getCategory(): string {
        let rawName = path.basename(this.filePath);
        let parts = rawName.split("-");
        for (let i = 0, l = parts.length; i < l; i++) {
            let part = parts[i].split('');
            part[0] = part[0].toUpperCase();
            parts[i] = part.join('')
        }
        return parts.join(" ")
    }
}


export class DocumentNode extends FileNode {
    constructor(filePath: string, parent: FileNode) {
        super(filePath, parent);
    }

}

export class DirectoryIsActuallyFileError extends Error {
    constructor(filePath: string) {
        super(`attempted to scan non-directory: ${filePath}`);
    }
}

export class DirectoryDoesNotExistError extends Error {
    constructor(filePath: string) {
        super(`attempted to load document directory for non-existing directory: ${filePath}`);
    }
}

