const { task, src, dest, series } = require('gulp');
const rename = require('gulp-rename');
const through = require('through2');
const fs = require('fs');
const argv = require('yargs').argv;
const request = require('request');
const del = require('del');
const readlineSync = require('readline-sync');

// read input parameter
const ci = argv.ci;
const personalEnvironmentName = argv.env;
const resetFlag = argv.resetFlag;
const projectRefDirect = argv.projectRefDirect;
const isUpdateAssetsTnxshared = argv.isUpdateAssetsTnxshared;
const projectName = argv.projectName;
const publishType = argv.publishType;

if (!personalEnvironmentName && !ci && !resetFlag) {
    console.error('Generated đã bị huỷ, vui lòng chạy generate theo 1 profile cá nhân của bạn, ví dụ npm run generate-all-namnh (namnh là profile cá nhân)');
    console.error('------------------------------------------------------------------------------------------------------------------------------------');
    return;
}

const rootProjectPath = '../../../';

const pathAngularSchema = rootProjectPath + 'angular-schema.json';
const pathGitIgnoreFile = rootProjectPath + '/projects/.gitignore';
const pathAngularSchemaTemplate = 'angular-schema-template.json';
let contentPathShared = `../../commonworkspacev5/App/shared`;
const packageJsonTemplate = 'package.json.tmp';

let pathShared = rootProjectPath + contentPathShared;
if (ci) {
    pathShared = rootProjectPath + `commonworkspacev5/App/shared`;
    contentPathShared = `commonworkspacev5/App/shared`;
}
const pathSharedAssets = pathShared + '/shared-assets';
const pathSharedEnvironment = pathShared + '/environments';
const pathBuildConfig = pathShared + '/build-config';
const pathtTsConfig = pathShared;
const pathPackageJson = rootProjectPath + 'package.json';
const tnxSharedAssetPath = `${rootProjectPath}../../shared/AppSharedV5/projects/tnx-shared/src/lib/assets`;

let angularSchemaObj = {};
let angularSchemaTemplateObj = {};

String.prototype.replaceAll = function (target, replacement) {
    return this.split(target).join(replacement);
};

task('generateEnvironmentJsonFile', async function () {
    const listEnvironment = angularSchemaObj.environments;
    // generate application.json file
    generateApplicationJsonFile();

    for (const [env] of Object.entries(listEnvironment)) {
        if (env != personalEnvironmentName) continue;
        generateEnvironmentJsonFile(env);
    }
});

task('generateEnvironmentTsFile', async function () {
    const listEnvironment = angularSchemaObj.environments;
    for (const [env] of Object.entries(listEnvironment)) {
        if (env != personalEnvironmentName) continue;
        if (env == 'prod') continue;
        generateEnvironmentTsFile(env);
    }
});

task('generateDockerBuildConfig', async function () {
    Object.keys(angularSchemaObj.projectList).filter(x => angularSchemaObj.projectList[x].projectType == 'application').forEach(project => {
        const prj = angularSchemaObj.projectList[project];
        let baseHref = `/${project}/`;
        if (prj.deploy) {
            if (prj.deploy.baseHref) {
                baseHref = prj.deploy.baseHref;
            }
        }

        const replaceKey = { projectName: project, baseHref: baseHref };
        copyDockerBuild('nginx.conf.template', project, replaceKey);
        copyDockerBuild('docker-entrypoint.sh', project, replaceKey);
        copyDockerBuild('Dockerfile', project, replaceKey);
        copyDockerBuild('.dockerignore', project, {});
    });
});

task('updateTsConfigJson', async function () {
    copyTsConfig();
});

task('generateAngularJson', function () {
    return modifyFile(pathAngularSchema, rootProjectPath, (content) => {
        const parsed = JSON.parse(content);
        angularSchemaObj = getAngularSchemaData(parsed);
        // transform json into desired shape
        const transformed = transformAngularJson(parsed);
        // make string from javascript obj
        return JSON.stringify(transformed, null, 2);
    }, 'angular.json');
});

task('loadAngularSchemaTemplate', function () {
    return readFile(pathAngularSchemaTemplate, (content) => {
        angularSchemaTemplateObj = cloneObject(content);
        return true;
    });
});

task('loadAngularSchema', function () {
    return readFile(pathAngularSchema, (content) => {
        angularSchemaObj = cloneObject(content);
        return true;
    });
});


task('updatePackageJson', function () {
    return modifyFile(pathPackageJson, rootProjectPath, (content) => {
        const parsed = JSON.parse(content);
        // transform json into desired shape
        const transformed = transformPackageJson(angularSchemaObj, angularSchemaTemplateObj, parsed);
        // make string from javascript obj
        return JSON.stringify(transformed, null, 2);
    });
});

task('confirmCopyProjectToRefDirect', function (done) {
    if (readlineSync.keyInYN(`Ban co chac chan muon copy "${projectRefDirect}" project sang de ref truc tiep? Luu y neu da ton tai san project "${projectRefDirect}" ben nay thi co nguy co bi mat code. Neu ban da chac chan thi nhan Y`)) {
        return done();
    }
    console.log('huy copy');
    process.exit(1);
});

task('copyProjectToRefDirect', function () {
    const projectList = getProjectList();
    if (!projectList) {
        console.error(`khong ton tai project ${projectRefDirect} trong projectList tai angular-schema.json`);
    }
    const projectInfo = projectList[projectRefDirect];
    if (!projectInfo) {
        console.error(`khong ton tai project ${projectRefDirect} trong projectList tai angular-schema.json`);
    }
    addProjectToGitIgnore(projectRefDirect);
    return src(`${rootProjectPath}${projectInfo.refPath}/**/*`).pipe(dest(`${rootProjectPath}projects/${projectRefDirect}`));
});

task('confirmUpdateProjectRefDirectToOriginalProject', function (done) {
    if (readlineSync.keyInYN(`Ban co chac chan muon update project "${projectRefDirect}" dang ref direct ve thu muc goc? Luu y viec copy nay se overwrite toan bo code o thu muc goc cua project ${projectRefDirect}. Nhan Y de dong y.`)) {
        return done();
    }
    console.log('huy update');
    process.exit(1);
});

task('updateProjectRefDirectToOriginalProject', function () {
    const projectList = getProjectList();
    if (!projectList) {
        console.error(`khong ton tai project ${projectRefDirect} trong projectList tai angular-schema.json`);
    }
    const projectInfo = projectList[projectRefDirect];
    if (!projectInfo) {
        console.error(`khong ton tai project ${projectRefDirect} trong projectList tai angular-schema.json`);
    }
    return src(`${rootProjectPath}projects/${projectRefDirect}/**/*`).pipe(dest(`${rootProjectPath}${projectInfo.refPath}`));
});

task('reset', function (cb) {
    // reset script package.json
    resetPackageJson();
    // delete tsconfig.json
    removeTsConfigJson();
    // delete angular.json
    removeAngularJson();
    cb();
});

task('onInitDone', function (cb) {
    if (personalEnvironmentName == 'init' && !isUpdateAssetsTnxshared) {
        console.log('Tiep theo can chay them script npm run generate-all-<ten profile cau ban>');
    }
    cb();
});

task('init', series('loadAngularSchemaTemplate', 'generateAngularJson', 'updatePackageJson', 'onInitDone'));

task('confirmUpdateAssetTnxShared', function (done) {
    if (readlineSync.keyInYN(`Ban co chac chan muon update thu muc assets tai "node_modules/tnx-shared/assets" ve project goc? Luu y viec copy nay se overwrite toan bo code o thu muc goc. Nhan Y de dong y.`)) {
        return done();
    }
    console.log('huy update');
    process.exit(1);
});

task('updateAssetTnxShared', function (done) {
    src('./**/*')
        .pipe(dest(tnxSharedAssetPath));
    done();
});

task('updateNextVersionService', function (done) {
    processNextVersion(projectName, publishType, done);
});

task('updateDepdendServiceToLatest', function (done) {
    const serviceName = argv.serviceName;
    try {
        getCurrentVersion({}, serviceName, (version) => {
            if (version) {
                console.log(`latest version of service ${serviceName} is ${version.versionString}`);
                modifyFile(pathPackageJson, rootProjectPath, (content) => {
                    const json = JSON.parse(content);
                    json.dependencies[serviceName] = `~${version.versionString}`;
                    return JSON.stringify(json);
                });
            }
            else {
                console.log(`cannot get version or does not existed of service ${serviceName}`);
            }
            done();
        });
    }
    catch (e) {
        console.log(`error on update service to latest ${serviceName}`, e);
        done();
    }
});

function resetPackageJson() {
    modifyFileAsync(packageJsonTemplate, rootProjectPath, (content) => {
        return new Promise((resolve) => {
            const json = JSON.parse(content);
            getCurrentVersion({}, 'tnx-shared', (data) => {
                json.dependencies['tnx-shared'] = `~${data.versionString}`;
                resolve(JSON.stringify(json));
            });
        });
    }, 'package.json');
}

function removeTsConfigJson() {
    del([`${rootProjectPath}tsconfig.json`], { force: true });
}

function removeAngularJson() {
    del([`${rootProjectPath}angular.json`], { force: true });
}

/**
 * Sửa và copy file sang thư mục mới
* @param {Sửa nội dung từ file} sourceFile
 * @param {Thư mục chứa file có nội dung mới} targetDir
 * @param {Hàm sửa nội dung file} procressFileContent
 * @param {Tên file mới (nếu không khai báo thì lấy giống tên sourceFile)} targetFileName
 * @returns promise
 */
function modifyFile(sourceFile, targetDir, procressFileContent, targetFileName) {
    let t = src(sourceFile)
        .pipe(
            through.obj((file, enc, cb) => {
                // get content of json file
                const rawContent = file.contents.toString();
                if (procressFileContent) {
                    const content = procressFileContent(rawContent);
                    if (content) {
                        file.contents = Buffer.from(content);
                    }
                    else {
                        console.warn(`content file ${sourceFile} after process is null`);
                    }
                }
                cb(null, file);
            })
        );
    if (targetFileName) {
        // đổi tên file sau khi sửa nội dung
        t = t.pipe(rename(targetFileName));
    }
    t = t.pipe(dest(targetDir));
    return t;
}

function modifyFileAsync(sourceFile, targetDir, procressFileContentAsync, targetFileName) {
    let t = src(sourceFile)
        .pipe(
            through.obj(async (file, enc, cb) => {
                // get content of json file
                const rawContent = file.contents.toString();
                if (procressFileContentAsync) {
                    const content = await procressFileContentAsync(rawContent);
                    if (content) {
                        file.contents = Buffer.from(content);
                    }
                    else {
                        console.warn(`content file ${sourceFile} after process is null`);
                    }
                }
                cb(null, file);
            })
        );
    if (targetFileName) {
        // đổi tên file sau khi sửa nội dung
        t = t.pipe(rename(targetFileName));
    }
    t = t.pipe(dest(targetDir));
    return t;
}


function readFile(sourceFile, readContent) {
    return src(sourceFile).pipe(
        through.obj((file, enc, cb) => {
            // get content of json file
            const rawContent = file.contents.toString();
            readContent(rawContent);
            cb(null, file);
        })
    );
}

function generateEnvironmentTsFile(environment) {
    return modifyFile(`${pathSharedEnvironment}/environment.template.ts`, `${pathSharedEnvironment}/${angularSchemaObj.scopeName}`,
        (content) => {
            return transformEnvironmentTsAndJsonFile(environment, content);
        }, `environment.${environment}.ts`);
}

function transformEnvironmentTsAndJsonFile(envName, input) {
    return input.replaceAll('{{environmentName}}', 'application,' + envName).replaceAll('{{scopeName}}', angularSchemaObj.scopeName);
}

function generateApplicationJsonFile() {
    if (!fs.existsSync(`${pathSharedAssets}/environments/${angularSchemaObj.scopeName}/application.json`)) {
        return src(`${pathSharedAssets}/environments/application-template.json`)
            .pipe(rename(`application.json`))
            .pipe(dest(`${pathSharedAssets}/environments/${angularSchemaObj.scopeName}`));
    }

    return true;
}

function generateEnvironmentJsonFile(environment) {
    fs.access(`${pathSharedAssets}/environments/${angularSchemaObj.scopeName}/${environment}.json`, (err) => {
        if (err) {
            return modifyFile(
                `${pathSharedAssets}/environments/template.json`,
                `${pathSharedAssets}/environments/${angularSchemaObj.scopeName}`,
                (content) => {
                    return transformEnvironmentTsAndJsonFile(environment, content);
                },
                `${environment}.json`
            );
        }
    });
}

function copyTsConfig() {
    return modifyFile(`${pathtTsConfig}/tsconfig.json`, rootProjectPath, (content) => {
        // parse raw json into javscript object
        const parsed = JSON.parse(content);
        // transform json into desired shape
        const transformed = transformTsConfigJson(parsed);
        // make string from javascript obj
        return JSON.stringify(transformed, null, 2);
    });
}

function copyDockerBuild(fileName, project, replaceList) {
    return modifyFile(`${pathBuildConfig}/template/${fileName}`, `${rootProjectPath}./build-config/${project}`,
        (content) => {
            let op = content;
            for (const key in replaceList) {
                op = op.replaceAll(`{{${key}}}`, replaceList[key]);
            }
            return op;
        });
}

function transformTsConfigJson(input) {
    const paths = input.compilerOptions.paths;
    const projectList = getProjectList();

    Object.keys(projectList).filter(x => projectList[x].projectType == 'library').forEach(project => {
        buildTsConfigPath(paths, project);
    });

    delete paths[`${getRefPrefix()}template`];
    delete paths[`${getRefPrefix()}template/*`];
    delete paths[`${getRefPrefix()}template-direct`];
    delete paths[`${getRefPrefix()}template-direct/*`];
    delete paths[`${getRefPrefix()}template-package`];
    delete paths[`${getRefPrefix()}template-package/*`];

    return input;
}

function transformPackageJson(angularSchema, angularSchemaTemplate, input) {
    const listEnvironment = angularSchema.environments;
    const scopeName = angularSchema.scopeName;

    let scriptDependAll = 'npm run depend-tnx-shared';
    Object.keys(angularSchema.projectList).filter(x => angularSchema.projectList[x].projectType == 'library').forEach(project => {
        const prjList = getProjectList();
        const prj = prjList[project];
        if (!prj.refType) {
            prj.refType = 'package';
        }

        if (prj.projectType == 'library') {
            if (prj.refType == 'direct') {
                // generate package function only for library
                const nodeMaxOldSpaceSize = angularSchema.nodeMaxOldSpaceSize ? angularSchema.nodeMaxOldSpaceSize : 3072;
                input.scripts[`build-${project}`] = angularSchemaTemplate.scripts['build-service-template']
                    .replaceAll('{{projectName}}', project)
                    .replaceAll('{{nodeMaxOldSpaceSize}}', nodeMaxOldSpaceSize);
                input.scripts[`package-${project}`] = angularSchemaTemplate.scripts[`package-template`]
                    .replaceAll('{{projectName}}', project);
                input.scripts[`package-major-${project}`] = angularSchemaTemplate.scripts[`package-major-template`]
                    .replaceAll('{{projectName}}', project);
                input.scripts[`package-minor-${project}`] = angularSchemaTemplate.scripts[`package-minor-template`]
                    .replaceAll('{{projectName}}', project);
                input.scripts[`update-version-${project}`] = angularSchemaTemplate.scripts['update-version-template']
                    .replaceAll('{{projectName}}', project);
                input.scripts[`update-major-version-${project}`] = angularSchemaTemplate.scripts['update-major-version-template']
                    .replaceAll('{{projectName}}', project);
                input.scripts[`update-minor-version-${project}`] = angularSchemaTemplate.scripts['update-minor-version-template']
                    .replaceAll('{{projectName}}', project);

                if (prj.refPath) {
                    input.scripts[`ref-${project}`] = angularSchemaTemplate.scripts['ref-template']
                        .replaceAll('{{projectName}}', project)
                        .replaceAll('{{environmentName}}', personalEnvironmentName);
                    input.scripts[`ref-update-${project}`] = angularSchemaTemplate.scripts['ref-update-template']
                        .replaceAll('{{projectName}}', project)
                        .replaceAll('{{environmentName}}', personalEnvironmentName);
                }
            }
            else {
                input.scripts[`depend-${project}`] = angularSchemaTemplate.scripts[`depend-template`]
                    .replaceAll('{{serviceName}}', project)
                    .replaceAll('tnx-tnx-', 'tnx-');
                scriptDependAll += ` && npm run depend-${project}`;
            }
        }
    });

    // default install latest version tnx-shared
    // input.scripts['depend-tnx-shared'] = angularSchemaTemplate.scripts[`depend-template`]
    //     .replaceAll('{{serviceName}}', 'shared');
    input.scripts['depend-all'] = scriptDependAll;
    input.scripts['prebuild'] = angularSchemaTemplate.scripts[`prebuild-template`];
    input.scripts['update-assets-tnx-shared'] = angularSchemaTemplate.scripts['update-assets-tnx-shared-template'];
    input.scripts['publish-shared'] = angularSchemaTemplate.scripts['publish-shared'];
    // additional dependencies and devDependencies
    if (angularSchema.dependencies) {
        Object.keys(angularSchema.dependencies).forEach(item => {
            input.dependencies[item] = angularSchema.dependencies[item];
        });
    }

    if (angularSchema.devDependencies) {
        Object.keys(angularSchema.devDependencies).forEach(item => {
            input.devDependencies[item] = angularSchema.devDependencies[item];
        });
    }

    for (const [env] of Object.entries(listEnvironment)) {
        if (personalEnvironmentName != 'init' && env != personalEnvironmentName) continue;
        input.scripts[`generate-all-${env}`] = angularSchemaTemplate.scripts[`generate-all-template`]
            .replaceAll('{{environmentName}}', env);
    }

    Object.keys(angularSchema.projectList).filter(x => angularSchema.projectList[x].projectType == 'application').forEach(project => {
        const prj = angularSchema.projectList[project];
        const port = prj.port;
        const nodeMaxOldSpaceSize = angularSchema.nodeMaxOldSpaceSize ? angularSchema.nodeMaxOldSpaceSize : 3072;

        for (const [env] of Object.entries(listEnvironment)) {
            if (env != personalEnvironmentName) continue;
            const additionalParams = '';

            input.scripts[`start-${project}-${env}`] = angularSchemaTemplate.scripts['start-template']
                .replaceAll('{{projectName}}', project)
                .replaceAll('{{environmentName}}', env)
                .replaceAll('{{port}}', port)
                .replaceAll('{{additionalParams}}', additionalParams)
                .replaceAll('{{nodeMaxOldSpaceSize}}', nodeMaxOldSpaceSize);
            input.scripts[`start-quick-${project}-${env}`] = angularSchemaTemplate.scripts['start-quick-template']
                .replaceAll('{{projectName}}', project)
                .replaceAll('{{environmentName}}', env)
                .replaceAll('{{port}}', port)
                .replaceAll('{{additionalParams}}', additionalParams)
                .replaceAll('{{nodeMaxOldSpaceSize}}', nodeMaxOldSpaceSize);
        }

        let baseHref = `/${project}/`;
        if (prj.deploy) {
            if (prj.deploy.baseHref) {
                baseHref = prj.deploy.baseHref;
            }
        }

        input.scripts[`deploy-${project}`] = angularSchemaTemplate.scripts['deploy-template']
            .replaceAll('{{projectName}}', project)
            .replaceAll('{{clientVersion}}', angularSchema.projectList[project].clientVersion)
            .replaceAll('{{scopeName}}', scopeName)
            .replaceAll('{{nodeMaxOldSpaceSize}}', nodeMaxOldSpaceSize)
            .replaceAll('{{baseHref}}', baseHref);
        input.scripts[`format-${project}`] = angularSchemaTemplate.scripts['format-template']
            .replaceAll('{{projectName}}', project);
        input.scripts[`build-${project}-ci`] = angularSchemaTemplate.scripts['build-ci-template']
            .replaceAll('{{projectName}}', project)
            .replaceAll('{{nodeMaxOldSpaceSize}}', nodeMaxOldSpaceSize);
    });

    // add reset all generation
    input.scripts[`reset`] = angularSchemaTemplate.scripts['reset-template'];
    return input;
}

function transformAngularJson(input) {
    // fix angular.json version = 1
    input['version'] = 1;
    input['$schema'] = input.schema;
    delete input.schema;
    const listEnvironment = input.environments;

    Object.keys(input.projectList).forEach(project => {
        const projectInfo = input.projectList[project];
        const projectType = projectInfo.projectType;
        if (!projectType) {
            console.error(`Project ${project} cần được định nghĩa projectType là application hoặc library`);
            return false;
        }
        const templateStr = `template-${projectType}`;
        // const template = input.projects[templateStr];
        const template = angularSchemaTemplateObj.projects[templateStr];
        const clonedTemplate = cloneObject(template);
        const projectPort = projectInfo.port;

        replaceKey(clonedTemplate, '{{projectName}}', project);

        if (projectType == 'application') {
            const build = clonedTemplate.architect.build.configurations;
            const buildEnvironmentTemplate = build.environmentTemplate;
            const serve = clonedTemplate.architect.serve;
            const serveEnvironmentTemplate = serve.configurations.environmentTemplate;

            for (const [env] of Object.entries(listEnvironment)) {
                if (env != personalEnvironmentName) continue;
                const clonedBuildEnvironmentTemplate = cloneObject(buildEnvironmentTemplate);
                const clonedServeEnvironmentTemplate = cloneObject(serveEnvironmentTemplate);
                clonedBuildEnvironmentTemplate.fileReplacements[0].with
                    = clonedBuildEnvironmentTemplate.fileReplacements[0].with
                        .replace('{{environmentName}}', env)
                        .replace('{{sharedDirectory}}', contentPathShared)
                        .replace('{{scopeName}}', angularSchemaObj.scopeName);
                build[env] = clonedBuildEnvironmentTemplate;
                clonedServeEnvironmentTemplate.browserTarget = clonedServeEnvironmentTemplate.browserTarget.replace('{{environmentName}}', env);
                clonedServeEnvironmentTemplate.port = +projectPort;
                // hmr
                clonedServeEnvironmentTemplate.hmr = false; // fix để không bao giờ dùng hmr
                if (clonedServeEnvironmentTemplate.hmr == null) {
                    clonedServeEnvironmentTemplate.hmr = true;
                }
                // open or not
                clonedServeEnvironmentTemplate.open = true; // fix để luôn luôn mở trình duyệt khi debug
                if (clonedServeEnvironmentTemplate.open == null) {
                    clonedServeEnvironmentTemplate.open = true;
                }
                serve.configurations[env] = clonedServeEnvironmentTemplate;
            }

            if (projectInfo.deploy && projectInfo.deploy.environmentName) {
                // xử lý environment prod build
                const prodEnvironmentName = projectInfo.deploy.environmentName;

                const clonedBuildProdEnvironmentTemplate = cloneObject(buildEnvironmentTemplate);
                build['production'].fileReplacements[0].with = clonedBuildProdEnvironmentTemplate.fileReplacements[0].with
                    .replace('{{environmentName}}', prodEnvironmentName)
                    .replace('{{sharedDirectory}}', contentPathShared)
                    .replace('{{scopeName}}', angularSchemaObj.scopeName);
            }

            clonedTemplate.architect.build.options.index = clonedTemplate.architect.build.options.index.replaceAll('{{sharedDirectory}}', contentPathShared);
            clonedTemplate.architect.build.options.assets[1].input = clonedTemplate.architect.build.options.assets[1].input.replaceAll('{{sharedDirectory}}', contentPathShared);
            const styles = clonedTemplate.architect.build.options.styles;
            const scripts = clonedTemplate.architect.build.options.scripts;

            for (let i = 0; i < styles.length; i++) {
                styles[i] = styles[i].replaceAll('{{sharedDirectory}}', contentPathShared);
            }

            for (let i = 0; i < scripts.length; i++) {
                scripts[i] = scripts[i].replaceAll('{{sharedDirectory}}', contentPathShared);
            }

            // update share directory
            const productionConfiguration = clonedTemplate.architect.build.configurations.production;
            for (let i = 0; i < productionConfiguration.fileReplacements.length; i++) {
                productionConfiguration.fileReplacements[i].with = productionConfiguration.fileReplacements[i].with.replaceAll('{{sharedDirectory}}', contentPathShared);
            }

            delete build.environmentTemplate;
            delete serve.configurations.environmentTemplate;
        }

        if (!input.projects) {
            input.projects = {};
        }

        input.projects[project] = clonedTemplate;
    });

    delete input.projectList;
    delete input.environments;
    delete input.projects['template-application'];
    delete input.projects['template-library'];
    delete input.scopeName;
    delete input.clientVersion;
    delete input.scripts;
    delete input.refPrefix;
    delete input.dependencies;
    delete input.devDependencies;

    return input;
}

function cloneObject(obj) {
    try {
        if (obj === obj.toString()) {
            return JSON.parse(obj);
        }
        else {
            return JSON.parse(JSON.stringify(obj));
        }
    }
    catch (e) {
        console.error(e);
    }
    return null;
}

function getAngularSchemaData(obj) {
    try {
        let op = JSON.parse(JSON.stringify(obj));
        if (personalEnvironmentName
            && op.environments
            && op.environments[personalEnvironmentName]
            && Object.keys(op.environments[personalEnvironmentName]).length > 0) {
            const envData = op.environments[personalEnvironmentName];
            op = mergeJSONOld(op, envData);
        }
        return op;
    }
    catch (e) {
        console.error(e);
    }
    return null;
}

function replaceKey(obj, pattern, value) {
    for (const [k, v] of Object.entries(obj)) {
        if (typeof v !== 'object') {
            if (obj[k].toString().indexOf(pattern) > -1) {
                obj[k] = obj[k].toString().replace(pattern, value);
            }
        }
        else {
            replaceKey(v, pattern, value);
        }
    }
}

function buildTsConfigPath(paths, project) {
    const prj = getProjectInfo(project);
    const prefix = getRefPrefix();
    const refType = prj.refType ? prj.refType : 'package';
    if (project == 'tnx-shared' && refType == 'direct') {
        paths[`tnx-shared`] = [`${prj.refPath}/src/lib/public-api`];
    }
    else {
        const pathAlias = `${prefix}${project}`;
        if (refType != 'package') {
            paths[`${pathAlias}/*`] = [`${prj.refPath}/src/lib/*/public-api`];
        }
    }
}

function addProjectToGitIgnore(projectName) {
    return modifyFile(pathGitIgnoreFile, `${rootProjectPath}projects`, (content) => {
        let raw = content;
        if (raw.indexOf('/' + projectName) == -1) {
            raw += '\r\n/' + projectName;
        }
        return raw;
    }, '.gitignore');
}

function getRefPrefix(projectName = null) {
    const prefix = 'tnx-'; // tạm fix luôn vì chưa thấy có trường hợp nào cần tùy biến prefix
    return prefix;
}

function getProjectInfo(projectName) {
    return angularSchemaObj.projectList[projectName];
}

function getProjectList() {
    return angularSchemaObj.projectList;
}

function mergeJSONOld(target, sourceOld) {
    const source = cloneOld(sourceOld);
    if (isObjectOld(source)) {
        for (const key in source) {
            if (isObjectOld(source[key])) {
                if (!isObjectOld(target[key])) {
                    target[key] = source[key];
                }
                else {
                    mergeJSONOld(target[key], source[key]);
                }
            }
            else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

function isObjectOld(obj) {
    if (!obj) return false;
    const s = JSON.stringify(obj);
    if (s && s.startsWith('{')) return true;
    return false;
}

function cloneOld(obj) {
    if (obj) {
        return JSON.parse(JSON.stringify(obj));
    }
    return null;
};

function processNextVersion(packageName, type, callback) {
    modifyFileAsync(`${rootProjectPath}projects/${packageName}/src/lib/package.json`, `${rootProjectPath}projects/${packageName}/src/lib`,
        async (content) => {
            return new Promise((resolve, reject) => {
                const obj = JSON.parse(content);
                getCurrentVersion(obj, packageName, (currentVersion) => {
                    if (currentVersion) {
                        obj.versionPrev = currentVersion.versionString;
                        const nextVersion = getNextVersion(currentVersion, type);

                        obj.version = nextVersion.versionString;
                        obj[`version${angularSchemaObj.clientVersion}`] = obj.version;
                        console.log(`version moi cua package ${packageName} se la ${obj.version}`);

                        resolve(JSON.stringify(obj));
                        callback();
                    }
                    else {
                        console.log(`cannot get version ${angularSchemaObj.clientVersion} or does not existed of service ${packageName}`);
                    }
                });
            });
        });
}

function getNextVersion(currentVersion, nextVersionType) {
    if (nextVersionType == 'major') {
        currentVersion.major++;
        currentVersion.minor = 0;
        currentVersion.revision = 0;
    }
    else if (nextVersionType == 'minor') {
        currentVersion.minor++;
        currentVersion.revision = 0;
    }
    else {
        currentVersion.revision++;
    }

    currentVersion.versionString = `${currentVersion.major}.${currentVersion.minor}.${currentVersion.revision}`;
    return currentVersion;
}

function getCurrentVersion(pkgObj, packageName, onGetVersion,) {
    let realPackageName;
    let packageNameWithoutPrefix;
    if (!packageName.startsWith('tnx-')) {
        realPackageName = 'tnx-' + packageName;
        packageNameWithoutPrefix = packageName;
    } else {
        realPackageName = packageName;

        if (packageName != 'tnx-shared') {
            packageNameWithoutPrefix = packageName.replace('tnx-', '');
        } else {
            packageNameWithoutPrefix = packageName;
        }
    }

    request({
        url: `https://registry.npmjs.org/${realPackageName}`
    }, function (error, response, body) {
        let versionStr;
        if (body) {
            const bodyObj = JSON.parse(body).versions;
            const pkgVersions = Object.keys(bodyObj).filter(x => x.startsWith(angularSchemaObj.projectList[packageNameWithoutPrefix].clientVersion));
            if (pkgVersions.length == 0) {
                versionStr = `${angularSchemaObj.clientVersion}.0`;
            } else {
                versionStr = pkgVersions[pkgVersions.length - 1];
            }
        }
        else {
            console.warn('cannot get latest version, get from local instead', body);
            versionStr = pkgObj.version;
        }

        if (versionStr) {
            const arr = versionStr.split('.');
            onGetVersion({
                versionString: versionStr,
                major: +arr[0],
                minor: +arr[1],
                revision: +arr[2]
            });
        }
        else {
            onGetVersion(null);
        }
    });
}

exports.generateAngularJson = task('generateAngularJson');
exports.loadAngularSchemaTemplate = task('loadAngularSchemaTemplate');
exports.updatePackageJson = task('updatePackageJson');
exports.generateDockerBuildConfig = task('generateDockerBuildConfig');
exports.updateTsConfigJson = task('updateTsConfigJson');
exports.generateEnvironmentJsonFile = task('generateEnvironmentJsonFile');
exports.generateEnvironmentTsFile = task('generateEnvironmentTsFile');
exports.reset = task('reset');
exports.init = task('init');
exports.copyRefDirect = series('confirmCopyProjectToRefDirect', series('init', 'copyProjectToRefDirect', 'updateTsConfigJson'));
exports.updateRefDirect = series('confirmUpdateProjectRefDirectToOriginalProject', series('init', 'updateProjectRefDirectToOriginalProject'));
exports.updateAssetsTnxshared = series('confirmUpdateAssetTnxShared', series('init', 'updateAssetTnxShared'));
exports.updateNextVersionService = series('loadAngularSchema', 'updateNextVersionService');
exports.updateDepdendServiceToLatest = series('loadAngularSchema', 'updateDepdendServiceToLatest');
exports.generateAll = series(
    'loadAngularSchemaTemplate',
    'generateAngularJson',
    'updatePackageJson',
    'generateDockerBuildConfig',
    'updateTsConfigJson',
    'generateEnvironmentJsonFile',
    'generateEnvironmentTsFile'
);
