/* base.js: Common javascript functions will go here. */

/* Check that variable is not type undefined */
function defined(obj) {
    return typeof(obj)!="undefined";
}

/* Encode dictionary object into our special hash format.
 * Example:
 *   encode_hash({id: 1234, tags:['bar','baz']});
 *   => #id=1234;tags=bar,baz;
 * And we can do fun things like:
 *   encode_hash(decode_hash(encode_hash({id: 1234, tags:['bar','baz']})));
 */
function encode_hash(dict) {
    var ret = "#";

    for(var key in dict) {
        var current_val = dict[key];
        if(!current_val || current_val=="") continue; // For the sake of having somewhat-clean urls
        // Convert arrays to strings
        // NOTE: This breaks for dictionaries. We don't care.
        if(typeof(current_val)=="object") {
            current_val = current_val.join();
        }
        ret += key + "=" + current_val + ";"
    }
    return ret; 
}

/* Decode our special hash format into a dictionary object. 
 * Example:
 *   decode_hash("#id=1234;tags=bar,baz;");
 *   => Object id=1234 tags=bar,baz
 * And we can do fun things like:
 *   decode_hash(encode_hash(decode_hash("#id=1234;tags=bar,baz;")));
 */
function decode_hash(input) {
    if(!defined(input)) {
        input = window.location.hash;
    }

    var result = {};
    var pairs = input.substr(1).split(';');
    for(var i in pairs) {
        var keyvalue = pairs[i].split('=');
        if(keyvalue[0] && keyvalue[1]) result[keyvalue[0]] = keyvalue[1];
    }

    return result;
}

/* Returns the coordinates of the mouse event 'e' RELATIVE
 * to the top-left corner of e.target. Useful for, say,
 * determining where on the ColorWheel the user clicked.
 */
function get_coords(e, position) {
    var x, y;

    var IE = document.all?true:false;

    if(IE && position == 'absolute') {
	x = event.clientX + document.body.scrollLeft;
        y = event.clientY + document.body.scrollTop;
        return Array(x, y);
    }

    if (document.layers) {
        x = e.layerX;
        y = e.layerY;
    } else if (document.all) {
        x = event.offsetX;
        y = event.offsetY;// + document.body.scrollTop; // mac IE fix
    } else if (document.getElementById) {
        var x_off = 0; var y_off = 0;
        if (position == 'relative') {
            var pos = get_position(e.target);
            x_off = pos[0]; y_off = pos[1];
        }
        x = (e.pageX - x_off);
        y = (e.pageY - y_off);
    }
    return Array(x, y);
}

/* Returns the page position of the element 'obj'.
 * See http://blog.firetree.net/2005/07/04/javascript-find-position/
 * for details.
 */
function get_position(obj) {
    var x = 0, y = 0;
    if (obj.offsetParent) {
        while (true) {
            if (obj.offsetParent) {
                x += obj.offsetLeft;
                y += obj.offsetTop;
                obj = obj.offsetParent;
            }
            else break;
        }
    }
    else if (obj.x && obj.y) {
        x = obj.x;
        y = obj.y;
    }
    return Array(x, y);
}

/* Checks whether a color hex is valid
 * (we are assuming only 6 characters ones are valid).
 */
function check_hex_color(hex) {
    var strPattern = /^#?([0-9a-f]){6}$/i;
    return strPattern.test(hex);
}

/* Checks whether a color hex is valid
 * (we are assuming only 6 characters ones are valid).
 */
function check_percentage(value) {
    if(isNaN(value))
        return false;
    if(value < 0 || value > 100)
        return false;
    return true;
}

/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- */
// BASE FUNCTIONS FOR SEARCH PAGES
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- */

// Do a post ajax request - have to pass it a query obj to call handle_response
function postJSON(query, url, data) {    
    $.ajax({type: "POST", 
            url: url, 
            data: data,
            dataType: "json",
            success: function(response) { 
                query.handle_response(response);
            }
          });
}

// Do a get ajax request - have to pass it a query obj to call handle_response
function getJSON(query, url, data) {
    $.ajax({type: "GET",
            url: url,
            data: data,
            dataType: "json",
            success: function(response) {
                query.handle_response(response);
            }
          });
}

// Set the location hash to the given hash. This is a wrapper which helps us
// keep track of when the hash changes (such as when the user hits the back
// button).
function set_hash(hash) {    
    // Crazy hack for safari - if you set window.location.hash directly safari gets stuck in page reloading
    // mode. But if you create a temp form and submit it then it works.
    if (navigator.userAgent.indexOf("Safari") != -1) {
        var new_location = "http://" + window.location.host + window.location.pathname + hash;

        if (window.location.href != new_location) {
            window.location.href = new_location;
        } else {
            current_hash = window.location.hash;
        }
    } else {
        window.location.hash = hash;
        current_hash = window.location.hash;
    }
}

var current_hash = window.location.hash;        // current URL hash

// Handle errors (redundancy for the sake of consistency ftw)
function handle_error(error) {
    var e = $("#errors").html(error).fadeIn("fast").fadeOut(5000);
}

/* Concatenate string lists of tags without repetition.
 * Does not eliminate any tags that appear more than once in oldtags.
 */
function concat_tags(oldtags, newtags) {
    if (!(oldtags && newtags)) return oldtags || newtags; // Is one of them empty?

    var t1 = oldtags.split(',');
    var t2 = newtags.split(',');
    
    for(var i in t2) {        
        if (!in_list(t1, t2[i])) {
            t1.push(t2[i]);
        }
    }   
    return t1.join(",");
}

/* Return the set difference of oldtags and newtags (i.e.
 * oldtags \ newtags). Both oldtags and newtags are treated
 * as sets; any repetition is ignored.
 */
function diff_tags(oldtags, newtags) {
    if (!(oldtags && newtags)) return oldtags;
    
    var t1 = oldtags.split(',');
    var t2 = newtags.split(',');
    
    var out = new Array();
    for(var i in t1) {
        if (!in_list(t2, t1[i])) {
            out.push(t1[i]);
        }
    }
    return out.join(",");
}

/* Appends 'item' to the CSV list 'lst'.
 */
function append_list(lst, item) {
    if (!lst) return item;
    var t = lst.split(",");
    t.push(item);
    return t.join(",");
}

/* Removes the item at position 'index' in the CSV list
 * 'lst'.
 */
function remove_list(lst, index) {
    var t = lst.split(",");
    var out = t.slice(0,index).concat(t.slice(parseInt(index)+1))
    return out.join(",");
}

/* Check if a string is present in a list. */
function in_list(tags, tag) {
    if (tags.length == 0) return false;

    if (typeof(tags) == "string") 
        tags = tags.split(",");

    for (var i in tags) {
        if (tags[i] == tag)
            return true;
    }
    return false;
}

/* Construct pagination. */
function show_pagination(response) {
    // Draw pagination for result images
    var page = QueryHistory.current.page + 1;
    var has_more = QueryHistory.current.has_more;
    var pagination = $("#pages");
    pagination.empty();
    
    // add previous link
    var prev;
    if (page == 1)    
        prev = $('<span class="disabled">&laquo; Previous</span>');
    else
        prev = $('<a onclick="QueryHistory.current.prev();">&laquo; Previous</a>');
    prev.bind('selectstart', {}, function(event) {
        event.preventDefault();
        return false;
    });
    prev.bind('mousedown', {}, function(event) {
        event.preventDefault();
        return false;
    });
    pagination.append(prev);
    
    // add page links
    var curpage = (page - 4) < 1 ? 1 : page - 4;
    for(var i=curpage; i < page; i++) {
        var pageLink = $('<a onclick="QueryHistory.current.goto_page('+ (i-1) +');">' + i + '</a>');
        pagination.append(pageLink);
    }
    
    // add current page (no link)
    var currentPage = $('<span class="active">' + page + '</span>');
    pagination.append(currentPage);
    
    // add next link
    var next;
    if (!has_more)
        next = $('<span class="disabled">Next &raquo;</span>');
    else {
        var ellipsis = $('<span>...</span>');
        pagination.append(ellipsis);
        next = $('<a onclick="QueryHistory.current.next();">Next &raquo;</a>');
    }
    next.bind('selectstart', {}, function(event) {
        event.preventDefault();
        return false;
    });
    next.bind('mousedown', {}, function(event) {
        event.preventDefault();
        return false;
    });
    pagination.append(next);  
}

/* Keep track of the `back` button by polling the window.location object and
 * seeing if the hash changes. Doesn't look like there is a cleaner way to do 
 * this. :-(
 */
function pollLocation() {
    // Check if the hash changed
    if(current_hash != window.location.hash) {
        current_hash = window.location.hash;
        process_hash();
    }
}
setInterval("pollLocation()", 200);

/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- */

/* QueryHistory acts as a global history for all queries.
 */
function QueryHistory(){}
QueryHistory.current = false;
QueryHistory.history = Array();
QueryHistory.capacity = 10;

QueryHistory.add = function(query) {
    QueryHistory.current = query;
    QueryHistory.history.push(QueryHistory.current);
    if (QueryHistory.history.length > QueryHistory.capacity)
        QueryHistory.history.shift();
}

/* Checks in the history to see if any query in the history
 * has attributes that match params.
 */

QueryHistory.find = function(params) {   
    for (var i in QueryHistory.history) {
        var current = QueryHistory.history[i];
        var matches = true;
        var has_checked_something = false;
        for (var attr in params) {
            /* skip tags - user should be able to play around with
             * tags without spawning new searches in the history!
             */
            if (attr == 'tags' || attr == 'autotags')
                continue;
            
            if (!defined(current[attr]) || current[attr] != params[attr]) {
                matches = false;
                break;
            }
            
            has_checked_something = true;
        }
        if (matches && has_checked_something)
            return current;
    }
    return false;
}

/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- */

/* Query is a common base class for the various Query types (e.g. Query,
 * ColorQuery, RandomQuery). It specifies the interface for:
 *
 *  - pagination
 *  - making requests
 *  - handling responses
 *  - redrawing the interface
 *  - forming a hash from this query
 *
 * This makes it easier to add new Query types (e.g. UploadQuery, SketchQuery)
 * by simply extending this class.
 */
function Query() {
    this.page = 0;              // Current page number (int, starting from 0)
    this.total_pages = 50;      // Initial guess at the number of pages (int)
    this.has_more = true;       // Are there remaining pages? (bool)
}

/* REQUEST INTERFACE
 */

/* search(params)
 *
 * params:      dict mapping parameters to values
 *
 * Initiates a new search with the given parameters. Any parameters
 * that are not used by set() will be ignored.
 */
Query.search = function(params) {
    var q = new Query();
    q.set(params);
    q.load();
}

/* set(params)
 *
 * params:      dict mapping parameters to values 
 *
 * Sets the parameters for this search.
 */
Query.prototype.set = function(params) {} // override this

/* load()
 *
 * Forms a valid JSON query using the parameters from set() and
 * sends it to /services/rest.
 */
Query.prototype.load = function() {} // override this

/* RESPONSE INTERFACE
 */

/* handle_response(response)
 *
 * response:    JSON response from /services/rest
 *
 * Handles the response from /services/rest by grabbing whatever data is
 * useful from it, then calls draw() to redraw the interface as needed.
 */
Query.prototype.handle_response = function(response) {} // override this

/* DRAWING INTERFACE
 */
 
/* draw()
 *
 * Redraws the VSL search interface by calling functions in search.js.
 */
Query.prototype.draw = function() {} // override this

/* PAGINATION INTERFACE
 */

/* first()
 *
 * Go to the first page of results.
 */
Query.prototype.first = function() { this.page=0; this.load(); }

/* goto_page(page)
 *
 * page:        result page to load
 *
 * Go to the given page of results, if it exists.
 */
Query.prototype.goto_page = function(page) {
    if(page < 0) return;
    this.page = page;
    this.load();
}

/* next()
 *
 * Go to the next page of results, if it exists.
 */
Query.prototype.next = function() { 
    if(this.total_pages == this.page) return;
    this.page++;
    this.load();
}

/* prev()
 *
 * Go to the previous page of results, if it exists.
 */
Query.prototype.prev = function() {
    if(this.page == 0) return;
    this.page--;
    this.load();
}

/* HASHING INTERFACE
 */

/* to_hash()
 *
 * Returns a hash representing this query. The hash is created
 * using encode_hash, and so can be used directly in the address
 * bar.
 */
Query.prototype.to_hash = function() {} // override this

/* HISTORY INTERFACE
 */

/* thumb_src()
 *
 * Returns the img src to the thumbnail for this query. How this is
 * formed will depend on the type of query.
 */
Query.prototype.thumb_src = function() {} // override this

/* history_thumb()
 *
 * Returns a history thumbnail for this query. How this is
 * formed will depend on the type of query.
 */
Query.prototype.history_thumb = function() {} // override this

