import * as fs from 'fs';
import * as fse from 'fs-extra';
import * as path from 'path';
import { spawnSync } from 'child_process';
import { Logger, LogLevel } from './logger';

export class Paths {

    private log = new Logger('Paths');

    public cwd(): string {
        return process.cwd();
    }

    public samePath(p1: string, p2: string): boolean {

        if (!p1 || !p2) return false;

        const p1Norm = p1
            .replace(/\\/g, '/')
            .toLowerCase()
            .split('/');
        const p2Norm = p2
            .replace(/\\/g, '/')
            .toLowerCase()
            .split('/');

        return (
            (p1Norm.length === p2Norm.length)
            && p1Norm.every((val, i) => val === p2Norm[i])
        );
    }

    public async findFilesInDir(dir: string, filter?: RegExp): Promise<string[]> {
        const results: string[] = [];

        if (!fs.existsSync(dir)) {
            return results;
        }

        const files = await fse.readdir(dir);
        for (const file of files) {
            const filename = path.join(dir, file);
            const stat = await fse.lstat(filename);
            if (stat.isDirectory()) {
                results.push(...(await this.findFilesInDir(filename, filter)));
            } else if (!filter || filter.test(filename)) {
                results.push(filename);
            }
        }
        return results;
    }

    // https://github.com/vercel/pkg/issues/420
    public copyFromPkg(src: string, dest: string): void {
        const stat = fs.lstatSync(src);
        if (stat.isDirectory()) {
            const files = fs.readdirSync(src);
            for (const file of files) {
                const fullPath = path.join(src, file);
                const fullDest = path.join(dest, file);
                this.copyFromPkg(fullPath, fullDest);
            }
        } else {
            fse.ensureDirSync(path.dirname(dest));
            const buff = fs.readFileSync(src);
            fs.writeFileSync(dest, buff);
        }
    }

    public removeLink(target: string): boolean {
        // cmd //c rmdir "$__TARGET_DIR"
        return (spawnSync(
            'cmd',
            [
                '/c',
                'rmdir',
                '/S',
                '/Q',
                target,
            ],
        ).status === 0);
    }

    public linkDirsFromTo(source: string, target: string): boolean {
        // cmd //c mklink //j "$__TARGET_DIR" "$__SOURCE_DIR"
        try {
            if (fs.existsSync(target)) {
                if (!this.removeLink(target)) {
                    this.log.log(LogLevel.ERROR, 'Could not remove link before creating new one');
                    return false;
                }
            }
            return (spawnSync(
                'cmd',
                [
                    '/c',
                    'mklink',
                    '/j',
                    target,
                    source,
                ],
            ).status === 0);
        } catch (e) {
            this.log.log(LogLevel.ERROR, `Error linking ${source} to ${target}`, e);
            return false;
        }
    }

    public async copyDirFromTo(source: string, target: string): Promise<boolean> {
        try {
            if (fs.existsSync(target)) {
                if (!this.removeLink(target)) {
                    this.log.log(LogLevel.ERROR, 'Could not remove dir before creating new one');
                    return false;
                }
            }

            await fse.ensureDir(target);
            await fse.copy(source, target);

            return true;
        } catch (e) {
            this.log.log(LogLevel.ERROR, `Error copying ${source} to ${target}`, e);
            return false;
        }
    }

}