/*
	Licensed Material - Property of IBM

	(C) Copyright 2015, 2016 IBM Corp.

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/


'use strict';

var moduleName = 'mdo-app-pull'; // - name for clarification during logging

//private modules
var promptForAppInfo = require('../utils/promptForAppInfo');
var serverSupport = require('mdo-server-support');
var platformManager = require('platform-manager');
var strings = require('ibm-strings');
var messages = require('../externalizedStrings');

//3rd party modules
var nopt = require('nopt');
var log = require('npmlog');
var Q = require('q');
var urlFormatter = require('url');

//Function to split the native Windows8 projects, in a Windows8 project there can be a Windows folder and a WindowsPhone,
//These two folders are treated as seperate projects in 8.0, so we need to split them so register can run for each project type.
//This is not the same as Cordova because for Cordova they share a file that is parsed (config.xml) and they don't generate a mfpclient file
function splitWindows8Projects(project) {

var projects = [];
for(var i = 0; i < project.platforms.length; i++) {
  var projectObj = {};
  for(var key in project) {
    if(key !== 'platforms') {
      projectObj[key] = project[key];
    }
  }
  //Replace rootDir with path to specific windows folder (Windows or WindowsPhone)
  projectObj.rootDir = project.platforms[i].windowsEnvPath;
  projectObj.platforms = [];
  projectObj.platforms.push(project.platforms[i]);
  projects.push(projectObj);
}

return projects;
}

//run iterator function over all elements in the array, ensuring
//that the asynchronous iterator function runs in array order
function mapSeries (allProjects, mfpDevAppMethod, serverDetails, runtimeConfig) {

	// create a empty promise to start our series (so we can use `then`)
	var currentPromise = new Q();
	var promises = allProjects.map(function (mfpDevProj) {
		return currentPromise = currentPromise.then(function () {
			// execute the next function after the previous has resolved successfully
			return mfpDevAppMethod(mfpDevProj, serverDetails, runtimeConfig);
		});

	});

	// group the results and return the group promise
	return Q.all(promises);

}

var generateApplicationDetails = function (platformObj, appInfo) {

    var idName;
    var altPlatformId;
    if(platformObj.platform === 'android') {
        idName = 'packageName';
    } else if(platformObj.platform === 'ios') {
        idName = 'bundleId';
    } else if(platformObj.platform === 'browser') {
        idName = 'applicationId';
    } else {
        idName = 'assemblyName';
    }

	var appJSON = {};

	if (platformObj.platform === 'browser') {
		appJSON = {
			applicationKey: {
				clientPlatform: 'web'
			}
		};
	}
	else {
		appJSON = {
			applicationKey: {
				version: appInfo['app.version'],
				clientPlatform: altPlatformId ? altPlatformId : platformObj.platform
			}
		};
	}
    appJSON.applicationKey[idName] = appInfo['app.dotId'];

    return appJSON;

};

var runAppPull = function(detectedProjectInfo, serverDetails, runtimeConfig){

	var d = Q.defer();
    var parsingDefer = Q.defer();
    var parsingPromise = parsingDefer.promise;
    var mdoProject = detectedProjectInfo;
    var appName;

    log.verbose(moduleName, 'detectedProjectInfo: ' + JSON.stringify(detectedProjectInfo));

    //We have either multiple Cordova project that all use the config.xml to parse for info or we have only one platform (ios, android, etc) that will be at index 0.
    platformManager.parsePlatform(mdoProject.platforms[0].platformId, mdoProject.rootDir)
        .then(function (platformInfo) {
            parsingDefer.resolve(platformInfo);
        }, function () {
            log.silly(moduleName, 'Failed to parse info from project file.');
            parsingDefer.resolve({
                'app.dotId': '',
                'app.version': '',
                'app.name': ''
            });
        });

    parsingPromise.then(function (platformInfo) {

        //Use platform info to try and not have to prompt user for info.
        log.verbose(moduleName, 'Found the following platform information for mdoProjects[0]: '+JSON.stringify(platformInfo));
        appName = platformInfo['app.name'] !== '' ? platformInfo['app.name'] : mdoProject.platforms[0].appName;
        log.silly(moduleName, 'current application name: '+appName);
        return promptForAppInfo(platformInfo, mdoProject.platforms[0].platform);

    })
    .then(function (retPromptedInfo) {

        var appDescriptorDefer = Q.defer();
        var applicationDescriptorPromises = [];
        var appInfo = retPromptedInfo;
        log.verbose(moduleName, 'Prompts returned the following Application Information: '+JSON.stringify(appInfo));

        appInfo.name = appName;
		if (mdoProject.platforms[0].platform === 'browser') {
			appInfo.environment = 'web';
		} else {
			appInfo.environment = mdoProject.platforms[0].platform;
		}
        log.verbose(moduleName, 'Updated Application Information: '+JSON.stringify(appInfo));

        // pull application descriptor for each for each platform found
        mdoProject.platforms.forEach(function (platform) {

            // Tack on the appDetails to be registered
            var appDetails = generateApplicationDetails(platform, appInfo);
            serverDetails['applicationDetails'] = appDetails;

            log.verbose(moduleName, 'serverDetails with application information: ' + JSON.stringify(serverDetails));
            var appDescPromise = serverDetails.getApplicationDescriptor(appDetails.applicationKey);
            applicationDescriptorPromises.push(appDescPromise);

        });

        Q.allSettled(applicationDescriptorPromises)
            .then(function (results) {
                results.forEach(function (result) {
                    log.verbose(moduleName, result);
                    if (result.state === "fulfilled") {
                        if(result.value.appInfo) {
							if (result.value.appInfo.clientPlatform === 'web')
								strings.info(strings.format(messages.pulledApp, result.value.appInfo.clientPlatform, 'none'));
							else
								strings.info(strings.format(messages.pulledApp, result.value.appInfo.clientPlatform, result.value.appInfo.version));
                        }
                    } else {
                        if(result.reason.message) {
                            strings.warn(result.reason.message);
                            strings.warn(result.reason.appDetails);
                        }
                    }
                });
                appDescriptorDefer.resolve(retPromptedInfo);
            });

        return appDescriptorDefer.promise;
    })
    .then(function (retPromptedInfo) {

        if (runtimeConfig === true) {

            var runtimeConfigPromise;
            var appInfo = retPromptedInfo;
            log.verbose(moduleName, 'Prompts returned the following Application Information: '+JSON.stringify(appInfo));

            appInfo.name = mdoProject.platforms[0].appName;
            appInfo.environment = mdoProject.platforms[0].platform;
            log.verbose(moduleName, 'Updated Application Information: '+JSON.stringify(appInfo));

            // pull runtime configuration for the first platform found (this configuration is server level
            // and will be the same for all platforms)
            if (mdoProject.platforms.length > 0) {
                // Tack on the appDetails to be registered
                var appDetails = generateApplicationDetails(mdoProject.platforms[0], appInfo);
                serverDetails['applicationDetails'] = appDetails;

                log.verbose(moduleName, 'serverDetails with application information: ' + JSON.stringify(serverDetails));
                runtimeConfigPromise = serverDetails.getRuntimeConfiguration();
            }

            runtimeConfigPromise.then(function (result) {

                log.verbose(moduleName, 'runtime config promise returned: ' + JSON.stringify(result));
                if (result && result.state === 'fulfilled') {

                    if(result.value) {

                        // store the runtime configuration under the proper filename
                        var fs = require('fs');
                        var path = require('path');

                        // if the mobilefirst directory does not exist then create it
                        var mfpDir = path.resolve(process.cwd(), 'mobilefirst');
                        if (!fs.existsSync(mfpDir)){
                          fs.mkdirSync(mfpDir);
                        }

                        // construct the runtime config JSON file path
                        var runtimeConfigFilePath = path.resolve(mfpDir,'mfp-server-runtime-config.json');

                        // delete runtime config JSON file if it already exists
                        log.verbose(moduleName, 'Attempting to delete Runtime Configuration File if it exists: ' + runtimeConfigFilePath);
                        try {
                            fs.unlinkSync(runtimeConfigFilePath);
                        } catch (e) {}

                        // add content to runtime config JSON file
                        log.verbose(moduleName, 'Writing to Runtime Configuration File Path: ' + runtimeConfigFilePath);
                        fs.writeFileSync(runtimeConfigFilePath, JSON.stringify(result.value.runtimeConfig));

                        strings.info(messages.pulledRuntimeConfiguration);

                    }

                } else {
                    if(result && result.reason && result.reason.message) {
                        strings.warn(result.reason.message);
                        strings.warn(result.reason.appDetails);
                    }
                }
                d.resolve();

            }, function (err) {
                log.verbose(moduleName, err);
                d.reject(err);
            });

        } else {
            d.resolve();
        }

    })
    .catch(function (err) {
        log.verbose(moduleName, err);
        strings.fatal(err.name + ': ' + err.message + ': ' + err.appDetails);
        d.reject(err);
    });

    return d.promise;

};

module.exports = function run(args) {

    var d = Q.defer(),
        server,
        runtime,
        password,
        serverDetails;

    var opts = nopt({
        'password': String,
        'runtime-config': Boolean
    }, {
        '-p': '--password',
        'r': ['--runtime-config']
    }, args, 0);
    var runtimeConfig = opts['runtime-config'] ? opts['runtime-config'] : false;

    password = opts.password ? opts.password : undefined;
    if(opts.argv.remain.length > 0) {
        server = opts.argv.remain[0];
        runtime = opts.argv.remain[1] ? opts.argv.remain[1] : undefined;
    }

    //Process server options
    log.silly(moduleName, 'Passing getServerDetails the following settings: ' + {'server':server, 'runtime':runtime, 'password':password});
    serverSupport.getServerDetails(server, runtime, password)
        .then(function (sDetails) {

            log.verbose(moduleName, 'Server Details response: ' + JSON.stringify(sDetails));

            serverDetails = sDetails;
            if(typeof serverDetails.path !== 'undefined' && serverDetails.path.length > 1)
                serverDetails.serverProfile.fullURL = urlFormatter.format({protocol:(serverDetails.protocol || 'http') , hostname: serverDetails.host, port: serverDetails.port, pathname: serverDetails.path});           
            else
                serverDetails.serverProfile.fullURL = urlFormatter.format({protocol:(serverDetails.protocol || 'http') , hostname: serverDetails.host, port: serverDetails.port});
            strings.info(strings.format(messages.pullingApplicationMessage, serverDetails.serverProfile.fullURL, serverDetails.runtime));
            return platformManager.detectPlatforms();

        })
        .then(function (detectedProjectInfo) {
            var mfpDevProject = detectedProjectInfo;
            var projects = [];

            //If we have a windows8 project then we need to split it and create two projects, one for the Windows folder and one for the WindowsPhone folder
            if(mfpDevProject.platforms[0].platform.indexOf('windows8') >= 0 && mfpDevProject.platforms.length > 1) {
              projects = splitWindows8Projects(mfpDevProject);
            } else {
              projects.push(mfpDevProject);
            }
            return mapSeries(projects, runAppPull, serverDetails, runtimeConfig);
        })
        .then(function () {
            d.resolve();
        })
        .catch(function (err) {
            log.verbose(moduleName, err);
            if (err.appDetails){
                strings.fatal(err.name + ': ' + err.message + ': ' + err.appDetails);
            }
            else if (err.code !== undefined && err.description !== undefined) {
                strings.fatal(err.code + ': ' + err.description);
            }
            else if (err.name !== undefined && err.message !== undefined){
                strings.fatal(err.name + ': ' + err.message);
            }
            d.reject(err);
        });

    return d.promise;
};
