"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("@iotize/device-client.js/config/iotize-studio/parser/xml");
const core_1 = require("@angular-devkit/core");
const strings_1 = require("@angular-devkit/core/src/utils/strings");
const iotize_studio_1 = require("@iotize/device-client.js/config/iotize-studio");
const configurator_1 = require("@iotize/device-client.js/configurator");
const dependencies_1 = require("@schematics/angular/utility/dependencies");
const fs_1 = require("fs");
const config_dependencies_1 = require("../config-dependencies");
const config_generator_1 = require("./config-generator");
const cordova_1 = require("./cordova");
const definitions_1 = require("./definitions");
const config_adapter_1 = require("./iotize-studio/config-adapter");
const pwa_1 = require("./pwa/pwa");
const util_1 = require("./util");
const schematics_1 = require("./utility/schematics");
const semver_1 = require("semver");
exports.TAP_CONFIG_FILEPATH = '.iotize/tapconfig.json';
exports.APP_CONFIG_FILEPATH = '.iotize/appconfig.json';
exports.IOTIZE_STUDIO_CONFIG_FILEPATH = '.iotize/iotize-studio-config.iotz';
function createAppConfigFromIoTizeStudio(iotizeStudioConfigString) {
    let config = config_adapter_1.IoTizeStudioConfigAdapter.createAppConfigFromIotizeStudioConfig(iotizeStudioConfigString);
    if (!config) {
        const tapConfigSchema = configurator_1.parseTapConfig(iotizeStudioConfigString);
        config = config_generator_1.createDefaultConfigGenerator(tapConfigSchema, undefined);
    }
    return config;
}
exports.createAppConfigFromIoTizeStudio = createAppConfigFromIoTizeStudio;
class Project {
    constructor(tree, context) {
        this.tree = tree;
        this.context = context;
        // this.context.logger.info(`Project instance`);
        if (this.hasAppConfigFile() || this.hasIotizeStudioFile() || this.hasTapConfigFile()) {
            this.reloadConfig();
        }
    }
    get pwa() {
        return new pwa_1.PWAHelper(this.tree);
    }
    get communicationProtocols() {
        let protocols = [];
        let tapConnectPage = this.appConfigBuilder.getPageByType("TapConnectionPage");
        if (tapConnectPage) {
            if (tapConnectPage.protocols.mqtt && tapConnectPage.protocols.mqtt.enabled) {
                protocols.push(definitions_1.AppProtocol.MQTT);
            }
            if (tapConnectPage.protocols.ble && tapConnectPage.protocols.ble.enabled) {
                protocols.push(definitions_1.AppProtocol.BLE);
            }
            if (tapConnectPage.protocols.nfc && tapConnectPage.protocols.nfc.enabled) {
                protocols.push(definitions_1.AppProtocol.NFC);
            }
            if (tapConnectPage.protocols.socket && tapConnectPage.protocols.socket.enabled) {
                protocols.push(definitions_1.AppProtocol.SOCKET);
                protocols.push(definitions_1.AppProtocol.WEBSOCKET);
                protocols.push(definitions_1.AppProtocol.WIFI);
            }
        }
        return protocols;
        // return this.appConfig.app.communicationProtocols;
    }
    get tapConfig() {
        if (!this._tapConfig) {
            this._tapConfig = this.loadTapConfig();
        }
        return this._tapConfig;
    }
    get appConfigBuilder() {
        if (!this._appConfigBuilder) {
            this.loadAppBuilder();
        }
        return this._appConfigBuilder;
    }
    get appConfigSchema() {
        return this.appConfigBuilder.schema;
    }
    get logger() {
        return this.context.logger; // TODO check type
    }
    getDeviceMenu() {
        return (this.appConfigSchema.app.menu || [])
            .filter(menu => {
            let page = this.appConfigBuilder.getPageByPath(menu.url);
            return !page || page.enabled;
        });
    }
    get dependencies() {
        if (!this._dependenciesManager) {
            this._dependenciesManager = new DependecyManager(this.tree);
        }
        return this._dependenciesManager;
    }
    createRoutesForPages(parentPage, pages, lineModifier = (page) => { }) {
        return pages
            .filter((page) => page.enabled)
            .map(page => {
            let projectPath = page.projectPath;
            // this.context.logger.info(`Create route for page: ${page.type} (path: ${page.path}, project: ${page.projectPath})`);
            let moduleFileName = projectPath.split('/')[projectPath.split('/').length - 1];
            // let moduleClassName = classify(`${moduleFileName}Module`);
            let moduleClassName = page.moduleName || strings_1.classify(`${moduleFileName}Module`);
            let path = page.path;
            if (path.startsWith('/')) {
                this.context.logger.warn(`Invalid path "${path}". It should not start with "/". It was removed from path`);
                path = path.substr(1);
            }
            let loadChildrenPageFullPath = `${projectPath}/${moduleFileName}.module#${moduleClassName}`;
            let loadChildrenPageRelativePath = loadChildrenPageFullPath;
            if (parentPage) {
                let relativeLoadChildrenPage = loadChildrenPageRelativePath.replace(`${parentPage.projectPath}/`, '');
                // this.context.logger.info(`Rewriting loadChildren path: ${loadChildrenPage} relatively to ${parentPage.projectPath}: ${relativeLoadChildrenPage}`);
                loadChildrenPageRelativePath = relativeLoadChildrenPage;
            }
            let baseUrl = `src/app`; // TODO CRITICAL read from config !!!
            // this.context.logger.info(`Router: ${path} => ${loadChildrenPage}`);
            let route = Object.assign({ path: `${path}`, 
                // Using relative path may bug with SystemJS module loader when 2 path are identical
                // loadChildren: `./${loadChildrenPageRelativePath}`,
                loadChildren: `${baseUrl}/${loadChildrenPageFullPath}` }, lineModifier(page));
            return route;
        });
    }
    setConfig(configContent) {
        if (configContent.iotizeStudioConfig) {
            this.setIotizeStudioConfig(configContent.iotizeStudioConfig);
        }
        if (configContent.appConfig) {
            this.setAppConfig(configContent.appConfig);
        }
        if (configContent.tapConfig) {
            this.setTapConfig(configContent.tapConfig);
        }
        this.loadAppBuilder();
    }
    setTapConfig(tapConfig) {
        this._overwriteJson(exports.TAP_CONFIG_FILEPATH, tapConfig);
    }
    setAppConfig(appConfig) {
        this._overwriteJson(exports.APP_CONFIG_FILEPATH, appConfig);
    }
    setIotizeStudioConfig(iotizeStudioConfig) {
        this.createOrOverwrite(exports.IOTIZE_STUDIO_CONFIG_FILEPATH, iotizeStudioConfig);
        this._iotizeStudioConfig = iotize_studio_1.IotizeConfigFactory.fromXML(iotizeStudioConfig);
        this._iotizeStudioConfigString = iotizeStudioConfig;
    }
    reloadConfig() {
        this.loadTapConfig();
        this.loadAppBuilder();
    }
    getAppRouting() {
        let routes = this.createRoutesForPages(undefined, this.appConfigBuilder.rootPageList);
        const defaultPageGuess = '/connect';
        if (!this.appConfigSchema.app.defaultPage) {
            this.context.logger.warn(`Configuration app.defaultPage configuration key is not present. Setting default page to: "${defaultPageGuess}"`);
            this.appConfigSchema.app.defaultPage = defaultPageGuess;
            this.save();
        }
        routes.push({
            pathMatch: 'full',
            path: '',
            redirectTo: this.appConfigSchema.app.defaultPage
        });
        let notFoundPageIndex = routes.findIndex(route => route.path === "*" || route.path === "**");
        if (notFoundPageIndex >= 0) {
            util_1.arrayMove(routes, notFoundPageIndex, routes.length - 1);
        }
        return routes;
    }
    get iotizeStudioConfigString() {
        if (!this._iotizeStudioConfigString && this.tree.exists(exports.IOTIZE_STUDIO_CONFIG_FILEPATH)) {
            this._iotizeStudioConfigString = this.tree.read(exports.IOTIZE_STUDIO_CONFIG_FILEPATH).toString();
        }
        return this._iotizeStudioConfigString;
    }
    get iotizeStudioConfig() {
        if (!this._iotizeStudioConfig && this.tree.exists(exports.IOTIZE_STUDIO_CONFIG_FILEPATH)) {
            this._iotizeStudioConfig = this.loadIoTizeStudioConfigFromFile();
        }
        return this._iotizeStudioConfig;
    }
    createTapConfig(tapConfigPath, appConfigPath, regenerateAppConfig) {
        let tapConfigContent = fs_1.readFileSync(tapConfigPath).toString();
        // this.tree.create(TAP_CONFIG_FILEPATH, tapConfigContent);
        this._tapConfig = configurator_1.parseTapConfig(tapConfigContent);
        if (tapConfigPath.endsWith('.iotz')) {
            this._iotizeStudioConfig = iotize_studio_1.IotizeConfigFactory.fromXML(tapConfigContent);
            this.setIotizeStudioConfig(tapConfigContent);
        }
        if (appConfigPath) {
            this.loadAppConfigFromFile(appConfigPath);
            this.loadAppBuilder();
        }
        else if (!this.hasAppConfigFile() || regenerateAppConfig) {
            this.createAppConfig();
        }
        // writeJsonFile(this.tree, TAP_CONFIG_FILEPATH, this._tapConfig);
        // console.info('CREATE TAP CONFIG', this._tapConfig);
    }
    createAppConfig() {
        const currentAppConfig = this._appConfigBuilder ? this._appConfigBuilder.schema : undefined;
        if (this.iotizeStudioConfigString) {
            this.context.logger.info('Creating app config from IoTize studio config...');
            this._appConfigBuilder = createAppConfigFromIoTizeStudio(this.iotizeStudioConfigString);
        }
        else {
            this.context.logger.info('Creating default app config');
            this._appConfigBuilder = config_generator_1.createDefaultConfigGenerator(this.tapConfig, undefined);
        }
        if (currentAppConfig) {
            if (!this._appConfigBuilder.schema.app.integration) {
                this._appConfigBuilder.schema.app.integration = currentAppConfig.app.integration;
            }
        }
    }
    hasAppConfigFile() {
        return this.tree.exists(exports.APP_CONFIG_FILEPATH);
    }
    hasTapConfigFile() {
        return this.tree.exists(exports.TAP_CONFIG_FILEPATH);
    }
    hasIotizeStudioFile() {
        return this.tree.exists(exports.IOTIZE_STUDIO_CONFIG_FILEPATH);
    }
    protocolOptions(name) {
        var _a, _b;
        name = name.toLowerCase();
        let tapConnectPage = this.appConfigBuilder.getPageByType("TapConnectionPage");
        if ((_a = tapConnectPage) === null || _a === void 0 ? void 0 : _a.protocols) {
            if (name in tapConnectPage.protocols) {
                return (_b = tapConnectPage) === null || _b === void 0 ? void 0 : _b.protocols[name];
            }
        }
        return {};
    }
    hasProtocol(name) {
        if (name === "webview") {
            return true;
        }
        let enumKey = name.toUpperCase();
        if (!(enumKey in definitions_1.AppProtocol)) {
            throw new Error(`Protocol "${name}" does not exist. Available: ${Object.values(definitions_1.AppProtocol).join(', ')}`);
        }
        return this.communicationProtocols.indexOf(definitions_1.AppProtocol[enumKey]) >= 0;
        // return this.pages.TapConnectionPage
        //     && this.pages.TapConnectionPage.protocols
        //     && (name in this.pages.TapConnectionPage.protocols)
        //     && this.pages.TapConnectionPage.protocols[name].enabled;
    }
    toTemplate() {
        let extraAppRouting = this.getAppRouting();
        return Object.assign({ hasProtocol: this.hasProtocol.bind(this), protocolOptions: this.protocolOptions.bind(this), useNFC: this.hasProtocol(definitions_1.AppProtocol.NFC), hasPage: this.appConfigBuilder.hasPageByPath.bind(this.appConfigBuilder), pages: this.appConfigSchema.app.pages, isProductType: (type) => {
                switch (type.toLowerCase()) {
                    case 'tapnpass':
                        return this.tapConfig.config.target.type === "TAPNPASS";
                    case 'tapnlink':
                        return this.tapConfig.config.target.type === "MCU";
                    default:
                        throw new Error(`Unknown product type: ${type}`);
                }
            }, appRouting: extraAppRouting, defaultPage: this.appConfigBuilder.getDefaultPage(), packageName: strings_1.dasherize(this.appConfigSchema.app.name || 'My IoTize Tap application'), appName: this.appConfigSchema.app.name, appVersion: this.appConfigSchema.app.version, appId: this.appConfigSchema.app.id, jsonify: util_1.jsonify,
            variablenamify: util_1.variablenamify,
            getDependencyVersionFromGroup: config_dependencies_1.getDependencyVersionFromGroup }, core_1.strings);
    }
    save() {
        if (!this._tapConfig) {
            throw new Error(`tapConfig has not been loaded yet. Cannot save`);
        }
        this.setTapConfig(this._tapConfig);
        if (!this._appConfigBuilder) {
            throw new Error(`_appConfigBuilder has not been loaded yet. Cannot save`);
        }
        this.setAppConfig(this._appConfigBuilder.schema);
        // console.info('SAVING CONFIG: ', this._appConfig.app.pages);
    }
    /**
     * TODO only overwrite if file changed
     * @param path
     * @param data
     */
    _overwriteJson(path, data) {
        this.createOrOverwrite(path, JSON.stringify(data, null, 4));
    }
    createOrOverwrite(path, content, overwrite = true) {
        if (overwrite && this.tree.exists(path)) {
            this.tree.overwrite(path, content);
        }
        else {
            this.tree.create(path, content);
        }
    }
    listVariables() {
        if (!this.tapConfig.config.data || !this.tapConfig.config.data.bundles) {
            return [];
        }
        return this.tapConfig.config.data.bundles
            .reduce((acc, bundle) => {
            if (bundle.variables) {
                acc.push(...bundle.variables);
            }
            return acc;
        }, []);
    }
    loadAppBuilder() {
        let appConfigFile = this.loadAppConfig();
        this._appConfigBuilder = new config_generator_1.ConfigGenerator(appConfigFile, this.tapConfig, this.iotizeStudioConfigString, this.logger);
        return this._appConfigBuilder;
    }
    loadAppConfigFromFile(filepath) {
        let appConfigContent = JSON.parse(fs_1.readFileSync(filepath).toString());
        if (!appConfigContent.version || !appConfigContent.version.generator) {
            throw new Error(`Invalid configuration in "${exports.APP_CONFIG_FILEPATH}". Missing key generator.version.`);
        }
        // else if (appConfigContent.version.generator !== Project.version){
        // TODO version mismatch
        // }
        this.setAppConfig(appConfigContent);
        return appConfigContent;
    }
    loadAppConfig() {
        if (!this.tree.exists(exports.APP_CONFIG_FILEPATH)) {
            this.createAppConfig();
            this.setAppConfig(this.appConfigBuilder.schema);
        }
        return schematics_1.getJsonFileContent(this.tree, exports.APP_CONFIG_FILEPATH);
    }
    loadIoTizeStudioConfigFromFile() {
        // this.context.logger.info('Loading iotize studio configuration');
        return iotize_studio_1.IotizeConfigFactory.fromXML(this.tree.read(exports.IOTIZE_STUDIO_CONFIG_FILEPATH).toString());
    }
    loadTapConfig() {
        if (!this.tree.exists(exports.TAP_CONFIG_FILEPATH)) {
            if (this.tree.exists(exports.IOTIZE_STUDIO_CONFIG_FILEPATH)) {
                this.context.logger.info(`Creating Tap config from iotize studio config`);
                this._tapConfig = configurator_1.parseTapConfig(this.tree.read(exports.IOTIZE_STUDIO_CONFIG_FILEPATH).toString());
                return this._tapConfig;
            }
        }
        if (!this.tree.exists(exports.TAP_CONFIG_FILEPATH)) {
            throw new Error(`Missing ${exports.TAP_CONFIG_FILEPATH} file in project.`);
        }
        let configFile = this.tree.read(exports.TAP_CONFIG_FILEPATH).toString();
        this._tapConfig = configurator_1.parseTapConfig(configFile);
        return this._tapConfig;
    }
    createFromConfigFile(configPath) {
        let configFile = fs_1.readFileSync(configPath).toString();
        let configContent = JSON.parse(configFile);
        // if (!configContent.iotizeStudioConfig) {
        //     throw new Error(`Invalid configuration file. Missing key "iotizeStudioConfig" in configuration file ${configPath}`);
        // }
        this.setConfig(configContent);
    }
}
exports.Project = Project;
class DependecyManager {
    constructor(tree) {
        this.tree = tree;
        this._newDependencies = [];
    }
    get newDependencies() {
        return this._newDependencies;
    }
    addCordovaPlugin(packages) {
        packages.forEach(p => {
            let cordovaPluginInfo = {
                name: p.id,
                spec: `${p.prefixVersionWithPackage ? `${p.package}@` : ''}${p.spec}`
            };
            cordova_1.addCordovaPluginToConfig(this.tree, cordovaPluginInfo);
            // addCordovaPluginToPackageJson(this.tree, cordovaPluginInfo);
            this.addNpmDependencies([
                {
                    name: p.package,
                    version: p.version || p.spec,
                    type: dependencies_1.NodeDependencyType.Default
                }
            ]);
        });
        return this;
    }
    /**
     * Add npm dependencies to package.json and update newDependencies array
     * if dependency is new or if previous version does not satisfy the new dependency version
     * @param packages dependencies to add
     */
    addNpmDependencies(packages) {
        packages.forEach(packageOrString => {
            const newDep = typeof packageOrString === "string" ? {
                name: packageOrString,
                overwrite: false,
                type: dependencies_1.NodeDependencyType.Default,
                version: 'latest'
            } : packageOrString;
            const existingDep = dependencies_1.getPackageJsonDependency(this.tree, newDep.name);
            // Check that previous dependency version satisfy the new dependency version
            if (newDep.version === 'latest' || !existingDep || !semver_1.satisfies(existingDep.version, newDep.version)) {
                this._newDependencies.push(newDep);
            }
            dependencies_1.addPackageJsonDependency(this.tree, newDep);
        });
        return this;
    }
    hasNewDependencies() {
        return this._newDependencies.length > 0;
    }
}
exports.DependecyManager = DependecyManager;
//# sourceMappingURL=project.js.map