/*
	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 propertiesParser = require('properties-parser');
var _ = require('lodash');

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 {
    createAndroidFolder(projectFolder);
    async.series([
      function(callback) {
        async.parallel([
          modifyManifest.bind(null, appDetails),
          modifyStrings.bind(null, appDetails),
          modifyAppGradle.bind(null, appDetails),
          modifyJavaPackage.bind(null, appDetails),
          modifyJavaTestPackage.bind(null, appDetails),
          modifyMfpClient.bind(null, appDetails, configDetails)
        ], function(err) {
            if (err) {
              callback(err);
            } else {
              callback();
            }
        })
      },
      function(callback) {
        if (appDetails.services.includes(consts.PUSH)) {
          const pushFile = fs.readFileSync(path.join(__dirname, '..', 'templates', 'push.json'));
          const pushJson = JSON.parse(pushFile);        
          async.parallel([
            modifyPushManifest.bind(null, pushJson.android.manifest, appDetails),
            modifyServiceAppGradle.bind(null, pushJson.android.appGradle, appDetails),
            modifyPushRootGradle.bind(null, pushJson.android.rootGradle, appDetails),
            modifyPushJavaFile.bind(null, appDetails),            
          ], function(err) {
              if (err) {
                callback(err);
              } else {
                callback();
              }
          })
        } else {
          callback();
        }
      },
      function(callback) {
        if (appDetails.services.includes(consts.LIVEUPDATE)) {
          const luFile = fs.readFileSync(path.join(__dirname, '..', 'templates', 'liveupdate.json'));
          const luJson = JSON.parse(luFile);        
          async.parallel([
            modifyServiceAppGradle.bind(null, luJson.android.appGradle, appDetails),
            modifyLUJavaFile.bind(null, appDetails, configDetails),            
          ], function(err) {
              if (err) {
                callback(err);
              } else {
                callback();
              }
          })
        } else {
          callback();
        }
      },
      function(callback) {
        if (appDetails.services.includes(consts.ANALYTICS)) {      
          async.parallel([
            modifyAnalyticsJavaFile.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 payload = {
          applicationKey: {
            version: appDetails.appVersion,
            clientPlatform: 'android',
            packageName: appDetails.appId,              
          },
          displayName: appDetails.appName,
        };          
        deferred.resolve(payload);
      }
    });
  } catch (error) {
    deferred.reject(error);
  }

  return deferred.promise;
};

function createAndroidFolder(projectFolder) {
  try {    
    var androidZipPath = path.join(__dirname, '..', 'templates', 'android.zip');
    var androidZip = new AdmZip(androidZipPath);
    androidZip.extractAllTo(projectFolder, true, true);
  } catch (error) {
    strings.error(error.message);
    throw error;
  }
}

function modifyManifest(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var manifestPath = path.join(projectFolder, 'app', 'src', 'main', 'AndroidManifest.xml');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + manifestPath);
  fs.readFile(manifestPath, 'utf-8', function (manifestErr, data)  {
    if (manifestErr) {
      callback(manifestErr);
    } else {
      xml2js.parseString(data, function (parseErr, manifestData) {
        if (parseErr) {
          callback(parseErr);
        } else {
          var manifestRoot = manifestData['manifest'];
          manifestRoot.$.package = appDetails.appId;
          var builder = new xml2js.Builder();
          var xml = builder.buildObject(manifestData);
          fs.writeFile(manifestPath, xml, function (modifiedManifestErr) {
            if (modifiedManifestErr) {
              callback(modifiedManifestErr);
            } else {
              log.verbose(consts.LOG_PREFIX, 'Written file: ' + manifestPath);
              callback();
            }
          });          
        }
      });
    }
  });
}

function modifyStrings(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var stringsPath = path.join(projectFolder, 'app', 'src', 'main', 'res', 'values', 'strings.xml');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + stringsPath);
  fs.readFile(stringsPath, 'utf-8', function (stringsErr, data)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      xml2js.parseString(data, function (parseErr, stringsData) {
        if (parseErr) {
          callback(parseErr);
        } else {
          var stringsRoot = stringsData['resources'];
          var stringElements = stringsRoot['string'];
          stringElements.forEach((stringElement, index) => {
            if (stringElement.$.name== 'app_name') {
              stringElement._ = appDetails.appName;
            }
          });
          
          var builder = new xml2js.Builder();
          var xml = builder.buildObject(stringsData);
          fs.writeFile(stringsPath, xml, function (modifiedStringsErr) {
            if (modifiedStringsErr) {
              callback(modifiedStringsErr);
            } else {
              log.verbose(consts.LOG_PREFIX, 'Written file: ' + stringsPath);
              callback();
            }
          });          
        }
      });
    }
  });
}

function modifyAppGradle(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var appGradlePath = path.join(projectFolder, 'app', 'build.gradle');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + appGradlePath);
  fs.readFile(appGradlePath, 'utf-8', function (stringsErr, gradleData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      gradleData = gradleData.replace('applicationId "com.ibm.mfpstarterandroid"', `applicationId "${appDetails.appId}"`);
      gradleData = gradleData.replace('versionName "1.0"', `versionName "${appDetails.appVersion}"`);
      fs.writeFile(appGradlePath, gradleData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + appGradlePath);
          callback();
        }
      });       
    }
  });
}


function modifyMfpClient(appDetails, configDetails, callback) {
  if (configDetails && configDetails.serverConfig) {
    var projectFolder = path.join(process.cwd(), appDetails.appName);
    const mfpClientPath = path.join(projectFolder, 'app', 'src', 'main', 'assets', 'mfpclient.properties'); 
    
    var server = JSON.parse(fs.readFileSync(configDetails.serverConfig));
    if (server.mfp) {
      propertiesParser.createEditor(mfpClientPath, function (err, editor) {
        if(err) {
          callback(err);
        } else {
          editor.set('wlServerProtocol', server.mfp.protocol);
          editor.set('wlServerHost', server.mfp.host);
          editor.set('wlServerPort', '' + server.mfp.port);
          editor.save(mfpClientPath, function(err) {
            if (err) {
              callback(err);
            } else {
              callback(); 
            }
          });      
        }
      });
    } else {
      callback();
    }
  } else {
    callback();
  }
}


function modifyJavaPackage(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var packagePath = path.join(projectFolder, 'app', 'src', 'main', 'java');
  var javaPath = path.join(projectFolder, 'app', 'src', 'main', 'java', 'com', 'ibm', 'mfpstarterandroid', 'MainApplication.java');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + javaPath);
  fs.readFile(javaPath, 'utf-8', function (stringsErr, javaData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      fs.unlinkSync(javaPath);
      fs.rmdirSync(path.join(projectFolder, 'app', 'src', 'main', 'java', 'com', 'ibm', 'mfpstarterandroid'));
      fs.rmdirSync(path.join(projectFolder, 'app', 'src', 'main', 'java', 'com', 'ibm'));
      fs.rmdirSync(path.join(projectFolder, 'app', 'src', 'main', 'java', 'com'));

      var folders = appDetails.appId.split('.');
      for (let i = 0; i < folders.length; i++) {
        packagePath = path.join(packagePath, folders[i]);
        fs.mkdirSync(packagePath);
      }
      javaData = javaData.replace('package com.ibm.mfpstarterandroid;', `package ${appDetails.appId};`);
      javaPath = path.join(packagePath, 'MainApplication.java');
      fs.writeFile(javaPath, javaData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + javaPath);
          callback();
        }
      });       
    }
  });
}

function modifyJavaTestPackage(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var packagePath = path.join(projectFolder, 'app', 'src', 'androidTest', 'java');
  var javaPath = path.join(projectFolder, 'app', 'src', 'androidTest', 'java', 'com', 'ibm', 'mfpstarterandroid', 'ApplicationTest.java');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + javaPath);
  fs.readFile(javaPath, 'utf-8', function (stringsErr, javaData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      fs.unlinkSync(javaPath);
      fs.rmdirSync(path.join(projectFolder, 'app', 'src', 'androidTest', 'java', 'com', 'ibm', 'mfpstarterandroid'));
      fs.rmdirSync(path.join(projectFolder, 'app', 'src', 'androidTest', 'java', 'com', 'ibm'));
      fs.rmdirSync(path.join(projectFolder, 'app', 'src', 'androidTest', 'java', 'com'));

      var folders = appDetails.appId.split('.');
      for (let i = 0; i < folders.length; i++) {
        packagePath = path.join(packagePath, folders[i]);
        fs.mkdirSync(packagePath);
      }
      javaData = javaData.replace('package com.ibm.mfpstarterandroid;', `package ${appDetails.appId};`);
      javaPath = path.join(packagePath, 'ApplicationTest.java');
      fs.writeFile(javaPath, javaData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + javaPath);
          callback();
        }
      });       
    }
  });
}

function modifyPushManifest(android, appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var manifestPath = path.join(projectFolder, 'app', 'src', 'main', 'AndroidManifest.xml');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + manifestPath);
  fs.readFile(manifestPath, 'utf-8', function (manifestErr, data)  {
    if (manifestErr) {
      callback(manifestErr);
    } else {
      xml2js.parseString(data, function (parseErr, manifestData) {
        if (parseErr) {
          callback(parseErr);
        } else {
          var manifestRoot = manifestData['manifest'];
          if (android.usesPermission) {
            if (!manifestRoot['uses-permission']) {
              manifestRoot['uses-permission'] = [];
            }        
            let missedPermissions = _.difference(android.usesPermission, 
                                                _.map(manifestRoot['uses-permission'], `$['android:name']`));
      
            if (missedPermissions.length > 0) {
              missedPermissions.forEach((permission) => {
                manifestRoot['uses-permission'].push({'$': {'android:name': permission}});
              });
            }
          }
          if (android.application) { 
            if (android.application.mainActivity) { 
              if (!manifestRoot['application'][0]['activity'][0]['intent-filter']) {
                manifestRoot['application'][0]['activity'][0]['intent-filter'] = [];
              }                 
              manifestRoot['application'][0]['activity'][0]['intent-filter'].push(android.application.mainActivity["intent-filter"]);           
            }               
            if (android.application.activities) {
              android.application.activities.forEach((activity) => {
                  manifestRoot['application'][0]['activity'].push(activity);                
              });
            }
            if (android.application.services) {
              if (!manifestRoot['application'][0]['service']) {
                manifestRoot['application'][0]['service'] = [];
              }                
              android.application.services.forEach((service) => {
                  manifestRoot['application'][0]['service'].push(service);                
              });
            } 
            if (android.application.metadata) {
              if (!manifestRoot['application'][0]['meta-data']) {
                  manifestRoot['application'][0]['meta-data'] = [];
              }                     
              android.application.metadata.forEach((meta) => {
                  manifestRoot['application'][0]['meta-data'].push(meta);                
              });
            }                             
          } 



          var builder = new xml2js.Builder();
          var xml = builder.buildObject(manifestData);
          xml = xml.replace('YOUR.PACKAGE_NAME.HERE', appDetails.appId);
          fs.writeFile(manifestPath, xml, function (modifiedManifestErr) {
            if (modifiedManifestErr) {
              callback(modifiedManifestErr);
            } else {
              log.verbose(consts.LOG_PREFIX, 'Written file: ' + manifestPath);
              callback();
            }
          });          
        }
      });
    }
  });
}

function modifyServiceAppGradle(android, appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var appGradlePath = path.join(projectFolder, 'app', 'build.gradle');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + appGradlePath);
  fs.readFile(appGradlePath, 'utf-8', function (stringsErr, gradleData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      if (android.dependencies) {
        const index = gradleData.search("dependencies");
        let tempStr = gradleData.substring(index);
        const tempIndex = tempStr.indexOf("{");
        tempStr = tempStr.substring(0, tempIndex + 1);     
        let replaceStr = tempStr;
        android.dependencies.forEach((dependency) => {  
          replaceStr = replaceStr.concat(`\n    ${dependency}`);         
        });
        gradleData = gradleData.replace(tempStr, replaceStr);
      }
      if (android.applyPlugins) {        
        android.applyPlugins.forEach((applyPlugin) => {  
          gradleData = gradleData.concat(`\n${applyPlugin}`);         
        });
      }  
      fs.writeFile(appGradlePath, gradleData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + appGradlePath);
          callback();
        }
      });       
    }
  });
}

function modifyPushRootGradle(android, appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var rootGradlePath = path.join(projectFolder, 'build.gradle');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + rootGradlePath);
  fs.readFile(rootGradlePath, 'utf-8', function (stringsErr, gradleData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      if (android.dependencies) {
        const index = gradleData.search("dependencies");
        let tempStr = gradleData.substring(index);
        const tempIndex = tempStr.indexOf("{");
        tempStr = tempStr.substring(0, tempIndex + 1);     
        let replaceStr = tempStr;     
        android.dependencies.forEach((dependency) => {  
          replaceStr = replaceStr.concat(`\n        ${dependency}`);         
        });
        gradleData = gradleData.replace(tempStr, replaceStr);
      } 
      fs.writeFile(rootGradlePath, gradleData, (err) => {
        if (err) {
          callback(err);
        } else {
          log.verbose(consts.LOG_PREFIX, 'Written file: ' + rootGradlePath);
          callback();
        }
      });       
    }
  });
}

// TODO - a) Revisit below methods to make the parsing robust instead of string manipulation
//        b) Get the code snippet from json template
function modifyPushJavaFile(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var packagePath = path.join(projectFolder, 'app', 'src', 'main', 'java'); 
  var folders = appDetails.appId.split('.');
  for (let i = 0; i < folders.length; i++) {
    packagePath = path.join(packagePath, folders[i]);
  }

  var javaPath = path.join(packagePath, 'MainApplication.java');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + javaPath);
  fs.readFile(javaPath, 'utf-8', function (stringsErr, javaData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      let tempIndex = javaData.indexOf("public");
      let tempStr = javaData.substring(0, tempIndex - 1);     
      let replaceStr = tempStr.concat(consts.PUSH_ANDROID_IMPORTS);
      javaData = javaData.replace(tempStr, replaceStr);



      tempIndex = javaData.indexOf("private");
      tempStr = javaData.substring(0, tempIndex);     
      replaceStr = tempStr.concat(consts.PUSH_ANDROID_DECL_CALL);    
      javaData = javaData.replace(tempStr, replaceStr);


      tempIndex = javaData.indexOf("}");
      tempStr = javaData.substring(0, tempIndex - 1);     
      replaceStr = tempStr.concat(consts.PUSH_ANDROID_IMPL_CALL);    
      javaData = javaData.replace(tempStr, replaceStr);

      tempIndex = javaData.indexOf("}");
      tempStr = javaData.substring(0, tempIndex + 1);     
      replaceStr = tempStr.concat(consts.PUSH_ANDROID_IMPL);                                                                                                     
   
      javaData = javaData.replace(tempStr, replaceStr);        

      tempStr = javaData.substring(0, javaData.lastIndexOf("}"));
      replaceStr = tempStr.concat(consts.PUSH_ANDROID_LISTENERS);    
      javaData = javaData.replace(tempStr, replaceStr);   


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

function modifyLUJavaFile(appDetails, configDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var packagePath = path.join(projectFolder, 'app', 'src', 'main', 'java'); 
  var folders = appDetails.appId.split('.');
  for (let i = 0; i < folders.length; i++) {
    packagePath = path.join(packagePath, folders[i]);
  }

  var javaPath = path.join(packagePath, 'MainApplication.java');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + javaPath);
  fs.readFile(javaPath, 'utf-8', function (stringsErr, javaData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      let tempIndex = javaData.indexOf("public");
      let tempStr = javaData.substring(0, tempIndex - 1);     
      let replaceStr = tempStr.concat(consts.LIVEUPDATE_ANDROID_IMPORTS);
   
      javaData = javaData.replace(tempStr, replaceStr);

      tempIndex = javaData.indexOf("}");
      tempStr = javaData.substring(0, tempIndex - 1);     
      replaceStr = tempStr.concat(consts.LIVEUPDATE_ANDROID_IMPL_CALL);    
      javaData = javaData.replace(tempStr, replaceStr);

      tempIndex = javaData.indexOf("}");
      tempStr = javaData.substring(0, tempIndex + 1);     
      replaceStr = tempStr.concat(consts.LIVEUPDATE_ANDROID_IMPL);    
      javaData = javaData.replace(tempStr, replaceStr);      

      let liveUpdateSchema = '';
      if (configDetails && configDetails.appConfig) {
        const appConfig = JSON.parse(fs.readFileSync(configDetails.appConfig));
        if (appConfig.services) {
          appConfig.services.forEach(function(service) {
            if (service.name == consts.LIVEUPDATE && service.schema) {
              liveUpdateSchema = liveUpdateSchema.concat(`try {\
                                                        \n                org.json.JSONObject luObject = new org.json.JSONObject();\n`);
            
              if (service.schema.features) {
                Object.keys(service.schema.features).forEach(function(schemaFeature) {
                  const feature = service.schema.features[schemaFeature];
                  liveUpdateSchema = liveUpdateSchema.concat(`                luObject.put("${feature.name}", configuration.isFeatureEnabled("${feature.name}"));\n`);
                })
              }
              if (service.schema.properties) {
                Object.keys(service.schema.properties).forEach(function(schemaProperty) {
                  const property = service.schema.properties[schemaProperty];
                  liveUpdateSchema = liveUpdateSchema.concat(`                luObject.put("${property.name}", configuration.getProperty("${property.name}"));\n`);
                })
              } 
              liveUpdateSchema = liveUpdateSchema.concat(`                changeStatusMessage("Live update schema:" + luObject.toString(2));\
                                                        \n            } catch (org.json.JSONException e) {\
                                                        \n                changeStatusMessage("Failed to obtain live update configuration");\
                                                        \n            }`);
              
            }
          });
        }
      }

      tempStr = javaData.substring(0, javaData.lastIndexOf("}"));
      replaceStr = tempStr.concat(consts.LIVEUPDATE_ANDROID_LISTENERS);  
      replaceStr = replaceStr.replace('{liveUpdateSchema}', liveUpdateSchema);
      javaData = javaData.replace(tempStr, replaceStr);   


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

function modifyAnalyticsJavaFile(appDetails, callback) {
  var projectFolder = path.join(process.cwd(), appDetails.appName);
  var packagePath = path.join(projectFolder, 'app', 'src', 'main', 'java'); 
  var folders = appDetails.appId.split('.');
  for (let i = 0; i < folders.length; i++) {
    packagePath = path.join(packagePath, folders[i]);
  }

  var javaPath = path.join(packagePath, 'MainApplication.java');
  log.verbose(consts.LOG_PREFIX, 'Reading file: ' + javaPath);
  fs.readFile(javaPath, 'utf-8', function (stringsErr, javaData)  {
    if (stringsErr) {
      callback(stringsErr);
    } else {
      let tempIndex = javaData.indexOf("public");
      let tempStr = javaData.substring(0, tempIndex - 1);     
      let replaceStr = tempStr.concat(consts.ANALYTICS_ANDROID_IMPORTS);
   
      javaData = javaData.replace(tempStr, replaceStr);

      tempIndex = javaData.indexOf("}");
      tempStr = javaData.substring(0, tempIndex - 1);     
      replaceStr = tempStr.concat(consts.ANALYTICS_ANDROID_IMPL_CALL);    
      javaData = javaData.replace(tempStr, replaceStr);

      tempIndex = javaData.indexOf("}");
      tempStr = javaData.substring(0, tempIndex + 1);     
      replaceStr = tempStr.concat(consts.ANALYTICS_ANDROID_IMPL);  
      replaceStr = replaceStr.replace('{appDetails.appName}', appDetails.appName);  
      javaData = javaData.replace(tempStr, replaceStr);      

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




