/*
 * Copyright (C) SayMama Ltd 2011
 *
 * All rights reserved. Any use, copying, modification, distribution and selling
 * of this software and it's documentation for any purposes without authors' written
 * permission is hereby prohibited.
 */


/**
 *===========================================================================================================
 * Scripts for embeding and initializing saymama Plug-In
 *===========================================================================================================
 */


/**
 *===========================================================================================================
 *                                      PluginWrapper
 *===========================================================================================================
 */


function PluginWrapper(configuration) {
  this.mimeType = configuration.mimeType;
  this.classId = configuration.classId;
  this.testMethod = configuration.testMethod;

  this.log = new LoggerWrapper( configuration.logger ? configuration.logger : new NullLogger());
  this.POLL_INTERVAL = 500;
  this.objectId = generateObjectTagId();
  this.jsId = 'js_' + this.objectId;
  this.params = {}
  this.attributes = {}
  this.polling = false;
  
}


/**
 *  Initializes process of polling for plugin
 */
PluginWrapper.prototype.startPolling = function (handler) {
  this.log.debug("[PluginWrapper] Starting polling for plugin of type: " + this.mimeType)
  var self = this
  this.pollingHandler = handler
  if(this.polling == true) {
    this.log.warn("[PluginWrapper] Polling already started. Just updated handler");
    return;
  }
  this.polling = true;
  this.startPollingInternal()
}

/**
 *  Initializes process of polling for plugin
 */
PluginWrapper.prototype.startPollingInternal = function () {

  var self = this
  setTimeout(function() {
    self.pollForPlugin(self.callbackHandler);
  }, this.POLL_INTERVAL);
}


/**
 * Checks whether plugin can be loaded, sets timer if it failes
 */
PluginWrapper.prototype.pollForPlugin = function() {
  this.log.debug("[PluginWrapper] Polling for plugin...");
  var loadStatus = this.loadPlugin();
  if (!loadStatus) {
    this.log.debug("[PluginWrapper] failed to load the plugin, retrying");
    this.startPollingInternal()
  } else {
    this.log.debug("[PluginWrapper] Plugin loaded, notyfing listener");
    this.pollingHandler()
  }
}

/**
 *  Tries  to load plugin, by embeding object tag
 */
PluginWrapper.prototype.loadPlugin = function() {
  this.log.debug("[PluginWrapper] Trying to embed plugin");
  navigator.plugins.refresh ();
  if(!this.pluginInstalled()) {
    this.log.warn("[PluginWrapper] Pre load tests shows that plugin isn't installed. Skipping");
    return false;
  }
  var pluginContainerId = this.pluginContainerId;
  if (!pluginContainerId) {
    this.log.error("[PluginWrapper] Cannot embed plugin: pluginContainerId was not specified");
    return false;
  }
  this.log.debug("[PluginWrapper] Setting up OBJECT tag, tag id: " + this.id);
  var attrString = "";
  for(var key in this.attributes) {
    attrString += key + '="'  + this.attributes[key] + '" ';
  }

  var tagContent = '<object type="' + this.mimeType + '" width="0" height="0" id="' + this.objectId + '" ' +attrString + '>'+
  '  <object classid="' + this.classId + '" width="0" height="0" id="' + this.objectId + '" ' +attrString + '>' ;
  for(var key in this.params) {
    tagContent += '<param name="' + key +'" value="' + this.params[key] + '"/>'
  }

  tagContent += '  </object>' +
  '</object>';
  this.log.debug("[PluginWrapper] Resetting innerHTML of container");
  document.getElementById(pluginContainerId).innerHTML = '';
  document.getElementById(pluginContainerId).innerHTML = tagContent;
  this.log.debug("[PluginWrapper] OBJECT tag added to DOM. Testing for method: " + this.testMethod);

  this.pluginInstance = document.getElementById(this.objectId);
  var result = (this.testMethod == null || this.testMethod in this.pluginInstance);
  if(!result) {
    this.log.warn("[PluginWrapper] Plugin " + this.mimeType + " seems not to be installed")
  }
  return result;
};


PluginWrapper.prototype.unload = function() {
    document.getElementById(this.pluginContainerId).innerHTML = '';
}


/**
 * ============================================================
 * Main initialization function.
 * ============================================================
 */
PluginWrapper.prototype.initialize = function() {
  // this method is just an alias to keep API intact
  return this.loadPlugin();
}


PluginWrapper.prototype.pluginInstalled = function() {
  /**
   *  Mock implementation override to for fine tune check
   */
  return true;
}

/**
 *
 */
PluginWrapper.prototype.setLogger = function(logger) {
  this.log = new LoggerWrapper(logger ? logger : new NullLogger());
  this.log.debug("[PluginWrapper] Logging initialized")
};


/**
 *===========================================================================================================
 *                                      SayMamaPlugin
 *===========================================================================================================
 */


/**
 * Saymama wrapping object.
 *
 * @param pluginContainerId - id of the HTML element where plugin OBJECT tag should be embedded. This element must be
 *                            statically defined in the DOM (i.e. it cannot be appended dynamically with JavaScript).
 */
function SayMamaPlugin(pluginContainerId) {
  this.pluginContainerId = pluginContainerId;
  this.INSTALLER_ID = 'pluginInstaller'
  this.INSTALL_STATUS_INSTALLED = "INSTALLED";
  this.INSTALL_STATUS_ERROR = "ERROR";
  this.MISSING_JRE = 150
  this.INSTALL_IN_PROGRESS_ERROR_CODE = 151
  this.UNKNOWN_ERROR_CODE = 152;
  this.FAILED_TO_INIT_AFTER_INSTALL = 153;

  this.appletInstaller = false;
  this.extensionInstaller = false;

}

var SAYMAMA_PLUGIN_CONFIG ={
  mimeType: "application/x-saymamaplugin",
  classId:"clsid: 051e3002-6ebb-5b93-9382-f13f091b2ab2",
  testMethod: "createService"
}

SayMamaPlugin.prototype = new PluginWrapper(SAYMAMA_PLUGIN_CONFIG)
SayMamaPlugin.prototype.constructor = SayMamaPlugin;





SayMamaPlugin.prototype.installViaJavaApplet = function(callbackHandler, jarUrl, updateDescriptorURL) {
  this.log.debug("[SayMamaPlugin] Performing Applet based installation")
  if(this.appletInstaller != false) {
    this.log.warn("[SayMamaPlugin] Applet already running, skipping")
    callbackHandler.installStatus(this.INSTALL_STATUS_ERROR, this.INSTALL_IN_PROGRESS_ERROR_CODE);
  }
  var self = this;
  var callbackWrapper = {
    installProgress: callbackHandler.installProgress,
    installStatus: function(status, errCode, errMsg) {
      if(status == self.INSTALL_STATUS_INSTALLED) {
        self.loadPluginAfterExtInstallation(callbackHandler, 0);
      } else {
        callbackHandler.installStatus(status, errCode, errMsg);
      }
    }
  }
  if(navigator.userAgent.match(/Firefox\/3.[0-9]+.[0-9]+/) != null && // it it's FF 3.X'
    navigator.platform === "MacIntel" /* running on mac */) {
    // use workaround for #570
    this.log.warn("[SayMamaPlugin] Script running on FF 3.X, on Mac - there won't be any notifications so I'm starting to poll for plugin")
    this.startPolling(function(){
      callbackHandler.installStatus(self.INSTALL_STATUS_INSTALLED)
    })
  }
  this.appletInstaller = new SayMamaAppletInstaller(this.pluginContainerId, callbackWrapper, jarUrl, updateDescriptorURL)
  this.appletInstaller.log = this.log
  if(!this.appletInstaller.initialize()) {
    this.log.error("[SayMamaPlugin] Failed to initialize applet.")
    callbackHandler.installStatus(this.INSTALL_STATUS_ERROR, this.MISSING_JRE);
  }
}

SayMamaPlugin.prototype.installationExtensionInstalled = function() {
    this.log.debug("Checking whether installation extension is installed.")
    var installerTmp = new SayMamaExtensionInstaller(this.pluginContainerId);  
    var status = installerTmp.initialize();
    this.log.debug("Result: " + status);
    installerTmp.unload();
    return status;

}

SayMamaPlugin.prototype.installViaExtension = function(installListener, updateDescr) {
  this.log.debug("[SayMamaPlugin] Performing extension based installation")
  this.extInstListener = installListener;
  this.updateDescr = updateDescr;
  if(!this.extensionInstaller) {
    this.extensionInstaller = new SayMamaExtensionInstaller(this.pluginContainerId);  
  }
  this.extensionInstaller.log = this.log
  this.log.debug("[SayMamaPlugin] embedding extension plugin")
  if(this.extensionInstaller.initialize()) {
    this.extensionInstallerReady()
  } else {
    var self = this;
    this.extensionInstaller.startPolling(function(){
      self.extensionInstallerReady();
    })
  }
}

SayMamaPlugin.prototype.loadPluginAfterExtInstallation = function(callbackHandler, tryNo) {
  this.log.debug("[SayMamaPlugin] Trying to load plugin after extension installation. Try: " + tryNo);
  if(tryNo > 20) {
    this.log.error("[SayMamaPlugin] Reached retry limit - although plugin installer thinks that installation was successfull, it wasn't");
    callbackHandler.installStatus(this.INSTALL_STATUS_ERROR, this.FAILED_TO_INIT_AFTER_INSTALL);
    return;
  }
  var status = this.initialize();
  if(status) {
    this.log.debug("[SayMamaPlugin] Plugin successfully initialized. Notifying listener")
    callbackHandler.installStatus(this.INSTALL_STATUS_INSTALLED);
  }
  else {
    var self = this
    setTimeout(function(){
      self.loadPluginAfterExtInstallation(callbackHandler, tryNo + 1);
    }, 500);

  }
}

SayMamaPlugin.prototype.extensionInstallerReady = function() {
  this.log.debug("[SayMamaPlugin] Plugin extension ready - performing installation")
  var callbackHandler = this.extInstListener
  var self = this
  var callbackWrapper = {
    installProgress: callbackHandler.installProgress,
    installStatus: function(status, errCode, errMsg) {
      if(status == self.INSTALL_STATUS_INSTALLED) {
        self.loadPluginAfterExtInstallation(callbackHandler, 0);
      } else {
        callbackHandler.installStatus(status, errCode, errMsg);
      }
    }
  }
  this.extensionInstaller.doInstall(this.updateDescr, callbackWrapper)
}

/**
 * ============================================================
 * Delegates to container
 * ============================================================
 */

SayMamaPlugin.prototype.createService = function(responder) {
  this.log.debug("[SayMamaPlugin] Creating new plugin service instance");
  this.pluginInstance.createService(responder);
}

 
    
SayMamaPlugin.prototype.update = function(responder, url) {
  this.log.debug("[SayMamaPlugin] Updating plugin");
  this.pluginInstance.update(responder, url);
}
  
  
SayMamaPlugin.prototype.revalidate = function() {
  this.log.debug("[SayMamaPlugin] Revalidating plugin");
  this.pluginInstance.revalidate();
  
}

SayMamaPlugin.prototype.invalidate = function() {
  this.log.debug("[SayMamaPlugin] Invalidating plugin");
  this.pluginInstance.invalidate();
  
}


/**
 *===========================================================================================================
 *                                      SayMamaAppletInstaller
 *===========================================================================================================
 */



function SayMamaAppletInstaller(containerId, callbackHandler, jarUrl, updateDescriptorUrl) {
  this.INSTALLER_CLASS_MAC = 'com.saymama.smpappletinstaller.SayMamaMacAppletInstaller';
  this.INSTALLER_CLASS_WIN = 'com.saymama.smpappletinstaller.SayMamaWinAppletInstaller';
  this.INSTALL_STATUS_INSTALLED = "INSTALLED";
  this.INSTALL_STATUS_ERROR = "ERROR";
  this.JRE_MIN_VERSION = "1.5"
  this.TEST_JAR = "getJavaInfo.jar"
  this.pluginContainerId = containerId;
  var macPlatform = navigator.appVersion.indexOf("Win") ==-1;
  if(macPlatform) {
    var installerClass = this.INSTALLER_CLASS_MAC
  } else {
    installerClass = this.INSTALLER_CLASS_WIN
  }

  this.params = {
    code:installerClass,
    archive:jarUrl,
    bundleUrl: updateDescriptorUrl,
    listener : this.jsId,
    sleepInterval: 0,
    name: "SayMama Installer"
  }
  this.attributes = this.params;
  this.callbackHandler = callbackHandler;
  window[this.jsId] = this;
}

var JRE15_CONFIG = {
  mimeType: "application/x-java-applet;version=1.5",
    
  /* The classId, taken from: http://www.oracle.com/technetwork/java/javase/family-clsid-140615.html */
  classId:"clsid:CAFEEFAC-0015-0000-FFFF-ABCDEFFEDCBA",
    
  testMethod: null
}

SayMamaAppletInstaller.prototype = new PluginWrapper(JRE15_CONFIG)
SayMamaAppletInstaller.prototype.constructor = SayMamaAppletInstaller;



SayMamaAppletInstaller.prototype.installationError = function(code, msg) {
  this.log.error("Got installation error: " + code + " - " + msg)
  this.callbackHandler.installStatus(this.INSTALL_STATUS_ERROR, code, msg)
}

SayMamaAppletInstaller.prototype.installationProgress = function(progress) {
  var self = this;
  this.callbackHandler.installProgress(progress)
  if(progress == 100) {
    this.log.debug("Got 100% progress.");
    setTimeout(function() {
      self.log.debug("Timeout triggered. Initializing");
      
      self.callbackHandler.installStatus(self.INSTALL_STATUS_INSTALLED)
      
      
    }, 3000)
  }
}


SayMamaAppletInstaller.prototype.installationLog = function(msg) {
  this.log.debug('[APPLET] ' + msg)
}

SayMamaAppletInstaller.prototype.pluginInstalled = function() {
  return this.testForJREUsingMimeType() || this.testUsingPluginsArray() || this.testUsingActiveX();
}


function getInternetExplorerVersion()
// Returns the version of Internet Explorer or a -1
// (indicating the use of another browser).
{
  var rv = -1; // Return value assumes failure.
  if (navigator.appName == 'Microsoft Internet Explorer')
  {
    var ua = navigator.userAgent;
    var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
    if (re.exec(ua) != null)
      rv = parseFloat( RegExp.$1 );
  }
  return rv;
}

SayMamaAppletInstaller.prototype.testUsingActiveX = function() {
  this.log.debug("Trying to test for JRE under IE");

  if(getInternetExplorerVersion() == -1) {
    this.log.warn("Browser isn't IE - skipping test")
    return false;
  }
  this.log.debug("Preparing test object")
  var pluginContainerId = this.pluginContainerId;
  if (!pluginContainerId) {
    this.log.error("Cannot embed plugin: pluginContainerId was not specified");
    return false;
  }
  var attrString = 'code="A" archive="getJavaInfo.jar" name="Test" ';
  var tagContent = '<object type="' + this.mimeType + '" width="0" height="0" id="' + this.objectId + '" ' +attrString + '>'+
  '  <object classid="' + this.classId + '" width="0" height="0" id="' + this.objectId + '" ' +attrString + '>' ;

  tagContent += '<param name="code" value ="A"/> <param name="archive" value ="getJavaInfo.jar"/> <param name="name" value ="Test"/>  </object></object>';
    
  this.log.debug("Trying to append object");
  document.getElementById(pluginContainerId).innerHTML = '';
  document.getElementById(pluginContainerId).innerHTML = tagContent;
  this.log.debug("Object appended, testing method getVersion");

  this.pluginInstance = document.getElementById(this.objectId);
  var result = ("getVersion" in this.pluginInstance);
  if(!result) {
    this.log.warn("Plugin " + this.mimeType + " seems not to be installed")

  } else {
    var version = "FAKE" //this.pluginInstance.getVersion();
    this.log.debug("Got JRE, version: " + version);
  }

  document.getElementById(pluginContainerId).innerHTML = '';
  return result;
}

SayMamaAppletInstaller.prototype.testForJREUsingMimeType = function() {
  var version = this.JRE_MIN_VERSION;
  this.log.debug("Trying to find JRE with version: " + version);
  if(!navigator.mimeTypes){
    this.log.error("Navigator doesn't have mimeTypes field defined")
    return false;
  }
  for(var i=0;i<navigator.mimeTypes.length;++i){
    var type = navigator.mimeTypes[i].type;
    if(type.match(/^application\/x-java-applet;version=1\.5$/) != null){
      this.log.debug("Got JRE: " + type);
      return true;
    }
  }
  this.log.debug("Couldn't find the JRE using mime types.");
  return false;
}

SayMamaAppletInstaller.prototype.testUsingPluginsArray = function(){
        
  var version = this.JRE_MIN_VERSION;
  this.log.debug("Trying to find JRE in version " + version + " using plugins array" )
  if((!navigator.plugins) || (!navigator.plugins.length)){
    this.log.warn("Either plugins not defined or plugins array is empty");
    return false;
  }
  var platform = navigator.platform.toLowerCase();
  for(var i=0 ; i<navigator.plugins.length ; ++i){
    var s = navigator.plugins[i].description;
    if(s.search(/^Java Switchable Plug-in (Cocoa)/)!=-1){
      if(this.compareVersions("1.5.0",version)){
        return true;
      }
    }else if(s.search(/^Java/)!=-1){
      if(platform.indexOf('win') != -1){
        if(this.compareVersions("1.5.0",version) || this.compareVersions("1.6.0",version)){
          return true;
        }
      }
    }
  }
  return false;
}


SayMamaAppletInstaller.prototype.compareVersions = function(installed,required){
  var a=installed.split('.');
  var b=required.split('.');
  for(var i=0;i<a.length;++i){
    a[i]=Number(a[i]);
  }
  for(var i=0;i<b.length;++i){
    b[i]=Number(b[i]);
  }
  if(a.length==2){
    a[2]=0;
  }
  if(a[0]>b[0])return true;
  if(a[0]<b[0])return false;
  if(a[1]>b[1])return true;
  if(a[1]<b[1])return false;
  if(a[2]>b[2])return true;
  if(a[2]<b[2])return false;
  return true;
}

/**
 *===========================================================================================================
 *                                      SayMamaExtensionInstaller
 *===========================================================================================================
 */


var NPAPI_INSTALLER_CONFIG = {
  mimeType: "application/x-smplugininstaller",
  /* It will be used only on FF or chrome - no need to use classId */
  classId:"",
  testMethod: "echo"
}

function SayMamaExtensionInstaller(containerId) {
  this.pluginContainerId = containerId;
  this.macPlatform = navigator.appVersion.indexOf("Win") == -1;
  var browser = navigator.userAgent.toLowerCase();
  this.chrome = browser.indexOf('chrome') != -1;
  this.INSTALL_STATUS_INSTALLED = "INSTALLED";
  this.INSTALL_STATUS_ERROR = "ERROR";
  this.POST_KILL_PRE_REFRESH_SLEEP = 1000;
  this.POST_INSTALL_PRE_KILL_SLEEP = 2000;
    
}


SayMamaExtensionInstaller.prototype = new PluginWrapper(NPAPI_INSTALLER_CONFIG)
SayMamaExtensionInstaller.prototype.constructor = SayMamaExtensionInstaller;


SayMamaExtensionInstaller.prototype.doInstall = function(updateDescriptor, responder) {
  this.log.debug("Calling install plugin");
  var self = this
  var listenerWrapper = {
    installProgress: function(prgrs) {
      self.log.debug("Got progres: " + prgrs);
      if(prgrs == 100) {
        setTimeout(function(){
          self.restartChromeFlash();
              
        }, this.POST_INSTALL_PRE_KILL_SLEEP)
      }
      responder.installProgress(prgrs)
    },
    onError: function(errCode, errMsg){
      self.log.error("Got error status. Code: " + errCode + ". msg: " + errMsg)
      responder.installStatus(self.INSTALL_STATUS_ERROR, errCode, errMsg)
    }
  }
  this.pluginInstance.installPlugin(updateDescriptor, listenerWrapper)
}

SayMamaExtensionInstaller.prototype.restartChromeFlash = function() {
  this.log.debug("Calling kill flash");
  var self = this;
  if(this.macPlatform && this.chrome) {
    setTimeout(function(){
      var customEvent = document.createEvent('Event');
      customEvent.initEvent('refreshRequest', true, true);
      document.dispatchEvent(customEvent);
        
    }, this.POST_KILL_PRE_REFRESH_SLEEP);
  } else {
    this.log.debug("Running on windows or firefox - skipping restart");
    responder.installStatus(self.INSTALL_STATUS_INSTALLED)          
  }
}



function generateObjectTagId() {
  var text = "plugin_";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < 5; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

function LoggerWrapper(target) {
  
  this.target = target
  
  this.trace = function(msg) {
    this.target.trace("  [SayMamaPlugin.js] " + msg)    
  };
  this.debug = function(msg) {
        this.target.debug("  [SayMamaPlugin.js] " + msg)

  };
  this.info = function(msg) {
        this.target.info("  [SayMamaPlugin.js] " + msg)

  };
  this.warn = function(msg) {
        this.target.warn("  [SayMamaPlugin.js] " + msg)

  };
  this.error = function(msg) {
        this.target.error("  [SayMamaPlugin.js] " + msg)

  };
}

function NullLogger() {
  this.trace = function() {
  };
  this.debug = function() {
  };
  this.info = function() {
  };
  this.warn = function() {
  };
  this.error = function() {
  };
}
