/*
	Licensed Material - Property of IBM

	(C) Copyright 2015 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 fs = require('fs');
var strings = require('ibm-strings');
var log = require('npmlog');
var path = require('path');
var async = require('async');
var AdmZip = require('adm-zip');
var Q = require('q');
var xml2js = require('xml2js');
var consts = require('./constants');
var urlFormatter = require('url');
var _ = require('lodash');
var S = require('string');
var exec = require('mdo-exec').run;
var externalizedStrings = require('./externalizedStrings');

log.level = global.MDO_CLI_LOG_LEVEL ? global.MDO_CLI_LOG_LEVEL : log.level;

module.exports.run = function(appDetails, configDetails) {
  var deferred = Q.defer();

  var projectFolder = path.join(process.cwd(), appDetails.appName);

  try {
    createIonicFolder(projectFolder);
    async.series([
      function(callback) {
        async.parallel([
          writeStudioFile.bind(null, appDetails, configDetails),
          modifyTemplate.bind(null, appDetails),
          addServerToSettings.bind(null, configDetails),
        ], function(err) {
            if (err) {
              callback(err);
            } else {
              callback();
            }
        })
      },
      function(callback) {
        if (appDetails.services.includes(consts.PUSH)) {
          async.parallel([
            installPushPlugin.bind(null, appDetails),
            modifyPushAppComponent.bind(null, appDetails),
            modifyPushStudio.bind(null, appDetails),
          ], function(err) {
              if (err) {
                callback(err);
              } else {
                callback();
              }
          })
        } else {
          callback();
        }
      },
      function(callback) {
        if (appDetails.services.includes(consts.LIVEUPDATE)) {   
          async.parallel([
            installLUPlugin.bind(null, appDetails),
            modifyCustomTypings.bind(null, appDetails),
            modifyLUAppComponent.bind(null, appDetails),
            createLUJsonFile.bind(null, appDetails),
            modifyLUStudio.bind(null, appDetails),
          ], function(err) {
              if (err) {
                callback(err);
              } else {
                callback();
              }
          })
        } else {
          callback();
        }
      },
      function(callback) {
        if (appDetails.services.includes(consts.ANALYTICS)) {      
          async.parallel([
            modifyAnalyticsAppComponent.bind(null, appDetails),
            modifyAnalyticsHomePage.bind(null, appDetails),            
          ], function(err) {
              if (err) {
                callback(err);
              } else {
                callback();
              }
          })
        } else {
          callback();
        }
      }             
    ], function(err) {
      if (err) {
        strings.error(err.message);
        deferred.reject(err);
      } else {
        var results = [];
        appDetails.platforms.forEach(function(platform) {
          var payload = {
            applicationKey: {
              version: appDetails.appVersion,
              clientPlatform: platform,            
            },
            displayName: appDetails.appName,
          };      
          if (platform == 'ios') {
            payload.applicationKey.bundleId = appDetails.appId;
          } else {
            payload.applicationKey.packageName = appDetails.appId;
          }               
          results.push(payload);
        });        
        deferred.resolve(results);
      }
    });
  } catch (error) {
    deferred.reject(error);
  }

  return deferred.promise;
};

function createIonicFolder(projectFolder) {
  try {    
    var ibmFolder = path.join(projectFolder, 'ibm');
    if (!fs.existsSync(ibmFolder)) {
      fs.mkdirSync(ibmFolder);  
    }  
    createIbmFiles(ibmFolder);  

    var ionicFolder = path.join(projectFolder, 'ionic');
    if (!fs.existsSync(ionicFolder)) {
      fs.mkdirSync(ionicFolder);  
    }  

    var ionicZipPath = path.join(__dirname, '..', 'templates', 'dab_ionic', 'ionic.zip');
    var ionicZip = new AdmZip(ionicZipPath);
    ionicZip.extractAllTo(ionicFolder, true, true);

    var targetFilePath = path.join(projectFolder, 'ionic', 'app-build.json');
    var appBuildFilePath = path.join(__dirname, '..', 'templates', 'dab_ionic', 'autoBuild', 'app-build.json');
    fs.copyFileSync(appBuildFilePath, targetFilePath);

    targetFilePath = path.join(projectFolder, 'ionic', 'dab-android.keystore');
    var dabKeystoreFilePath = path.join(__dirname, '..', 'templates', 'dab_ionic', 'autoBuild', 'dab-android.keystore');
    fs.copyFileSync(dabKeystoreFilePath, targetFilePath);
  } catch (error) {
    strings.error(error.message);
    throw error;
  }
}

function createIbmFiles(ibmFolder) {
  var watsonFolder = path.join(ibmFolder, 'watson');
  if (!fs.existsSync(watsonFolder)) {
    fs.mkdirSync(watsonFolder);  
  }   
  var dataFolder = path.join(ibmFolder, 'data');
  if (!fs.existsSync(dataFolder)) {
    fs.mkdirSync(dataFolder);  
  }
  var adapterFolder = path.join(ibmFolder, 'adapterGenerator');
  if (!fs.existsSync(adapterFolder)) {
    fs.mkdirSync(adapterFolder);  
  }  
  var adapterZipPath = path.join(__dirname, '..', 'templates', 'dab_ionic', 'adapterGenerator.zip');
  var adapterZip = new AdmZip(adapterZipPath);
  adapterZip.extractAllTo(adapterFolder, true);
}

function writeStudioFile(appDetails, configDetails, callback) {
  var server = {
    serverName: consts.ICPA_SERVER,
    mfpUrl: urlFormatter.format({
      protocol: consts.PROTOCOL, 
      hostname: consts.HOST, 
      port: consts.PORT
    })   
  };
  var appJson = {};
  var projectFolder = path.join(process.cwd(), appDetails.appName);

  if (configDetails && configDetails.serverConfig) {
    var serverJson = JSON.parse(fs.readFileSync(configDetails.serverConfig));
    if (serverJson.mfp) {
      server.serverName = serverJson.mfp.name;
      server.mfpUrl = urlFormatter.format({
        protocol: (serverJson.mfp.protocol || 'http'), 
        hostname: serverJson.mfp.host, 
        port: serverJson.mfp.port
      });      
    }
  }

  server.confidentialServerUrl = consts.CONF_SERVER_URL;
  appJson.server = server;
  appJson.type = consts.IONIC;
  appJson.platforms = appDetails.platforms;
  appJson.isAppAuth = false;
  appJson.isEncrypt = false;
  appJson.projectIcon = path.join(projectFolder, 'ionic', 'resources', 'icon.png');
  appJson.projectName = appDetails.appName;
  appJson.projectVersion = appDetails.appVersion;
  appJson.projectBundleId = appDetails.appId;
  appJson.isAnalyticsEnabled = true;
  appJson.isPush = false;
  appJson.isLiveUpdateEnabled = false;
  appJson.projectMode = consts.CODE;
  appJson.createdMode = consts.CLI;

  log.verbose(consts.LOG_PREFIX, 'App details: ' + JSON.stringify(appJson));

  var studioFile = path.join(projectFolder, '.studio');
  fs.writeFile(studioFile, JSON.stringify(appJson, null, 2), 'utf8', function (error) {
    if (error) {
      callback(error);
    } else {
      callback();
    }
  });
}

function modifyTemplate(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);

  var htmlTemplate = path.join(projectFolder, 'ionic', 'src', 'indextemplate.html');
  var indexHtmlFile = path.join(projectFolder, 'ionic', 'src', 'index.html');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + htmlTemplate);
  fs.readFile(htmlTemplate, 'utf-8', function (htmlError, data) {
    if (htmlError) {
      callback(htmlError);
    } else {
      var htmlFile = data;
      htmlFile = htmlFile.replace('$mfptemplate$', `
        <script>
          var wlInitOptions = {
            mfpContextRoot: "/mfp",
            applicationId: "${appDetails.appId}"
          };
        </script>
      `);

      var pwa = `
          <script class='pwa'>
            if ("serviceWorker" in navigator) {
              navigator.serviceWorker
                .register("service-worker.js")
                .then(() => console.log("service worker installed"))
                .catch(err => console.error("Error", err));
            }
          </script>
          `;
      if (appDetails.platforms.includes(consts.PWA)) {
        htmlFile = htmlFile.replace('$pwatemplate$', pwa);
      } else {
        htmlFile = htmlFile.replace('$pwatemplate$', '');
      }
      htmlFile = htmlFile.replace('$apptitle$', appDetails.appName);
      fs.writeFile(indexHtmlFile, htmlFile, function (indexErr) {
        if (indexErr) {
          callback(indexErr);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + indexHtmlFile);
          var configXmlPath = path.join(projectFolder, 'ionic', 'config.xml');
          log.verbose(consts.LOG_PREFIX, 'Reading file: ' + configXmlPath);
          fs.readFile(configXmlPath, 'utf-8', function (configErr, data)  {
            if (configErr) {
              callback(configErr);
            } else {
              xml2js.parseString(data, function (parseErr, result) {
                if (parseErr) {
                  callback(parseErr);
                } else {
                  // here we log the results of our xml string conversion
                  var json = result;
                  if (json && json.widget) {
                    json.widget.$.id = appDetails.appId;
                    json.widget.name = appDetails.appName;
                    // create a new builder object and then convert
                    // our json back to xml.
                    var builder = new xml2js.Builder();
                    var xml = builder.buildObject(json);
                    fs.writeFile(configXmlPath, xml, function (modifiedConfigErr) {
                      if (modifiedConfigErr) {
                        callback(modifiedConfigErr);
                      } else {
                        log.verbose(consts.LOG_PREFIX, 'Written file: ' + configXmlPath);
                        fs.unlink(htmlTemplate, function (deleteErr)  {
                          if (deleteErr) {
                            callback(deleteErr);
                          } else {                                                    
                            log.verbose(consts.LOG_PREFIX, 'Deleted file: ' + htmlTemplate);
                            callback();
                          }
                        });
                      }
                    });
                  }
                }
              });
            }
          });
        }
      });
    }
  });
}

function addServerToSettings(configDetails, callback) {
  var deferred = Q.defer();

  var settingsPath;
  if (process.platform === 'win32') {
    settingsPath = path.join(process.env['USERPROFILE'], 'AppData', 'Roaming', 'IBM Digital App Builder', 'Settings');    
  } else {
    settingsPath = path.join(process.env['HOME'], 'Library', 'Application Support', 'IBM Digital App Builder', 'Settings'); 
  }

  var serverName = consts.ICPA_SERVER;
  var mfpServerUrl = urlFormatter.format({
    protocol: consts.PROTOCOL, 
    hostname: consts.HOST, 
    port: consts.PORT
  });
  var userName = consts.USERNAME;
  var password = consts.PASSWORD; 

  if (configDetails && configDetails.serverConfig) {
    var serverJson = JSON.parse(fs.readFileSync(configDetails.serverConfig));
    if (serverJson.mfp) {
      serverName = serverJson.mfp.name;
      mfpServerUrl = urlFormatter.format({
        protocol: (serverJson.mfp.protocol || 'http'), 
        hostname: serverJson.mfp.host, 
        port: serverJson.mfp.port
      }); 
      userName = serverJson.mfp.servercred.split(':')[0],
      password = serverJson.mfp.servercred.split(':')[1]
    }
  }


  log.verbose(consts.LOG_PREFIX, 'Settings file: ' + settingsPath);
  if (fs.existsSync(settingsPath)) {
    log.verbose(consts.LOG_PREFIX, 'Reading file: ' + settingsPath);
    fs.readFile(settingsPath, 'utf-8', function (error, data)  {
      if (error) {
        callback(error);
      } else {
        var settingsJSON = JSON.parse(data);
        var list = settingsJSON.serverList;
        if (!list) {
          list = [];
        }
        var serverExists = false;
        for (var index = 0; index < list.length; index++) {
          if (list[index].serverName === serverName) {
            log.verbose(consts.LOG_PREFIX, 'Server %s already in the settings', serverName);
            serverExists = true;
            break;
          }
        }     
        if (!serverExists) {
          log.verbose(consts.LOG_PREFIX, 'Adding server %s to the settings', serverName);
          list.push({
            "serverName": serverName,
            "mfpServerUrl": mfpServerUrl,
            "serverUserName": userName,
            "serverPassword": password,
            "analyticsTenantName": consts.WORKLIGHT,
            "confidentialServerUrl": consts.CONF_SERVER_URL,
            "confidentialUserName":"",
            "confidentialserverPassword":""
          });
          settingsJSON.serverList = list;
          fs.writeFile(settingsPath, JSON.stringify(settingsJSON, null, 2), 'utf8', function (writeError) {
            if (writeError) {
              log.verbose(consts.LOG_PREFIX, 'Written file: ' + settingsPath);
              callback(writeError);
            } else {
              callback();
            }
          });          
        } else {
          callback();
        }
      }
    });
  } else {
    log.verbose(consts.LOG_PREFIX, 'File not found: ' + settingsPath);
    callback();
  }

  return deferred.promise;
}

function installLUPlugin(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName, 'ionic');

  strings.info(externalizedStrings.luInstalling);
  exec('ionic', ["cordova", "plugin", "add", "cordova-plugin-mfp-liveupdate@latest"], projectFolder).then(function() {
    callback();  
  }, function(err) {
    strings.warn(strings.format(externalizedStrings.luInstallFailed, JSON.stringify(err)));
    strings.warn(externalizedStrings.luInstallInfo);
    callback();
  });
}

function createLUJsonFile(appDetails, callback) {
  var luJsonPath = path.join(process.cwd(), appDetails.appName, 'ionic', 'src', 'liveupdate.json');  
  log.verbose(consts.LOG_PREFIX, 'Creating file: ' + luJsonPath);

  const luJSON = {
    "features" : [],
    "properties" : []
  }
  fs.writeFile(luJsonPath, JSON.stringify(luJSON, null, 2), (err) => {
    if (err) {
      callback(err);
    } else {
      log.verbose(consts.LOG_PREFIX, 'Written file: ' + luJsonPath);
      callback();
    }
  });  
}

function modifyLUStudio(appDetails, callback) {
  var studioPath = path.join(process.cwd(), appDetails.appName, '.studio');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + studioPath);
  let studioJson = JSON.parse(fs.readFileSync(studioPath))

  studioJson.isLiveUpdateEnabled = true;

  fs.writeFile(studioPath, JSON.stringify(studioJson, null, 2), (err) => {
    if (err) {
      callback(err);
    } else {
      log.verbose(consts.LOG_PREFIX, 'Written file: ' + studioPath);
      callback();
    }
  });  
}

function modifyCustomTypings(appDetails, callback) {
  var customFilePath = path.join(process.cwd(), appDetails.appName, 'ionic', 'src', 'custom.typing.d.ts');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + customFilePath);
  fs.readFile(customFilePath, 'utf-8', function (customErr, customFileData)  {
    if (customErr) {
      callback(customErr);
    } else {
      customFileData = customFileData.concat(`/// <reference path="../plugins/cordova-plugin-mfp-liveupdate/typings/liveupdate.d.ts"/>`); 
      fs.writeFile(customFilePath, customFileData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + customFilePath);
          callback();
        }
      });        
    }
  });  
}

function modifyLUAppComponent(appDetails, callback) {
  var componentFilePath = path.join(process.cwd(), appDetails.appName, 'ionic', 'src', 'app', 'app.component.ts');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + componentFilePath);
  fs.readFile(componentFilePath, 'utf-8', function (customErr, componentData)  {
    if (customErr) {
      callback(customErr);
    } else {
      let extracted = S(componentData).between('});', '//').s;
      let initWithLU = extracted.concat(consts.LIVEUPDATE_IONIC_IMPL_CALL);
      componentData = S(componentData).replaceAll(extracted, initWithLU).s; 

      extracted = S(componentData).between('}', 'openPage').s;
      initWithLU = extracted.concat(consts.LIVEUPDATE_IONIC_IMPL);
      componentData = S(componentData).replaceAll(extracted, initWithLU).s; 

      fs.writeFile(componentFilePath, componentData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + componentFilePath);
          callback();
        }
      });        
    }
  });  
}

function installPushPlugin(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName, 'ionic');

  strings.info(externalizedStrings.pushInstalling);
  exec('ionic', ["cordova", "plugin", "add", "cordova-plugin-mfp-push@latest"], projectFolder).then(function() {
    callback();  
  }, function(err) {
    strings.warn(strings.format(externalizedStrings.pushInstallFailed, JSON.stringify(err)));
    strings.warn(externalizedStrings.pushInstallInfo);
    callback();
  });
}

function modifyPushStudio(appDetails, callback) {
  var studioPath = path.join(process.cwd(), appDetails.appName, '.studio');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + studioPath);
  let studioJson = JSON.parse(fs.readFileSync(studioPath))

  studioJson.isPush = true;

  fs.writeFile(studioPath, JSON.stringify(studioJson, null, 2), (err) => {
    if (err) {
      callback(err);
    } else {
      log.verbose(consts.LOG_PREFIX, 'Written file: ' + studioPath);
      callback();
    }
  });  
}

function modifyPushAppComponent(appDetails, callback) {
  var componentFilePath = path.join(process.cwd(), appDetails.appName, 'ionic', 'src', 'app', 'app.component.ts');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + componentFilePath);
  fs.readFile(componentFilePath, 'utf-8', function (customErr, componentData)  {
    if (customErr) {
      callback(customErr);
    } else {
      const extracted = S(componentData).between('this.platform.ready()', '});').s;
      const initWithPush = extracted.concat(consts.PUSH_IONIC_IMPL_CALL);
      componentData = S(componentData).replaceAll(extracted, initWithPush).s;      

      let tempIndex = componentData.lastIndexOf('}');
      let tempStr = componentData.substring(0, tempIndex - 1);   
      let replaceStr = tempStr.concat(consts.PUSH_IONIC_IMPL);
      componentData = componentData.replace(tempStr, replaceStr);  

      tempIndex = componentData.lastIndexOf('}');
      tempStr = componentData.substring(0, tempIndex + 1);   
      replaceStr = tempStr.concat(consts.PUSH_IONIC_STMTS);
      componentData = componentData.replace(tempStr, replaceStr);      

      fs.writeFile(componentFilePath, componentData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + componentFilePath);
          callback();
        }
      });        
    }
  });  
}

function modifyAnalyticsAppComponent(appDetails, callback) {
  var componentFilePath = path.join(process.cwd(), appDetails.appName, 'ionic', 'src', 'app', 'app.component.ts');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + componentFilePath);
  fs.readFile(componentFilePath, 'utf-8', function (customErr, componentData)  {
    if (customErr) {
      callback(customErr);
    } else {
      let extracted = S(componentData).between('});', '//').s;
      let initWithLU = extracted.concat(consts.ANALYTICS_IONIC_ENABLE);
      componentData = S(componentData).replaceAll(extracted, initWithLU).s; 

      fs.writeFile(componentFilePath, componentData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + componentFilePath);
          callback();
        }
      });        
    }
  });  
}

function modifyAnalyticsHomePage(appDetails, callback) {
  var homeFilePath = path.join(process.cwd(), appDetails.appName, 'ionic', 'src', 'pages', 'home', 'home.ts');  
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + homeFilePath);
  fs.readFile(homeFilePath, 'utf-8', function (customErr, homeData)  {
    if (customErr) {
      callback(customErr);
    } else {
      let extracted = S(homeData).between('ionViewDidLoad', '}').s;
      let initWithAnalytics = extracted.concat(consts.ANALYTICS_IONIC_LOG_CALL);
      homeData = S(homeData).replaceAll(extracted, initWithAnalytics).s; 


      let tempStr = homeData.substring(0, homeData.lastIndexOf("}"));
      let replaceStr = tempStr.concat(consts.ANALYTICS_IONIC_LOG);
      replaceStr = replaceStr.replace('{APP_NAME}', appDetails.appName);
      replaceStr = replaceStr.replace('{APP_ID}', appDetails.appId);
      homeData = homeData.replace(tempStr, replaceStr);

      fs.writeFile(homeFilePath, homeData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + homeFilePath);
          callback();
        }
      });        
    }
  });  
}
