/*
	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 = 'register'; // - name for clarification during logging
//private modules
var promptUserForMissingInfo = require('./promptUserInput');
var delegateBuildingPropertiesFile = require('./delegateBuildingPropertiesFile');
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');

// run iterator function over all elements in the array, ensuring
// that the asynchronous iterator function runs in array order
function mapSeries (arr, iterator, serverInfo, cordovaWindowsPlatforms) {
  // create a empty promise to start our series (so we can use `then`)
  var currentPromise = new Q();
  var promises = arr.map(function (el) {
    return currentPromise = currentPromise.then(function () {
      // execute the next function after the previous has resolved successfully
      return iterator(el, serverInfo, cordovaWindowsPlatforms);
    });
  });
  // group the results and return the group promise
  return Q.all(promises);
}

//Generates the JSON that will be sent as the payload to the REST call to register an app
function generateRegisterJSON(platformObj, appInfo) {
    var idName;
    var appIdKey = 'app.dotId';
    var appName = appInfo.name ? appInfo.name : null;
    if(platformObj.platform === 'android') {
        idName = 'packageName';
        if(appInfo['app.androidId']) {
          appIdKey = 'app.androidId';
          appName += 'Android';
        }
    } else if(platformObj.platform === 'ios') {
        idName = 'bundleId';
        if(appInfo['app.iosId']) {
          appIdKey = 'app.iosId';
          appName += 'iOS';
        }
    } else if(platformObj.platform === 'browser') {
        idName = 'applicationId';
    }else {
        idName = 'assemblyName';
    }
    var registerJSON = {};
    if(platformObj.platform !== 'browser'){
        registerJSON = {
          applicationKey: {
              version: appInfo['app.version'],
              clientPlatform: platformObj.platform
          }
      };
    }else{
      registerJSON = {
          applicationKey: {
              clientPlatform: "web"
          }
      };
    }

    registerJSON.applicationKey[idName] = appInfo[appIdKey];
    //Add app name if it exists
    if(appInfo.name) {
      registerJSON.displayName = appName;
    }
    return registerJSON;
}


module.exports = function register(args) {
    var d = Q.defer(),
        project = {},
        server,
        runtime,
        password,
        serverDetails;

    var opts = nopt({
        'password': String,
        'windows': String
    }, {
        '-p': '--password',
        '-w': '--windows'
    }, args, 0);

    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
    serverSupport.getServerDetails(server, runtime, password)
    .then(function (serverDetailsReturned) {
        serverDetails = serverDetailsReturned;
        serverDetails.serverProfile.fullURL = urlFormatter.format({protocol:(serverDetails.serverProfile.protocol || 'http') , hostname: serverDetails.serverProfile.host, port: serverDetails.serverProfile.port});
        strings.info(strings.format(messages.registeringLocationMessage, serverDetails.serverProfile.fullURL, serverDetails.runtime));
        return platformManager.detectPlatforms();
    })
    .then(function (detectedProjectInfo) {
      project = 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(project.platforms[0].platform.indexOf('windows8') >= 0 && project.platforms.length > 1) {
        projects = splitWindows8Projects(project);
      } else {
        projects.push(project);
      }
      return mapSeries(projects, registerApp, serverDetails, opts.windows);
    })
    .then(function () {
      d.resolve();
    })
    .catch(function (err) {
        strings.fatal(err.description);
        d.reject(err);
    });

    return d.promise;
};

function registerApp(project, serverDetails, cordovaWindowsPlatforms) {

  var d = Q.defer();
    var appName;
    var appInfo;
    var registerPromises = [];
    var parsingDefer = Q.defer();

		//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.
    //Nested .then was needed because the error throw by parsePlatform had to be explicitly caught
		platformManager.parsePlatform(project.platforms[0].platformId, project.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': ''
      });
    });
    //Received back parsed information (if any) and are extracting the appName
    //Prompt the user if any information was missing in the parsed data, if all properties needed are present then it will
      //pass through the module
    parsingDefer.promise.then(function (platformInfo) {
      //Use platform info to try and not have to prompt user for info.
      appName = platformInfo['app.name'] !== '' ? platformInfo['app.name'] : project.platforms[0].appName;
      return promptUserForMissingInfo(platformInfo, project.platforms[0].platform);
    })
    //Received back the prompted data (if any). Delegates out to build/update the property file for current platform
    //Cordova -> updates config.xml, IOS -> builds/updates mfpclient.plist, Android&Windows -> builds/updates mfpclient.properties
    .then(function (retPromptedInfo) {
      appInfo = retPromptedInfo;
      appInfo.name = appName;
      //Update or build the property file for each platform type, 1 for native, 1-3 for Cordova
      return delegateBuildingPropertiesFile(project.platforms, serverDetails.serverProfile, serverDetails.runtime);
    })
    //Issue the actual REST call to register an app.  Wait for all promises to finish and check whether each one was successful or rejected

    .then(function () {
      var filteredPlatforms = project.platforms;
      //If --windows flag was set then remove the Windows platform that were not specified by it
      if(cordovaWindowsPlatforms && project.isCordova) {
        filteredPlatforms = removeNonSpecifiedWindows(cordovaWindowsPlatforms, project.platforms);
      }
      //Call register for each platform
      filteredPlatforms.forEach(function (platform) {
          //Tack on the jsonObj to be registered
          var registerJSONPayload = generateRegisterJSON(platform, appInfo);
          registerPromises.push(serverDetails.registerApplication(registerJSONPayload));
      });

      Q.allSettled(registerPromises)
      .then(function (results) {
          results.forEach(function (result) {
              if (result.state === "fulfilled") {
                  if(result.value.appInfo) {
                      strings.info(result.value.appInfo);
                  }
              } else {
                  if(result.reason.appInfo) {
                      strings.warn(result.reason.appInfo);
                  }
              }
          });
          d.resolve();
      });
    })
    //Print out if any promises above had errors
    .catch(function (err) {
      strings.fatal(err.description);
      d.reject(err);
    });

    return d.promise;
}

//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;
}

/**This function is for the --windows flag.  It removes the other windows platforms that weren't specified by the --windows flag
--windows takes a comma separated list, valid entries are windows,windows8,windowsphone8.
  Ex.  mfpdev app webupdate --windows windows,windows8  , this would cause the removal of the windowsphone8 platform
**/
function removeNonSpecifiedWindows(windowsPlats, platforms) {
  var filteredPlatforms = [];
  var invalid = false;

  var windowsPlatforms = windowsPlats.split(',');
  //If provided an invalid arg then just ignore the --windows command
  windowsPlatforms.forEach(function (windows) {
    if(windows !== 'windows8' && windows !== 'windowsphone8' && windows !== 'windows') {
      strings.warn(strings.format(messages.invalidWindowsArgument, windows));
      invalid = true;
    }
  });

  if(!invalid) {
    //Check for each platform if it is present in our --windows arguments or that it is not a Windows platform type
    platforms.forEach(function (platform) {
      if(windowsPlatforms.indexOf(platform.platform) >= 0 || platform.platform.indexOf('windows') < 0) {
        filteredPlatforms.push(platform);
      }
    });
    return filteredPlatforms;
  } else {
    return platforms;
  }
}
