/**
 * File: SetEnvironment.js
 * Author: Rafal Raposo - O Rapouso - orapouso@gmail.com
 * 
 * This file should work like some sort of "pre compilator" for Javascript,
 * setting the environment for the future to come. As Javascript is not a
 * Object Oriented language, this file has all the needed things for you to
 * work as it were similar to Java. The more you use this,
 * the more you will get used to develop with it.
 *
 */

top.errno = -1;

top.alertError = function() {
  alert(errno);
}

String.prototype.trim = function() {
  return this.replace(/(^\s*)|(\s*$)/g,"");
};

String.prototype.hashCode = function() {
  var result = 0;
  for (var i=0; i<this.length; i++)
    result += Math.pow(this.charCodeAt(i)*31, this.length-(i+1));
  return result;
}

/**
 * Moves every array position one step back starting at index until the end.
 *
 * @param int - array index from which you wish to move it back
 */
Array.prototype.moveBack = function(index) {
	for(var i = index; i < this.length-1; i++) {
	  this[i] = this[i+1];
	}
	this[i] = null;
	this.length--;
}

/**
 * Moves every array position one step foward and leaves the index position
 * with trash or nothing.
 *
 * @param int - array index you wish to leave empty to insert a new value
 */
Array.prototype.moveFoward = function(index) {
	for(var i = this.length; i > index; i--) {
    this[i] = this[i-1];
  }
}

/**
 * Inserts one to N elements to the end of the array and returns its new
 * length.
 *
 * Designed because some browsers don't support this native method.
 *
 * @param Object(...) - elemenst to be inserted to the array's end
 * @return int - the new array's length
 */
Array.prototype.push = function() {
  for(var i = 0 ; i < arguments.length ; i++) {
    this[this.length] = arguments[i];
  }
  return this.length;
}

/*
 * Removes the last element from the array and return its value.
 *
 * Designed because some browsers don't support this native method.
 *
 * @return Object - element at the last position of the array
 *                  undefined if the array is empty
 */
Array.prototype.pop = function() {
  return this[this.length];
}

//holds the original class when a class is extended
Object.prototype.Class = null;

//every object knows which class it extends
Object.prototype.extendedClass = "";

//every object knows which classes it implements
Object.prototype.implementedClasses = new Array();

/**
 * Same as "equals" in Java
 *
 * @param Object   - object to equalize
 * @return boolean - true if objects are equal, false otherwise
 */
Object.prototype.equals = function(o) {
  return (this == o);
}

/**
 * Returns a hash code value for the object.
 *
 * @return int - a hash code value for the object
 */
Object.prototype.hashCode = function() {
  return this.toString().hashCode();
}

/**
 * Returns the object's runtime Class
 *
 * @return Object - object's Class
 */
Object.prototype.getClass = function() {
  return (this.Class == null) ? this.constructor : this.Class;
}

/**
 * Returns the object's Class name
 *
 * @return String - object's Class name
 */
Object.prototype.getClassName = function() {
  var sClass = this.getClass().toString();
  
  return (sClass.substring(9, sClass.indexOf("(")).trim());
}

/**
 * Adds a propertie to an object with a set and get method for it.
 * Also adds a get and set methods for the new property being added
 *
 * @param String - name of the propertie
 * @param any    - (optional) initial propertie value
 */
Object.prototype.addGProp = function (sName, vValue) {
  this[sName] = vValue;
       
  var sFuncName = sName.charAt(0).toUpperCase() + sName.substring(1, sName.length);
  
  this["get" + sFuncName] = function () {return this[sName];};
  this["set" + sFuncName] = function (vNewValue) {this[sName] = vNewValue;};
}

/**
 * Adds a typed property to a class with a set and get method for it. If the
 * initial value of the propertie is a primitive type, the third parameter
 * is not obrigatory, but if it is an object, its class must be informed.
 * For this "typing try" to work, you should always change the value of any
 * property with its set method. Inside it there is the type checking. If the
 * new value type is not from propertie's initial value type, the set method
 * forbids the new value to be set and alerts the user with an error.
 *
 * @param String   - propertie name
 * @param any      - initial properte value
 * @param Function - propertie class if its initial value is an object
 */
Object.prototype.addTProp = function (sName, vValue, cClass) {
  var sType = (typeof vValue);
  
  if(sType == "undefined" && !cClass) {
    alert("The initial value of property " + sName + " must be set.");
    return false;
  }
  
  if(cClass) {
    if(!(vValue instanceof cClass) && vValue != null) {
      alert("The initial value assigned is not an instance of class informed.");
      return false;
    }
  }

  this[sName] = vValue;
       
  var sFuncName = sName.charAt(0).toUpperCase() + sName.substring(1, sName.length);
  
  this["get" + sFuncName] = function () { return this[sName]; };
  this["set" + sFuncName] = function (vNewValue) {

    if ((typeof vNewValue) != sType) {
      alert("Property " + sName + " must be of type " + sType + ".");
      return;
    }
    
    if(cClass) {
      if(sType == "object") {
        if(!(vNewValue instanceof cClass)) {
          var className = cClass.toString().substring(9, cClass.toString().indexOf("("));
          alert("New value assigned is not from class " + className);
          return;
        }
      }
    }
  
    this[sName] = vNewValue;
  };
}

/**
 * Adds a propertie to an object with a set and get method for it. This property
 * looks like the a "final" JAVA propertie. The value cannot be changed, but this
 * will only work if you try to change it with its set method that forbids any
 * value change.
 *
 * @param String - propertie name
 * @param any    - propertie initial value
 */
Object.prototype.addFProp = function (sName, vValue) {
  var sType = (typeof vValue);
  
  if(sType == "undefined") {
    alert("The initial value of property " + sName + " must be set.");
    return;
  }

  this[sName] = vValue;
       
  var sFuncName = sName.charAt(0).toUpperCase() + sName.substring(1, sName.length);
  
  this["get" + sFuncName] = function () { return this[sName]; };
  this["set" + sFuncName] = function (vNewValue) {
    alert("The property " + sName + " is set to final and cannot have its value changed.");
  };
}

top.check = false; //if true, checks for errors
top.extensionErrors = new Array();
top.implementationErrors = new Array();
top.abstractionErrors = new Array();

//list of classes to implement.
//[0]=list of implementing classes
//[1]=list of classes being implemented
top.implementsClassList =  new Array(new Array(), new Array());

//list of classes to extend.
//[0]=list of sub classes
//[1]=list of super classes
top.extendsClassList = new Array(new Array(), new Array());

/**
 * This acts like the "extends" in Java. The only diference is that in Javascript
 * you need to call this method right after the Class code. You give it a
 * String with the name of the class to be extended and the system takes care of
 * the rest. Of course you can only extend one time for each class, like in Java.
 * If you extend a sencond time, this one will be ignored.
 *
 * Extension example:
 * ClassA.extendss("ClassB");
 * This way ClassB will be the super class of ClassA
 *
 * @param String   - super class name
 * @return boolean - false if the class was not extended, true otherwise
 */
Object.prototype.extendss = function(sSuper) {
  if(!top.checkExtension(this)) {
    return (false);
  }
  top.prepareExtension(this.getClassName(), sSuper);
  this.extendedClass = sSuper;
  return (true);
}

/**
 * Checks for EXTENSION ERRORS. Checks if the class is trying to extend more
 * then one class.
 *
 * @param Object   - object extending a class
 * @return boolean - false if there is any error, true otherwise
 */
top.checkExtension = function(oToCheck) {
  if(top.check) {
    if(oToCheck.extendedClass != "") {
      top.extensionErrors.push("The Class '" + oToCheck.getClassName() + "' is calling 'extendss' more than once");
      return (false);
    }
  }
  return (true);
}

/**
 * Prepares an extension to be concluded on the page load. Every call to
 * 'extends', calls this method. The list it generates is some sort of
 * hierarchicaly ordered list, where the firsts are the top ones in the
 * hierarchy and the lasts are the bottom ones.
 *
 * Searches in the extendable classes list if the sub class is a super
 * class of any other class. If positive, inserts the new item before
 * the item found. This is done so the sub class on the edge of the
 * chain inherits everything from its super classes.
 *
 * @param String - sub class name
 * @param String - super class name
 */
top.prepareExtension = function(sSub, sSuper) {
  for(var i = 0; i < top.extendsClassList[0].length; i++) {
    // If the "sSub" sub class is a super class of any other
    // class, insert it before the item found, otherwise push
    // it to the end of the list.
    if(top.extendsClassList[1][i] == sSub) {
      top.extendsClassList[0].moveFoward(i);
      top.extendsClassList[0][i] = sSub;
      top.extendsClassList[1].moveFoward(i);
      top.extendsClassList[1][i] = sSuper;
      return;
    }
  }
  top.extendsClassList[0].push(sSub);
  top.extendsClassList[1].push(sSuper);
}

/**
 * Processes every extension solicited on the 'extendsClassList'.
 * As long as the list is correctly ordered, using the 'prepareExtension'
 * function, at the end of this function, every sub class inherits its super class
 * properties and methods. This method should only be called after the page load,
 * ensuring that all files (classes) have been loaded.
 */
top.extendsAll = function() {
  var cSub, cSuper, p;
  for(var i = 0; i < top.extendsClassList[0].length; i++) {
    cSub = eval(top.extendsClassList[0][i]);
    cSuper = eval(top.extendsClassList[1][i]);

    // Store the sub class prototype so we can restore it later.
    p = cSub.prototype;

    // Here the sub class inherits the properties and methods from its super class.
    // This also tells the browser that 'cSuper' is really the super class of 'cSub'.
    cSub.prototype = new cSuper();

    cSub.prototype.Class = p.constructor;
    cSub.extendedClass = top.extendsClassList[1][i];
  }
  top.checkAbstraction();
}

/**
 * This acts like the "implements" in Java. The only diference is that in Javascript
 * you need to call this method right after the Class code. You give it a collection
 * of Strings with the names of the classes to be implementes and the system takes
 * care of the rest. 
 *
 * Implementation example:
 * ClassA.implementss("ClassB", "ClassC", "ClassD");
 * This way ClassA will implement classes ClassB, ClassC and ClassD
 *
 * @param String(...) - strings with the classe's names to be implemented
 * @return boolean    - false if the any class was not implemented, true otherwise
 */
Object.prototype.implementss = function() {
  if(!top.checkImplementations(this, implementss.arguments)) {
    return (false);
  }
  this.implementedClasses = implementss.arguments;
  top.prepareImplementation(this.getClassName(), implementss.arguments);
  return (true);
}

/**
 * Checks for IMPLEMENTATION ERRORS.
 * Checks if the user is calling "implementss" more than once.
 * Checks if the class is implementing the same class more than once.
 *
 * @param Object   - object implementing classes
 * @param Array    - array of implemented classes' names
 * @return boolean - false if there is any error, true otherwise
 */
top.checkImplementations = function(o, a) {
  if(top.check) {
    if(o.implementedClasses.length > 0) {
      top.implementationErrors.push("The Class '" + o.getClassName() + "' is calling 'implementss' more than once");
      return (false);
    }

    for(var i = 0; i < a.length; i++) {
      for(var j = i+1; j < a.length; j++) {
        if(a[i] == a[j]) {
          top.implementationErrors.push("The Class '" + o.getClassName() + "' is trying to implement the same class more than once");
          return (false);
        }
      }
    }
  }
  return (true);
}

/**
 * Prepares an implementation to be concluded on the page load. Every
 * call to 'implements', calls this method.
 *
 * @param String - implementing class name
 * @param String - implemented class name
 */
top.prepareImplementation = function (sImplementing, aImplemented) {
  top.implementsClassList[0].push(sImplementing);
  top.implementsClassList[1].push(aImplemented);
}  

/**
 * Processes every implementation solicited on 'implementsClassList'.
 * This method shoul only be called after the page load, ensuring 
 * that all files (classes) have been loaded.
 * 
 */
top.implementsAll = function() {
  var cImplementing, cImplemented, oImplemented, sImplementing;

  for(var i=0; i<top.implementsClassList[0].length; i++) {
    cImplementing = eval(top.implementsClassList[0][i]);
    sImplementing = cImplementing.toString();
    sPrototype = cImplementing.prototype.constructor.toString();
    for (var j=0; j<top.implementsClassList[1][i].length; j++) {
      cImplemented = eval(top.implementsClassList[1][i][j]);
      oImplemented = new cImplemented();
      for (sProperty in oImplemented) {
        switch(sProperty) {
          case "addGProp" :
          case "addTProp" :
          case "addFProp" :
          case "equals" :
          case "extendss" :
          case "implementss" :
          case "extendedClass" :
          case "implementedClasses" :
          case "getClass" :
          case "getClassName" :
          case "Class" :
            continue;
            break;
          default :
            if(sImplementing.indexOf(sProperty) == -1 && sPrototype.indexOf(sProperty) == -1) {
//              alert("Implementador: "+top.implementsClassList[0][i]+"\nImplementado: "+top.implementsClassList[1][i][j]+"\nPropriedade: "+sProperty);
              eval("cImplementing.prototype."+sProperty+" = oImplemented[sProperty]");
            }
        }
      }
      oImplemented = null;
    }
  }
}

/**
 * Checks for ABSTRACTION ERRORS. Checks if every abstract method of a super class
 * was overwritten by the subclass.
 * 
 * @return boolean - false if there is any error, true otherwise
 */
top.checkAbstraction = function() {
  var cSub, cSuper, sError, sSub, sSuper, oSuper, isAbstract, isMethodEnd, jPosFunction;

  if(!top.check) {
    return (true);
  }

  // Loops through all extandable classes to check if all the abstract methods
  // were overwritten.
  for(var i = 0; i < top.extendsClassList[0].length; i++) {
    // Gets the subclass and its super class from the array
    cSub = eval(top.extendsClassList[0][i]);
    cSuper = eval(top.extendsClassList[1][i]);
    sSub = cSub.toString();
    sSuper = cSuper.toString();
    oSuper = new cSuper();

    // Loops through every property in the super class to check if it has any
    // abstract method.
    for(sProperty in oSuper) {
      // Could use only one variable to end the loop, but I found it would be
      // better to explicit each case.
      isAbstract = true;
      isMethodEnd = false;
      
      //Property is not a method, go to next property.
      if((typeof oSuper[sProperty]) != "function") {
        continue;
      }

      // Puts the pointer on the method start and loops through its text to see
      // if it has any implementation.
      jPosFunction = sSuper.indexOf("{", sSuper.indexOf(sProperty));
      for(var j = jPosFunction + 1; j < sSuper.length, isAbstract, !isMethodEnd; j++) {
        switch(sSuper.charAt(j)) {
          case ' ':
          case '\n':
          case '\r':
          case '\r\n':
          case '\t':
            break;
          case '}':
            // Finding the close brakets ends the loop.
            isMethodEnd = true;
            break;
          default:
            // Finding any token different from the above, shows that the method
            // is not abstract and ends the loop.
            isAbstract = false;
        }
      }

      // If the method is abstract and there is not a method with the same name in
      // the sub class, then we have an ABSTRACTION ERROR.
      if(isAbstract) {
        if(sSub.indexOf(sProperty) < 0) {
          top.abstractionErrors.push("The abstract method " + sProperty + " in class " + sSuper.substring(9, sSuper.indexOf("(")) + " must be overridden in class " + sSub.substring(9, sSub.indexOf("(")));
        }
      }
    }
  }
}

/**
 * Alerts the user the existing errors
 */
top.alertPreCompilationErrors = function() {
  var errors;
  if(!top.check) {
    return (false);
  }
  
  errors = "\nEXTENSION ERRORS:\n\n";
  for(var i = 0 ; i < top.extensionErrors.length ; i++) {
    errors += top.extensionErrors[i] + "\n";
  }
  alert(errors);
  
  errors = "\nIMPLEMENTATION ERRORS:\n\n";
  for(i = 0 ; i < top.implementationErrors.length ; i++) {
    errors += top.implementationErrors[i] + "\n";
  }
  alert(errors);
  
  errors = "\nABSTRACTION ERRORS:\n\n";
  for(i = 0 ; i < top.abstractionErrors.length ; i++) {
    errors += top.abstractionErrors[i] + "\n";
  }
  alert(errors);
  
  return (true);
}

/**
 * Starts all the precompilation job.
 */
top.setEnvironment = function() {
  top.implementsAll();
  top.extendsAll();
  top.alertPreCompilationErrors();
}

//the classpath to find all Javascript files. Default is the directory above this one
top.CLASSPATH = "../";

//list of already loaded packages
top.loadedPackages = new Array();

/**
 * Imports javascript files into the aplication. Has the same usage as normal
 * import in JAVA. You may import the hole package by using the '*' as you use
 * in JAVA, but for that you have to create a special file named 'Package.js',
 * inside it you jus call the import for every file in the pacakage.
 *
 * Example:
 * Supose you have a package called 'js.example' (this is under $CLASSPATH\js\example),
 * and inside it you have two files: 'Ex1.js' and 'Ex2.js'.
 * You can import a single file like this:
 * importt("js.example.Ex1");
 * Or you can import the hole package like this:
 * importt("js.example.*");
 * If you do it this way you have to create the special file 'Package.js' under the same
 * directory of the package like this:
 * Package.js is ->importt("js.example.Ex1");
 *               ->importt("js.example.Ex2");
 *
 * @param String   - package to be imported
 * @param Window   - (optional) window/frame where the package should be imported
 * @return boolean - true if the package is correctly loaded, false otherwise
 */
top.importt = function(s, f) {
  if(top.isPackageLoaded(s)) {
    return (false);
  }

  if(!f) {
    f = top;
  }
  var head = f.document.getElementsByTagName("head").item(0);
  var script = f.document.createElement("script");
  script.setAttribute("type", "text/javascript");
  script.setAttribute("src", top.CLASSPATH + s.replace(/\./g,"/").replace("*","Package") + ".js");
  head.appendChild(script);

  return (true);
}

/**
 * Keeps track of all loaded packages.
 *
 * @param String   - package to be imported
 * @return boolean - true if the package was already loaded, false otherwise
 */
top.isPackageLoaded = function(sImport) {
  
  for(var i = 0; i < top.loadedPackages.length; i++) {
    if(top.loadedPackages[i] == sImport) {
      return (true);
    }
  }
  top.loadedPackages.push(sImport);
  return (false);
}

/**
 * Returns any object in the page, be it a HTML element with an
 * id atribute or a real javascript object already intanciated.
 *
 * NEVER GIVE THE SAME NAME TO A JAVASCRIPT OBJECT AND THE ID OF A HTML TAG.
 *
 * This method uses the Sniffer class.
 * This method has not been tested with any Opera, Safari or KDE browser.
 * and it does not work with Netscape 4 or under.
 *
 * @param String  - javascript object's name or HTML element's id 
 * @param Window  - (optional) window/frame where the object is located
 * @return Object - javascript object or element on frame 'fr'
 *                  identified by name or id 'id'
 */
top.getObj = function(sId, oFr) {
  if(!oFr) {
    oFr = top;
  }
  // verifying global variables
  try {
    if(eval("oFr." + sId)) {
      return (eval("oFr." + sId));
    }
  } catch(e) {}

  // verifying any element with an id
  try {
    if(document.all) {
      return (oFr.document.all[sId]);
    } else if(document.getElementById) {
      if(oFr.document.getElementById(sId) != null) {
        return (oFr.document.getElementById(sId));
      }
    }
  } catch(e) {}

  // verifying form elements
  try {
    if(oFr.document.forms.length > 0) {
      for(var i = 0 ; i < oFr.document.forms.length ; i++) {
        for(var j = 0 ; j < oFr.document.forms[i].elements.length ; j++) {
          if(oFr.document.forms[i].elements[j].name == sId)
            return (oFr.document.forms[i].elements[j]);
        }
      }
    }
  } catch(e) {}

  return (null);
}

/**
 * Returns the innerHTML of an HTML element that has an 'id'.
 * In case the element does not have the innerHTML property, this
 * method returns null.
 * 
 * @param String  - object's id
 * @param Window  - window/frame where the HTML element is located
 * @return String - innerHTML of the object, null if the object does 
 *                  not have the innerHTML property
 */
top.getInnerHTML = function(sId, oFr) {
  var obj = top.getObj(sId, oFr);
  try {
    if(obj.innerHTML) {
      return (obj.innerHTML);
    }
  } catch(e) {
    return (null);
  }
}

/**
 * Sets a new innerHTML for the HTML element with the 'id' value.
 * In case the element does not have the innerHTML property, this
 * method returns false.
 *
 * @param String   - object's id
 * @param String   - text to be inserted as the new innerHTML of the element
 * @param Window   - (optional) window/frame where the HTML element is located
 * @return boolean - true if the new innerHTML is set correctly, false
 *                   otherwise
 */
top.setInnerHTML = function(sId, sNewInnerHTML, oFr) {
  var obj = top.getObj(sId, oFr);
  try {
    obj.innerHTML = sNewInnerHTML;
    return (true);
  } catch(e) {
    return (false);
  }
}

/**
 * Sets a cookie on the client with the name and value passed.
 *
 * @param String - cookie's name
 * @param String - cookie's value
 * @param Date   - (optional) cookie's expire Date or below
 *        int    - (optional) cookie's expire year
 * @param int    - (optional) cookie's expire month
 * @param int    - (optional) cookie's expire day
 */
top.setCookie=function(name, value, expiresOrYear, month, day){
  if(!expiresOrYear){
    expiresOrYear = new Date(2100, 12, 31);
  } else if(!(expiresOrYear instanceof Date)) {
    expiresOrYear = new Date(expiresOrYear, month, day);
  }

  document.cookie=name+"="+escape(value)+";expires="+expiresOrYear.toGMTString()+"; path=\/";
}

/**
 * Gets a cookie with the provided "name".
 *
 * @param String  - cookie's name
 * @return String - cookie's value
 */
top.getCookie = function(name) {
  var cStart, cEnd;
  
  name = name + "=";
  cStart = document.cookie.indexOf(name);
  if(cStart == -1) return null;
  cEnd = document.cookie.indexOf(";", cStart + name.length);
  if(cEnd == -1) cEnd = document.cookie.length;
  
  return unescape(document.cookie.substring(cStart + name.length, cEnd));
}

top.addEvent = function(o, ev, h) {
  if (o.addEventListener) {
    o.addEventListener(ev, h, true);
    return true;
  } else if (o.attachEvent) {
    return o.attachEvent('on' + ev, h);
  } else {
		//eval("o.on" + ev) == h;
  }
  return false;
}

top.remEvent = function(o, ev, h) {
  if (o.removeEventListener) {
    o.removeEventListener(ev, h, true);
    return true;
  } else if (o.detachEvent) {
    return o.detachEvent('on' + ev, h);
  } else {
    //eval("o.on" + ev) == null;
  }
  return false;
}

/**
 * Default start function. It only sets the correct enviroment. Called on the Load window event.
 *
 * ATTENTION: THIS FUNCTION SHOULD BE OVERWRITTEN BY YOUR OWN START FUNCTION
 */
top.start = function() {
 top.setEnvironment();
}