'use strict';

/**
 * Creates a Set instance.
 * @constructor
 */
function Set() {
    this.set = {};
    this.numElems = 0;
}

/**
 * Get the number of elements in the set.
 * @returns {number} Number of elements in the set.
 */
Set.prototype.size = function() {
    return this.numElems;
};

/**
 * Returns whether the set is empty.
 * @returns {boolean} Whether or not the set is empty.
 */
Set.prototype.isEmpty = function() {
    return this.numElems === 0;
};

/**
 * Clears the elements in the set.
 * Results in a set equal to a newly constructed instance.
 */
Set.prototype.clear = function() {
    this.set = {};
    this.numElems = 0;
};

/**
 * Extract a key from an object for the set dictionary.
 * @param {elem} elem - A graphics object to get a key for.
 * @returns {string} A string representing the elen.
 */
Set.prototype.getKey = function(elem) {
    var name = elem;
    if(typeof elem == "object")
        name = elem.toString();
    return name;
};

/**
 * Add an object to a set.
 * @param {elem} elem - A graphics object to be added.
 */
Set.prototype.add = function(elem) {
    if(elem === null) {
        throw new TypeError('Cannot add a null to a set.');
    }
    var key = this.getKey(elem);
    if(!this.containsKey(key)){
        this.numElems++;
    }
    this.set[key] = elem;
};

/**
 * Remove an object from a set.
 * @param {elem} elem - A graphics object to be removed.
 */
Set.prototype.remove = function(elem) {
    if(elem === null) {
        throw new TypeError('Cannot remove null from a set.');
    }
    var key = this.getKey(elem);
    if (this.containsKey(key)) {
        delete this.set[key];
        this.numElems--;
    } else {
        throw new Error('Set does not contain ' + key);
    }
};

/**
 * Check if a set contains a key.
 * @return {boolean} Whether or not the set contains the key.
 */
Set.prototype.containsKey = function(key) {
    return typeof this.set[key] != 'undefined';
};

/**
 * Check if a set contains an elem.
 * @param {elem} elem - A graphics object to be checked.
 * @return {boolean} Whether or not the set contains the elem.
 */
Set.prototype.contains = function(elem) {
    return typeof this.find(elem) != "undefined";
};

/**
 * Get the items in the set.
 * @returns {dictionary} Dictionary of all items in the set
 */
Set.prototype.elems = function() {
    return this.set;
};

/**
 * Finds an elem in the set.
 * @param {elem} elem - An element to look for.
 * @returns {elem|undefined} Either the element (if found) or undefined.
 */
Set.prototype.find = function(elem) {
    var key = this.getKey(elem);
    return this.set[key];
};

/**
 * Creates a union between two sets.
 * @param {Set} otherSet - A set with which a union should be created.
 */
Set.prototype.union = function(otherSet) {
    for(var key in otherSet.elems()){
        this.add(otherSet.find(key));
    }
};

/**
 * Remove items from the set if they are not contained in otherSet.
 * @param {Set} otherSet - A set with which an intersection should be created.
 */
Set.prototype.intersect = function(otherSet) {
    var newElems = {};
    var newSize = 0;

    for(var key in otherSet.elems()){
        if(this.containsKey(key)){
            newSize++;
            newElems[key] = this.find(key);
        }
    }

    this.set = newElems;
    this.numElems = newSize;
};

/**
 * Create a string representation of the set.
 * @returns {string} String representation of the set
 * Follows the syntax 'Set: {elem, elem, elem}'
 */
Set.prototype.toString = function() {

    var result = "Set: {";
    var i = 0;
    for(var key in this.set){
        var cur = this.set[key];
        result += cur;
        if(i < this.size() - 1){
            result += ', ';
        }
        i++;
    }
    result += "}";
    return result;
};

module.exports = Set;
