/*
 * Copyright (C) 2008 TopCoder Inc., All Rights Reserved.
 *
 * JavaScript Browser Accessible Entity List 1.0
 */

/*
 * Set the component package.
 */
function registerNS(ns) {
    var nsParts = ns.split(".");
    var root = window;

    for (var i = 0; i < nsParts.length; i++) {
        if(typeof root[nsParts[i]] == "undefined")
        root[nsParts[i]] = new Object();
        root = root[nsParts[i]];
    }
}

registerNS("js.topcoder.web.entitylist.client.storageimpl");



/**
 * <p>
 * Creates new instance of <code>Helper</code> class.
 * </p>
 *
 * @private
 * @constructor
 *
 * @class
 *
 * <p>
 * <code>Helper</code> class contains methods to check arguments for this component classes methods.
 * </p>
 * <p>
 * This class should not be instantiated. Every method can be used in a static manner.
 * </p>
 * <p>
 * This class is immutable.
 * Thread-safety is not handled since it is a JavaScript class.
 * </p>
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.Helper = function () {
    // empty constructor
}



/**
 * <p>
 * Checks if the given variable is defined, not <code>null</code> and is of the correct
 * type.
 * </p>
 *
 * @param variable variable to check.
 * @param name the name of the variable.
 * @param type the type of the variable.
 *
 * @throws IllegalArgumentException if <code>variable</code> is not defined, <code>null</code>
 * or of wrong type.
 */
js.topcoder.web.entitylist.client.Helper.checkDefined =
    /* void */ function( /* Object */ variable, /* String */ name, /* String */ type) {
    if (typeof(variable) == "undefined") {
        throw new js.topcoder.web.entitylist.client.IllegalArgumentException(name + " is not defined.");
    }
    if (variable == null) {
        throw new js.topcoder.web.entitylist.client.IllegalArgumentException(name + " cannot be null.");
    }
    if (typeof(variable) != type) {
        throw new js.topcoder.web.entitylist.client.IllegalArgumentException(name + " is not " + type);
    }
}



/**
 * <p>
 * Checks if the given string is not empty after trim.
 * </p>
 *
 * @param string the string to check.
 * @param name the name of the argument.
 *
 * @throws IllegalArgumentException if <code>string</code> is not string or is empty after trim.
 */
js.topcoder.web.entitylist.client.Helper.checkString =
    /* void */ function( /* String */ string, /* String */ name) {
    // check if string is defined and is a string
    js.topcoder.web.entitylist.client.Helper.checkDefined(string, name, "string");

    // count the number of spaces in string
    var cnt = 0;

    for (var i = 0; i < string.length; i++) {
        if ((string.charAt(i) == '\x20') || (string.charAt(i) == '\t') || (string.charAt(i) == '\n')
            || (string.charAt(i) == '\x0B') || (string.charAt(i) == '\f') || (string.charAt(i) == '\r')) {
            cnt++;
        }
    }

    if (cnt == string.length) {
        throw new js.topcoder.web.entitylist.client.IllegalArgumentException(name
            + " cannot be empty after trim.");
    }
}


/**
 * <p>
 * Checks if the given number is greater or equal to the given value.
 * </p>
 *
 * @param {int} num the number to check
 * @param {String} name the name of the argument.
 * @param {int} value the value to compare <code>num</code> to.
 *
 * @throws IllegalArgumentException if <code>num</code> is not integer or less than <code>value</code>.
 */
js.topcoder.web.entitylist.client.Helper.checkGreaterOrEqual = function(num, name, value) {
    // check if num is defined and is a number
    js.topcoder.web.entitylist.client.Helper.checkDefined(num, name, "number");
    if (num < value) {
        throw new js.topcoder.web.entitylist.client.IllegalArgumentException(name
            + " should be greater or equal to " + value);
    }
}

/**
 * <p>
 * Clone the input JSON Object and return.
 * </p>
 *
 * @param {JSON Object} input the input JSON Object
 * @return the cloned JSON Object
 */
js.topcoder.web.entitylist.client.Helper.cloneJsonObject = function(input) {
    var result = {};

    for (var key in input) {
        result[key] = input[key];
    }

    return result;
}



/**
 * <p>
 * This exception shows that some parameters passed to functions were incorrect.
 * </p>
 * <p>
 * This class is immutable.
 * Thread-safety is not handled since it is a JavaScript class.
 * </p>
 *
 * @class
 *
 * @param newMessage the exception message.
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.IllegalArgumentException = function ( /* String */ newMessage) {
    /**
     * <p>
     * Represent error message.
     * </p>
     *
     * @private
     */
    var /* String */ message;

    /**
     * <p>
     * Returns the message field.
     * </p>
     *
     * @return the message
     */
    this.getMessage = /* String */ function() {
        return message;
    }

    /*
     * Start of constructor code.
     */

    // constructor code - sets message field
    message = newMessage;

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This exception shows that while loading or saving list to persistence some error occur.
 * </p>
 * <p>
 * This class is immutable.
 * Thread-safety is not handled since it is a JavaScript class.
 * </p>
 *
 * @class
 *
 * @param newMessage the exception message.
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.StorageException = function ( /* String */ newMessage) {
    /**
     * <p>
     * Represent error message.
     * </p>
     *
     * @private
     */
    var /* String */ message;

    /**
     * <p>
     * Returns the message field.
     * </p>
     *
     * @return the message
     */
    this.getMessage = /* String */ function() {
        return message;
    }

    /*
     * Start of constructor code.
     */

    // constructor code - sets message field
    message = newMessage;

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This interface provides the common functionality for persisting entity list objects.
 * </p>
 * <p>
 * This class is an interface, so it's thread safe.
 * </p>
 *
 * @interface
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.Storage = function () {
    /**
     * <p>
     * loads the list instance from the persistence.
     * </p>
     *
     * @param listName name of the list to be loaded. cannot be null or empty
     *
     * @return the loaded entity list instance
     *
     * @throws IllegalArgumentException if listName is null or empty
     * @throws StorageException if loading list failed for some reason.
     */
    this.loadList = /* EntityList */ function( /* String */ listName) {}

    /**
     * <p>
     * save the list instance to the persistence.
     * </p>
     *
     * @param listName name of the list to be loaded. cannot be null or empty
     * @param entityList the entity list instance to be saved.
     *
     * @throws IllegalArgumentException if parameters don't fit restrictions
     */
    this.saveList = /* void */ function( /* String */ listName, /* EntityList */ entityList) {}
}



/**
 * <p>
 * This class represents the entity which will be stored in entity list. Entity has the id and (optionally)
 * the set of additional data fields.
 * </p>
 * <p>
 * This class is mutable, so it's not thread safe. But JavaScript execution model is mostly single-threaded.
 * And there is no thread-synchronization features in JavaScript, so it is not an issue here.
 * Anyway, the external code should be careful if several threads are used to communicate with this class.
 * </p>
 *
 * @class
 *
 * <p>
 * creates entity instance from provided json object.
 * </p>
 *
 * @param newFields the json object from which the entity will be created. cannot be null
 *
 * @throws IllegalArgumentException if fields is null.
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.Entity = function ( /* JSON Object */ newFields) {
    /**
     * stores the id of the entity. This variable is set by the end-user.
     * can't be set to null or empty string
     *
     * @private
     */
    var /* String */ id;

    /**
     * stores the additional fields accosiated with the entity.
     * keys are fields' names, values - field objects
     * can be null. keys and values cannot be null
     *
     * @private
     * @final
     */
    var /* JSON Object */ fields = {};

    /**
     * gets the extra field object with the given name.
     *
     * @throws IllegalArgumentException if fieldName is null or empty,
     *     or field with this name is not present
     *
     * @param fieldName the field name. cannot be null or empty
     * @return the extra field object
     */
    this.getField = /* Object */ function(/* String */ fieldName) {
        js.topcoder.web.entitylist.client.Helper.checkString(fieldName, "fieldName");
        checkIsPresent(fieldName);

        return fields[fieldName];
    }

    /**
     * sets the extra field object with the given name and given value
     *
     * @throws IllegalArgumentException if fieldName is null or empty,
     *     or field with this name is not present
     *
     * @param fieldName the field name. cannot be null or empty
     * @param fieldValue value. no restrictions
     */
    this.setField = /* void */ function(/* String */ fieldName, /* Object */ fieldValue) {
        js.topcoder.web.entitylist.client.Helper.checkString(fieldName, "fieldName");
        checkIsPresent(fieldName);

        fields[fieldName] = fieldValue;
    }

    /**
     * returns all the fields and their values as json object.
     *
     * @return fields object.
     */
    this.getAllFields = /* JSON Object */ function() {
        return fields;
    }

    /**
     * checks if provided extra field is supported by the list.
     *
     * @throws IllegalArgumentException if fieldName is null or empty
     *
     * @param fieldName the field name to be checked. cannot be null or empty
     * @return true if field is present, false otherwise
     */
    this.isExtraFieldPresent = /* boolean */ function(/* String */ fieldName) {
        js.topcoder.web.entitylist.client.Helper.checkString(fieldName, "fieldName");

        return (typeof(fields[fieldName]) != "undefined");
    }

    /**
     * getter for id field.
     *
     * @return id of the entity
     */
    this.getId = /* String */ function() {
        return id;
    }

    /**
     * setter for id field.
     *
     * @throws IllegalArgumentException if id is null or empty
     *
     * @param newId the id to be set. cannot be null or empty
     */
    this.setId = /* void */ function(/* String */ newId) {
        js.topcoder.web.entitylist.client.Helper.checkString(newId, "newId");
        id = newId;
    }

    /**
     * gets the entity as json style object.
     *
     * @return json object representing the entity.
     */
    this.getJSONObject = /* JSONObject */ function() {
        return {"id":id, "fields":fields};
    }

    /**
     * Check the field name whether present in fields.
     *
     * @private
     *
     * @throws IllegalArgumentException if field with this name is not present
     *
     * @param fieldName the field name. cannot be null or empty
     */
    /* void */ function checkIsPresent(/* String */ fieldName) {
        if(typeof(fields[fieldName]) == "undefined") {
             throw new js.topcoder.web.entitylist.client.IllegalArgumentException("The fieldName["
                 + fieldName + "] is not present in fields.");
        }
    }

    /*
     * Start of constructor code.
     */

    if (typeof(newFields) != "undefined"){
        js.topcoder.web.entitylist.client.Helper.checkDefined(newFields, "newFields", "object");
        // the fields should be set with the cloned parameter
        fields = js.topcoder.web.entitylist.client.Helper.cloneJsonObject(newFields);
    }

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This class represents the entity list. It can store entities and set of additional fields that can be
 * set for each entity. This list can be of unlimited length or be configured to have max size.
 * </p>
 * <p>
 * This class is mutable, so it's not thread safe. But JavaScript execution model is mostly single-threaded.
 * And there is no thread-synchronization features in JavaScript, so it is not an issue here.
 * Anyway, the external code should be careful if several threads are used to communicate with this class.
 * </p>
 *
 * @class
 *
 * <p>
 * creates entity list.
 * </p>
 *
 * @param param the first parameter, it have three two types:
 *        1. jsonString - json style string that stores json representation of object. cannot be null or empty
 *        2. fields - array containing extra field objects. can be null
 * @param newMaxSize maximal size of the list. must be not negative
 *
 * @throws IllegalArgumentException if parameters don't fit restrictions
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.EntityList = function ( /*Object */ param, /* JSONObject */ newConfig) {
    /**
     * represents the maximal size of the list.
     * 1 if size is not limited, integer value. default is -1.
     * set in constructor. never changed after that. accessed via getter
     *
     * @private
     * @final
     */
    var /* Number */ maxSize = -1;
    
        /**
     * stores parameters for cookies.
     * can be null.
     * is set in ctor and never changed
     *
     * @private
     * @final
     */
    var /* JSONObject */ config;

    /**
     * stores the additional fields of the entities. key is filed's name, value is field object.
     * cannot  be null. cannot contain nulls as keys or values.
     * is set in constructor and never changed.
     *
     * @private
     * @final
     */
    var /* JSONObject*/ fields = {};

    /**
     * stores list items so they can be accessed via index.
     * cannot be null or contain nulls as values.
     * is changed in addItem, removeItem
     *
     * @private
     * @final
     */
    var /* JSONArray */ itemsList = new Array();

    /**
     * gets the first item with specified id.
     *
     * @throws IllegalArgumentException if id is null or empty
     *
     * @param id the id of the entity. cannot be null or empty
     * @return the entity with specified id
     */
    this.getItem = /* Entity */ function (/* String */ id) {
        js.topcoder.web.entitylist.client.Helper.checkString(id, "id");

        for (var i = 0; i < itemsList.length; i++) {
            if (itemsList[i].getId() == id) {
                return itemsList[i];
            }
        }
        return null;
    }

    /**
     * return all the available entities with specified id as array.
     *
     * @throws IllegalArgumentException if id is null or empty
     *
     * @param id the id of the entities. cannot be null or empty
     * @return array of elements with specified id
     */
    this.getItems = /* JSONArray */ function(/* String */ id) {
        js.topcoder.web.entitylist.client.Helper.checkString(id, "id");

        var res = new Array();
        for (var i = 0; i < itemsList.length; i++) {
            if (itemsList[i].getId() == id) {
                res.push(itemsList[i]);
            }
        }
        return res;
    }

    /**
     * returns all the items from the list.
     *
     * @return all the items from list
     */
    this.getAllItems = /* JSONArray */ function() {
        return itemsList;
    }

    /**
     * gets the item by its index.
     *
     * @throws IllegalArgumentException if index is be negative or >= list size
     *
     * @param index the index of item. can't be negative or >= list size
     * @return the entity with specified index. cannot be null
     */
    this.getItemByIndex = /* Entity */ function(/* int */ index) {
        js.topcoder.web.entitylist.client.Helper.checkDefined(index, "index", "number");
        if (index < 0 || index >= itemsList.length) {
            throw new js.topcoder.web.entitylist.client.IllegalArgumentException("The index[" + index +
                "] is negative or >= list size");
        }
        return itemsList[index];
    }

    /**
     * adds the new intem to the list. if list size is limited and this element will cause overflow,
     * the first element from the list should be deleted.
     *
     * @throws IllegalArgumentException if item is null,
     *     or field list of item doesn't correspond to fields item.
     *
     * @param item the entity to be added. cannot be null
     */
    this.addItem = /* void */ function(/* Entity */ item) {
        js.topcoder.web.entitylist.client.Helper.checkDefined(item, "item", "object");

        // check if each field from fields map is present in the item, else throw IllegalArgumentException
        var itemFields = item.getAllFields();
        for (var key in itemFields) {
            if (!fields[key]) {
                throw new js.topcoder.web.entitylist.client.IllegalArgumentException("The field key["
                    + key + "] is not exist in fields");
            }
        }

        //remove 1st element if is needed
        if (maxSize != 0 && itemsList.length == maxSize && itemsList.size() > 0) {
            removeItem(itemsList[0]);
        }

        // add the item to the list
        if (maxSize != 0) {
            itemsList.push(item);
        }
    }

    /**
     * removes the specified item from the list.
     *
     * @throws IllegalArgumentException if item is null
     *
     * @param item the item to be removed. cannot be null
     */
    this.removeItem = /* void */ function(/* Entity */ item) {
        js.topcoder.web.entitylist.client.Helper.checkDefined(item, "item", "object");

        var res = new Array();
        for (var i = 0; i < itemsList.length; i++) {
            if (itemsList[i] == item) {
                continue;
            }
            res.push(itemsList[i]);
        }
        itemsList = res;
    }

    /**
     * removes all the elements from list which have specified id.
     *
     * @throws IllegalArgumentException if id is null or empty
     *
     * @param id id of the enitity to be removed. cannot be null or empty
     */
    this.removeItems = /* void */ function(/* String */ id) {
        js.topcoder.web.entitylist.client.Helper.checkString(id, "id");

        var res = new Array();
        for (var i = 0; i < itemsList.length; i++) {
            if (itemsList[i].getId() == id) {
                continue;
            }
            res.push(itemsList[i]);
        }
        itemsList = res;
    }

    /**
     * creates new item with correct set of additional fields.
     *
     * @return new created item
     */
    this.createNewItem = /* Entity */ function() {
        return new js.topcoder.web.entitylist.client.Entity(fields);
    }

    /**
     * checks if provided extra field is supported by the list.
     *
     * @throws IllegalArgumentException if any parameter is null or empty
     *
     * @param fieldName the field name to be checked. cannot be null or empty
     * @return true if field is present, false otherwise
     */
    this.isExtraFieldPresent = /* boolean */ function(/* String */ fieldName) {
        js.topcoder.web.entitylist.client.Helper.checkString(fieldName, "fieldName");

        return (typeof(fields[fieldName]) != "undefined");
    }

    /**
     * returns number of elements in the list.
     *
     * @return current list size
     */
    this.getSize = /* Number */ function() {
        return itemsList.length;
    }

    /**
     * getter for maxSize.
     *
     * @return max possible size or -1 if list is unlimited.
     */
    this.getMaxSize = /* Number */ function() {
        return maxSize;
    }

    /**
     * returns json string that represents instance of the entity list.
     *
     * @return json style string that represents the list object
     */
    this.getJSONString = /* String */ function() {
        var res = {"maxSize":maxSize, "fields":fields };
        res["items"] = new Array();
        for (var i = 0; i < itemsList.length; i++) {
            res["items"] .push(itemsList[i].getJSONObject());
        }
        return JSON.stringify(res);
    }
    
    /**
    * returns dot-delimited string that represents the list object
    */
    this.getDotDelimitedString = /* String */ function() {
    	var ret = "";
    	ret += maxSize;
    	for (var i = 0; i < itemsList.length; i++) {
    		ret += ".";
    		ret += itemsList[i].getId();
    	}
    	return ret;
    }
    
    /*
     * Start of constructor code.
     */

    if (typeof(param) != "undefined"){
        if (typeof(param) == "string") {
            js.topcoder.web.entitylist.client.Helper.checkString(param, "param");
            
            if (typeof(newConfig) != "undefined"
                && newConfig["formatType"] == "dot_delimited") {
              config = newConfig;
              if (config["formatType"] == "dot_delimited") {
                var split = param.split(".");
                maxSize = Number(split[0]);
                js.topcoder.web.entitylist.client.Helper.checkGreaterOrEqual(maxSize, "maxSize", 0);
                for (var i = 1; i < split.length; i++) {
                    var entity = new js.topcoder.web.entitylist.client.Entity();
                    entity.setId(split[i]);
                    this.addItem(entity);
                }
                return;
              }
            } else {
              // Parse the JSON configuration string to JSON Object
              var json = JSON.parse(param);

              if (json["maxSize"]) {
                  js.topcoder.web.entitylist.client.Helper.checkGreaterOrEqual(json["maxSize"], "json[maxSize]", 0);
                  maxSize = json["maxSize"];
              }
              if (json["fields"]) {
                  fields = json["fields"];
              }
              if (json["items"]) {
                  var items = json["items"];
                  for (var i = 0; i < items.length; i++) {
                      var entity = new js.topcoder.web.entitylist.client.Entity(items[i]["fields"]);
                      entity.setId(items[i]["id"]);
                      this.addItem(entity);
                  }
              }
            }
        } else if (typeof(param) == "object") {
            js.topcoder.web.entitylist.client.Helper.checkDefined(param, "param", "object");
            // the fields should be set with the cloned parameter
            fields = js.topcoder.web.entitylist.client.Helper.cloneJsonObject(param);
            config = newConfig;
            if (typeof(config) != "undefined") {
                var newMaxSize = config['maxSize'];
                if (typeof(newMaxSize) != "undefined") {
                  js.topcoder.web.entitylist.client.Helper.checkGreaterOrEqual(newMaxSize, "newMaxSize", 0);
                  maxSize = newMaxSize;
                }
            }
        } else {
            throw new js.topcoder.web.entitylist.client.IllegalArgumentException(
                "The constructor parameter is invalid.");
        }
    }

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This class represents the session storage strategy for entity list.
 * </p>
 * <p>
 * This class is immutable, so it's thread safe. But JavaScript execution model is mostly single-threaded.
 * And there is no thread-synchronization features in JavaScript, so it is not an issue here.
 * Anyway, the external code should be careful if several threads are used to communicate with this class.
 * </p>
 *
 * @class
 *
 * <p>
 * creates the instance of the SessionStorage.
 * </p>
 *
 * @param newUrlPrefix the prefix to StorageServlet. cannot be null or empty
 *
 * @throws IllegalArgumentException if any parameter is null or empty
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.storageimpl.SessionStorage = function ( /*String */ newUrlPrefix, /* JSONObject */ newConfig) {

    /**
     * stores url prefix to the StorageServlet. example : http://localhost:8080/.
     * cannot be null or empty.
     * s set in ctor and never changed.
     *
     * @private
     * @final
     */
    var /* String */ urlPrefix;
    
    /**
     * stores parameters for cookies.
     * can be null.
     * is set in ctor and never changed
     *
     * @private
     * @final
     */
    var /* JSONObject */ config;

    /**
     * saves the entity list using session.
     *
     * @throws IllegalArgumentException if listName is null or empty
     * @throws StorageException if list wasn't loaded
     *
     * @param listName name of the list to be loaded. cannot be null or empty
     * @return the loaded entity list instance.
     */
    this.loadList = /* EntityList */ function(  /* String */ listName) {
        js.topcoder.web.entitylist.client.Helper.checkString(listName, "listName");

        // Store the result
        var result = null;
        // create AjaxProcessor
        var processor = new AJAXProcessor();
        // make AJAX request
        processor.request({
            url: urlPrefix + "StorageServlet?action=load&listName=" + listName + "&storageType=session",
            async: false,
            responseType : "text"
        });


        if (processor.getStatus() == 200) {
            return new js.topcoder.web.entitylist.client.EntityList(processor.getResponseText());
        } else {
            throw new js.topcoder.web.entitylist.client.StorageException(
                "The enetiy list is not exist with the name[" + listName + "]")
        }
    }

    /**
     * save the list instance using cookies or session if cookies are disabled.
     *
     * @throws IllegalArgumentException if listName is null or empty,
     *     or entityList is null or the instance is not EntityList
     *
     * @param entityList the entity list instance to be saved.
     * @param listName name of the list to be saved. cannot be null or empty
     */
    this.saveList = /* void */ function( /* String */ listName, /* EntityList */ entityList) {
        js.topcoder.web.entitylist.client.Helper.checkString(listName, "listName");
        js.topcoder.web.entitylist.client.Helper.checkDefined(entityList, "entityList", "object");
        if (!(entityList instanceof js.topcoder.web.entitylist.client.EntityList)) {
            throw new js.topcoder.web.entitylist.client.IllegalArgumentException(
                "The entityList is not instace of js.topcoder.web.entitylist.client.EntityList.");
        }

        // create AjaxProcessor
        var processor = new AJAXProcessor();
        // make AJAX request
        var fullUrl = urlPrefix + "StorageServlet?action=save&listName=" + listName + "&listData=";
        if (config["formatType"] == "dot_delimited") {
          fullUrl += entityList.getDotDelimitedString();
        } else {
          fullUrl += entityList.getJSONString();
        }
        fullUrl += "&storageType=session";
        processor.request({
            url: fullUrl,
            async: false,
            responseType : "text"
        });
    }

    /*
     * Start of constructor code.
     */

    // set up inheritance
    this.base = js.topcoder.web.entitylist.client.Storage;

    js.topcoder.web.entitylist.client.Helper.checkString(newUrlPrefix, "newUrlPrefix");
    urlPrefix = newUrlPrefix;
    config = newConfig;

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This class represents the cookie storage strategy for entity list.
 * </p>
 * <p>
 * This class is immutable, so it's thread safe. But JavaScript execution model is mostly single-threaded.
 * And there is no thread-synchronization features in JavaScript, so it is not an issue here.
 * Anyway, the external code should be careful if several threads are used to communicate with this class.
 * </p>
 *
 * @class
 *
 * <p>
 * creates the instance of the CookieStorage.
 * </p>
 *
 * @param newCookie the cookie string. can be null
 * @param newConfig the configuration of cookies. can be null
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.storageimpl.CookieStorage = function (
    /* String */ newCookie,  /* JSONObject */ newConfig) {

    /**
     * <p>
     * stores max cookie size. Is equal to 4KB. Used in saveList method.
     * </p>
     *
     * @private
     * @static
     * @final
     */
    var /* int */ MAX_COOKIE_SIZE = 4 * 1024;

    /**
     * stores string that represents cookies. is obtained by accessing document.cookie.
     * can be null or empty.
     * is set in ctor and never changed.
     *
     * @private
     * @final
     */
    var /* String */ cookie;

    /**
     * stores parameters for cookies.
     * can be null.
     * is set in ctor and never changed
     *
     * @private
     * @final
     */
    var /* JSONObject */ config;

    /**
     * saves the entity list using session.
     *
     * @throws IllegalArgumentException if listName is null or empty
     * @throws StorageException if list wasn't loaded
     *
     * @param listName name of the list to be loaded. cannot be null or empty
     * @return the loaded entity list instance.
     */
    this.loadList = /* EntityList */ function(  /* String */ listName) {
        js.topcoder.web.entitylist.client.Helper.checkString(listName, "listName");

        // if cookie is null, the cookie is disabled.
        if (cookie != null){
            var num = 0;
            var cookieValue = "";

            while (cookie.indexOf(listName + num) != -1) {
                cookieValue += $.cookie(listName + num);
                num++;
            }

            if (cookieValue == "") {
                throw new js.topcoder.web.entitylist.client.StorageException(
                    "The enetiy list is not exist with the name[" + listName + "]");
            }

            return new js.topcoder.web.entitylist.client.EntityList(cookieValue, config);
        } else {
            throw new js.topcoder.web.entitylist.client.StorageException(
                    "The cookie is disabled.");
        }
    }

    /**
     * save the list instance using cookies or session if cookies are disabled.
     *
     * @throws IllegalArgumentException if listName is null or empty,
     *     or entityList is null or the instance is not EntityList
     *
     * @param entityList the entity list instance to be saved.
     * @param listName name of the list to be saved. cannot be null or empty
     */
    this.saveList = /* void */ function( /* String */ listName, /* EntityList */ entityList) {
        js.topcoder.web.entitylist.client.Helper.checkString(listName, "listName");
        js.topcoder.web.entitylist.client.Helper.checkDefined(entityList, "entityList", "object");
        if (!(entityList instanceof js.topcoder.web.entitylist.client.EntityList)) {
            throw new js.topcoder.web.entitylist.client.IllegalArgumentException(
                "The entityList is not instace of js.topcoder.web.entitylist.client.EntityList.");
        }


        // if cookie is null, the cookie is disabled.
        if (cookie != null){
            // find the same listName cookie, and delete it
            var num = 0;
            while (cookie.indexOf(listName + num) != -1) {
                $.cookie(listName + num, null);
                num++;
            }

            // put the new json string into cookie
            var cookieString;
            if (config["formatType"] == "dot_delimited") {
              cookieString = entityList.getDotDelimitedString();
            } else {
              cookieString = entityList.getJSONString();
            }
            
            var index = 0;
            while (cookieString.length > 0) {
                var str;
                if (cookieString.length <= MAX_COOKIE_SIZE) {
                    str = cookieString;
                    cookieString = "";
                } else {
                    str = cookieString.substring(0, MAX_COOKIE_SIZE);
                    cookieString = cookieString.substring(MAX_COOKIE_SIZE);
                }

                $.cookie(listName + index, str, config);

                index++;
            }
        }
    }

    /*
     * Start of constructor code.
     */

    // set up inheritance
    this.base = js.topcoder.web.entitylist.client.Storage;

    cookie = newCookie;
    config = newConfig;

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This class represents the storage mode that allows cookie storage by default,
 * falling back to session storage if the client's browser does not support cookies or has them disabled.
 * First it tries to load from cookies, if it fails - tries to load from session. Saves both to cookies
 * and session.
 * </p>
 * <p>
 * This class is immutable, so it's thread safe. But JavaScript execution model is mostly single-threaded.
 * And there is no thread-synchronization features in JavaScript, so it is not an issue here.
 * Anyway, the external code should be careful if several threads are used to communicate with this class.
 * </p>
 *
 * @class
 *
 * <p>
 * creates the instance of the CookieStorage.
 * </p>
 *
 * @param newCookie the cookie string. can be null
 * @param newConfig the configuration of cookies. can be null
 * @param newUrlPrefix the prefix to StorageServlet. cannot be null
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.storageimpl.CookieToSessionFallbackStorage = function (
    /* String */ newCookie,  /* String */ newUrlPrefix, /* JSONObject */ newConfig) {
    /**
     * stores string that represents cookies. is obtained by accessing document.cookie.
     * can be null or empty.
     * is set in ctor and never changed.
     *
     * @private
     * @final
     */
    var /* String */ cookie;

    /**
     * stores parameters for cookies.
     * can be null.
     * is set in ctor and never changed
     *
     * @private
     * @final
     */
    var /* JSONObject */ config;

    /**
     * stores url prefix to the StorageServlet. example : http://localhost:8080/.
     * cannot be null or empty.
     * s set in ctor and never changed.
     *
     * @private
     * @final
     */
    var /* String */ urlPrefix;

    /**
     * saves the entity list using session.
     *
     * @throws IllegalArgumentException if listName is null or empty
     * @throws StorageException if list wasn't loaded
     *
     * @param listName name of the list to be loaded. cannot be null or empty
     * @return the loaded entity list instance.
     */
    this.loadList = /* EntityList */ function(  /* String */ listName) {
        js.topcoder.web.entitylist.client.Helper.checkString(listName, "listName");

        var result;
        var catchException = false;

        // try to get object from cookies. if it fails, use session storage.
        var  cookieStorage = new js.topcoder.web.entitylist.client.storageimpl.CookieStorage(cookie, config);
        try {
            result = cookieStorage.loadList(listName);
        } catch (e) {
            catchException = true;
        }

        // if no exceptions were caught and result is not null, cookies are working.
        //  Simply return retrieved object
        if (result != null && !catchException) {
             return result;
        }

        catchException = false;
        // cookies are disabled, use session storage
        var sessionStorage = new js.topcoder.web.entitylist.client.storageimpl.SessionStorage(urlPrefix, config);
        try {
            result = sessionStorage.loadList(listName);
        } catch (e) {
            catchException = true;
        }

        if (result != null && !catchException) {
             return result;
        } else {
            throw new js.topcoder.web.entitylist.client.StorageException(
                "The object with name[" + listName + "] is not exist.");
        }
    }

    /**
     * save the list instance using cookies or session if cookies are disabled.
     *
     * @throws IllegalArgumentException if listName is null or empty,
     *     or entityList is null or the instance is not EntityList
     *
     * @param entityList the entity list instance to be saved.
     * @param listName name of the list to be saved. cannot be null or empty
     */
    this.saveList = /* void */ function( /* String */ listName, /* EntityList */ entityList) {
        js.topcoder.web.entitylist.client.Helper.checkString(listName, "listName");
        js.topcoder.web.entitylist.client.Helper.checkDefined(entityList, "entityList", "object");
        if (!(entityList instanceof js.topcoder.web.entitylist.client.EntityList)) {
            throw new js.topcoder.web.entitylist.client.IllegalArgumentException(
                "The entityList is not instace of js.topcoder.web.entitylist.client.EntityList.");
        }

        // save info to cookies
        var cookieStorage = new js.topcoder.web.entitylist.client.storageimpl.CookieStorage(cookie, config);
        cookieStorage.saveList(listName, entityList);

        // save info to session
        var sessionStorage = new js.topcoder.web.entitylist.client.storageimpl.SessionStorage(urlPrefix, config);
        sessionStorage.saveList(listName, entityList);
    }

    /*
     * Start of constructor code.
     */

    // set up inheritance
    this.base = js.topcoder.web.entitylist.client.Storage;

    js.topcoder.web.entitylist.client.Helper.checkString(newUrlPrefix, "newUrlPrefix");
    urlPrefix = newUrlPrefix;
    cookie = newCookie;
    config = newConfig;

    /*
     * End of constructor code.
     */
}



/**
 * <p>
 * This class will be used by the end-user to load / save instances of the entity list.
 * </p>
 * <p>
 * This class is immutable, so it's thread safe. But JavaScript execution model is mostly single-threaded.
 * And there is no thread-synchronization features in JavaScript, so it is not an issue here.
 * Anyway, the external code should be careful if several threads are used to communicate with this class.
 * </p>
 *
 * @class
 *
 * <p>
 * creates the instance of the class.
 * </p>
 *
 * @throws IllegalArgumentException if parameters don't fit restrictions
 * @throws StorageException if storage type was missing or wrong
 *
 * @param newCookie the cookie string. can be null
 * @param newStorageConfig the storage configuration for the entity list.
 *     cannot be null or have nulls or empty string as keys or values.
 *
 * @constructor
 *
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
 */
js.topcoder.web.entitylist.client.StorageManager = function (
    /* String */ newCookie, /* JSONObject */ newStorageConfig) {
    /**
     * stores string that represents cookies. is obtained by accessing document.cookie.
     * can be null or empty.
     * is set in ctor and never changed.
     *
     * @private
     * @final
     */
    var /* String */ cookie;

    /**
     * represents the storage configuration for the entity list.
     * cannot  be null. cannot contain nulls or empty strings as keys or values.
     * is set in constructor and never changed.
     *
     * @private
     * @final
     */
    var /* JSONObject */ storageConfig;

    /**
     * represents the correct instance of the storage class based on the storage config.
     * cannot  be null.
     * is created in the ctor and never changed after that. used in loadList, saveList methods.
     *
     * private
     * @final
     */
    var /* Storage */ storage;

    /**
     * saves the entity list using session.
     *
     * @throws IllegalArgumentException if listName is null or empty
     * @throws StorageException if list wasn't loaded
     *
     * @param listName name of the list to be loaded. cannot be null or empty
     * @return the loaded entity list instance.
     */
    this.loadList = /* EntityList */ function(  /* String */ listName) {
        return storage.loadList(listName);
    }

    /**
     * save the list instance using cookies or session if cookies are disabled.
     *
     * @throws IllegalArgumentException if listName is null or empty,
     *     or entityList is null or the instance is not EntityList
     *
     * @param entityList the entity list instance to be saved.
     * @param listName name of the list to be saved. cannot be null or empty
     */
    this.saveList = /* void */ function( /* String */ listName, /* EntityList */ entityList) {
        storage.saveList(listName, entityList);
    }

    /*
     * Start of constructor code.
     */

    // set up inheritance
    this.base = js.topcoder.web.entitylist.client.Storage;

    js.topcoder.web.entitylist.client.Helper.checkDefined(newStorageConfig, "newStorageConfig", "object");
    cookie = newCookie;
    storageConfig = newStorageConfig;


    var type = storageConfig["type"];
    if (typeof(type) == "undefined" || type == null) {
        throw new js.topcoder.web.entitylist.client.StorageException("type[" + type + "] is missing.");
    }

    var url = storageConfig["urlPrefix"];
    if (type == "session") {
        js.topcoder.web.entitylist.client.Helper.checkString(url, "storageConfig['urlPrefix']");
        // create the SessionStorage instance
        storage = new js.topcoder.web.entitylist.client.storageimpl.SessionStorage(url, storageConfig);
    } else if (type == "cookie") {
        // create the CookieStorage instance
        storage = new js.topcoder.web.entitylist.client.storageimpl.CookieStorage(cookie, storageConfig);
    } else if (type == "cookieToSessionFallback") {
        js.topcoder.web.entitylist.client.Helper.checkString(url, "storageConfig['urlPrefix']");
        // create the CookieToSessionFallbackStorage instance
        storage = new js.topcoder.web.entitylist.client.storageimpl.CookieToSessionFallbackStorage(
            cookie, url, storageConfig);
    } else {
        throw new js.topcoder.web.entitylist.client.StorageException(
            "type[" + type + "] is unknown.");
    }

    /*
     * End of constructor code.
     */

}

