/************
 * Constants
 ************/

/*
 * Constant definition of action types
 */
var Action = {
  CLOSE: "close",
  OK: "ok",
  CANCEL: "cancel",
  APPLY: "apply",
  RETRY: "retry"
};

/*
 * Constant definition of response types
 */
var Response = {
  CLOSE: "close",
  OK: "ok",
  CANCEL: "cancel",
  APPLY: "apply",
  RETRY: "retry"
}

/*
 * Constant definition of sorting orders
 */
var Sorting = {
  ASCENDING: "ascending",
  DESCENDING: "descending"
}

// The human readable title, e.g. "Foo"
providers.addProvider("resourceProperties", "title", new Property("Titel"));
// The MIME type, e.g. "image/jpeg"
providers.addProvider("resourceProperties", "type", new Property("Typ"));
// The size in bytes, e.g. 21586
providers.addProvider("resourceProperties", "size", new Property("Größe", 0));
// The dimensions as [W, H], e.g. [200, 150]
providers.addProvider("resourceProperties", "dimensions", new Property("Dimensionen", [0, 0]));
// The rating as ratio [0..1], e.g. 0.3
providers.addProvider("resourceProperties", "rating", new Property("Bewertung", 0.0));
// Possible tags as ["tag1", "tag2", ...], e.g. ["holiday", "winter"]
providers.addProvider("resourceProperties", "tags", new Property("Tags", []));

// See http://stackoverflow.com/questions/1465374/javascript-event-keycode-constants/1465409#1465409
if (typeof KeyEvent === "undefined") {

  var KeyEvent = {
    DOM_VK_CANCEL: 3,
    DOM_VK_HELP: 6,
    DOM_VK_BACK_SPACE: 8,
    DOM_VK_TAB: 9,
    DOM_VK_CLEAR: 12,
    DOM_VK_RETURN: 13,
    DOM_VK_ENTER: 14,
    DOM_VK_SHIFT: 16,
    DOM_VK_CONTROL: 17,
    DOM_VK_ALT: 18,
    DOM_VK_PAUSE: 19,
    DOM_VK_CAPS_LOCK: 20,
    DOM_VK_ESCAPE: 27,
    DOM_VK_SPACE: 32,
    DOM_VK_PAGE_UP: 33,
    DOM_VK_PAGE_DOWN: 34,
    DOM_VK_END: 35,
    DOM_VK_HOME: 36,
    DOM_VK_LEFT: 37,
    DOM_VK_UP: 38,
    DOM_VK_RIGHT: 39,
    DOM_VK_DOWN: 40,
    DOM_VK_PRINTSCREEN: 44,
    DOM_VK_INSERT: 45,
    DOM_VK_DELETE: 46,
    DOM_VK_0: 48,
    DOM_VK_1: 49,
    DOM_VK_2: 50,
    DOM_VK_3: 51,
    DOM_VK_4: 52,
    DOM_VK_5: 53,
    DOM_VK_6: 54,
    DOM_VK_7: 55,
    DOM_VK_8: 56,
    DOM_VK_9: 57,
    DOM_VK_SEMICOLON: 59,
    DOM_VK_EQUALS: 61,
    DOM_VK_A: 65,
    DOM_VK_B: 66,
    DOM_VK_C: 67,
    DOM_VK_D: 68,
    DOM_VK_E: 69,
    DOM_VK_F: 70,
    DOM_VK_G: 71,
    DOM_VK_H: 72,
    DOM_VK_I: 73,
    DOM_VK_J: 74,
    DOM_VK_K: 75,
    DOM_VK_L: 76,
    DOM_VK_M: 77,
    DOM_VK_N: 78,
    DOM_VK_O: 79,
    DOM_VK_P: 80,
    DOM_VK_Q: 81,
    DOM_VK_R: 82,
    DOM_VK_S: 83,
    DOM_VK_T: 84,
    DOM_VK_U: 85,
    DOM_VK_V: 86,
    DOM_VK_W: 87,
    DOM_VK_X: 88,
    DOM_VK_Y: 89,
    DOM_VK_Z: 90,
    DOM_VK_CONTEXT_MENU: 93,
    DOM_VK_NUMPAD0: 96,
    DOM_VK_NUMPAD1: 97,
    DOM_VK_NUMPAD2: 98,
    DOM_VK_NUMPAD3: 99,
    DOM_VK_NUMPAD4: 100,
    DOM_VK_NUMPAD5: 101,
    DOM_VK_NUMPAD6: 102,
    DOM_VK_NUMPAD7: 103,
    DOM_VK_NUMPAD8: 104,
    DOM_VK_NUMPAD9: 105,
    DOM_VK_MULTIPLY: 106,
    DOM_VK_ADD: 107,
    DOM_VK_SEPARATOR: 108,
    DOM_VK_SUBTRACT: 109,
    DOM_VK_DECIMAL: 110,
    DOM_VK_DIVIDE: 111,
    DOM_VK_F1: 112,
    DOM_VK_F2: 113,
    DOM_VK_F3: 114,
    DOM_VK_F4: 115,
    DOM_VK_F5: 116,
    DOM_VK_F6: 117,
    DOM_VK_F7: 118,
    DOM_VK_F8: 119,
    DOM_VK_F9: 120,
    DOM_VK_F10: 121,
    DOM_VK_F11: 122,
    DOM_VK_F12: 123,
    DOM_VK_F13: 124,
    DOM_VK_F14: 125,
    DOM_VK_F15: 126,
    DOM_VK_F16: 127,
    DOM_VK_F17: 128,
    DOM_VK_F18: 129,
    DOM_VK_F19: 130,
    DOM_VK_F20: 131,
    DOM_VK_F21: 132,
    DOM_VK_F22: 133,
    DOM_VK_F23: 134,
    DOM_VK_F24: 135,
    DOM_VK_NUM_LOCK: 144,
    DOM_VK_SCROLL_LOCK: 145,
    DOM_VK_COMMA: 188,
    DOM_VK_PERIOD: 190,
    DOM_VK_SLASH: 191,
    DOM_VK_BACK_QUOTE: 192,
    DOM_VK_OPEN_BRACKET: 219,
    DOM_VK_BACK_SLASH: 220,
    DOM_VK_CLOSE_BRACKET: 221,
    DOM_VK_QUOTE: 222,
    DOM_VK_META: 224
  };
}

/**********
 * Classes
 **********/

/*
 * Simple storage for credentials
 */
function Credentials(username, password) {

  this.username = (username ? username : null);
  this.password = (password ? password : null);
  // TODO: realm/context required?
  
  this.toString = function() { return "[object Credentials]"; };
}

/*
 * Wrapper for resource property definitions
 */
function Property(title, defaultValue) {

  this.title = title;
  this.defaultValue = (typeof defaultValue == "undefined" ? "" : defaultValue);
  
  this.toString = function() { return "[object Property]"; };
}

/*
 * Signifies a provider for resources
 */
function Source(url, info) {

  this.url = url;
  this.resources = [];
  this.properties = {
    title: null,      // The descriptive title
  };
  this.allowed = {};     // Allowed request types
  this.restricted = {};  // Restricted request types
  this.credentials = {}; // Credentials for restricted request types
  
  // Ensure closing slash
  /*if (this.url[this.url.length - 1] != "/") {
  
    this.url += "/";
  }*/
  
  (function() {
    
    if (info) {
    
      for (var property in this.properties) {
      
        var value = info[property];
        
        if (value) {
        
          this.properties[property] = value;
        }
      }
      
      // Turn ["type1", ...] into { type1: true } for easier checking
      if ("allowed" in info) {
      
        for (var i = 0; i < info.allowed.length; ++i) {
        
          this.allowed[info.allowed[i]] = true;
        }
      }
      
      if ("restricted" in info) {
      
        for (var i = 0; i < info.restricted.length; ++i) {
        
          this.restricted[info.restricted[i]] = true;
        }
      }
    }
    
    if (!this.properties.title) {
    
      this.properties.title = this.url;
    }
  }).call(this);
  
  /**
   * Whether this source required credentials
   * for a specific MediaRequest type
   */
  this.requiresCredentials = function(requestType) {
  
    var restricted = requestType in this.restricted;
    var credentialsExist = requestType in this.credentials;
    
    return (restricted && !credentialsExist);
  };
  
  this.toString = function() {
  
    return this.properties.title;
  }
}

/*
 * Signifies a single resource item
 */
function Resource(source, url, properties) {

  this.source = source;
  this.url = url; // Automatically read by Firefox, Chrome, Safari, Opera?
  this.fullURL = this.url;
  this.properties = {
    thumbnail: ""
  };
  
  (function() {
  
    if (!/^[a-z]+:\/\//.test(this.fullURL)) {

      this.fullURL = this.source.url + this.url;
    }
    
    if (properties) {
  
      for (var name in properties) {
      
        this.properties[name] = properties[name];
      }
    }
    
    var propertyProvider = providers.getProviders("resourceProperties");
    
    for (var name in propertyProvider) {
    
      if (!(name in this.properties)) {
      
        this.properties[name] = propertyProvider[name].defaultValue;
      }
    }
    
    // Watch for resource property changes
    $(providers).bind({
      providerAdded: onProviderAdded.bind(this),
      providerRemoved: onProviderRemoved.bind(this)
    });
    
    if (!this.properties.title) {
    
      this.properties.title = this.url.split("/").pop(); // Filename
    }
    
    this.properties.prototype = {};
    this.properties.prototype.toJSON = (function() {
    
      var jsonObject = this.properties;
      
      if (("thumbnail" in this.properties) && !/^[a-z]+:\/\//.test(this.properties.thumbnail)) {
      
        jsonObject.thumbnail = this.source.url + this.properties.thumbnail;
      }

      // Provide credentials required to retrieve this resource
      if (MediaRequest.LIST in this.source.credentials) {

        jsonObject.__credentials__ = this.source.credentials
      }
    }).bind(this);
  }).call(this);
  
  /*
   * Sets property default values if necessary
   */
  function onProviderAdded(e) {
    
    if (!(e.providerName) in this.properties) {
    
      this.properties[e.providerName] = e.provider.defaultValue;
    }
  }
  
  /*
   * Deletes properties if they still have their default values
   */
  function onProviderRemoved(e) {
    
    if ((e.providerName in this.properties) &&
        (this.properties[e.providerName] == e.provider.defaultValue)) {
    
      delete this.properties[e.providerName];
    }
  }
  
  /*
   * Returns an appropriate string representation
   */
  this.toString = function() {
  
    return this.properties.title;
  };
}

/*
 * A rectangular area
 */
function Rectangle(left, top, width, height) {

  // Ensure always positive values
  this.left = (width < 0 ? left + width : left);
  this.top = (height < 0 ? top + height : top);
  this.width = Math.abs(width);
  this.height = Math.abs(height);
  this.right = this.left + this.width;
  this.bottom = this.top + this.height;
  
  /*
   * Checks whether a given location
   * is contained within this rectangle
   */
  this.contains = function(left, top) {
  
    if (this.left <= left &&
        left <= this.right &&
        this.top <= top &&
        top <= this.bottom) {
        
      return true;
    }
    
    return false;
  }
  
  /*
   * Checks whether a given rectangle
   * intersects this rectangle
   */
  this.intersects = function(other) {
  
    if (other.right < this.left || // To the left
        other.left > this.right || // To the right
        other.bottom < this.top || // Above
        other.top > this.bottom) { // Below
    
      return false;
    }
  
    return true;
  }
  
  this.toString = function() {
  
    return "Rectangle(" +
      [this.left, this.top, this.width, this.height].join(", ") +
    ")";
  }
}

/*
 * Calculates the number of days, hours,
 * minutes and seconds in a time span
 */
function TimeSpan(span) {

  (function() {
  
    this.seconds = Math.floor(span % 60);
    span = Math.floor(span / 60);
    this.minutes = Math.floor(span % 60);
    span = Math.floor(span / 60);
    this.days = Math.floor(span / 60);
    this.hours = Math.floor(span % 60);
  }).call(this);
  
  this.toString = function() {
  
    return [this.days + "d",
            this.hours + "h",
            this.minutes + "m",
            this.seconds + "s"].join(", ");
  }
}

/*
 * Unique object for keeping track of transactions
 */
function Transaction() {

  var $this = $(this);
  var _id = Transaction.id;
  ++Transaction.id;
  var _status = Transaction.STARTED;
  
  (function() {
  
    var properties = {
      id: {
        get: function() { return _id; }
      },
      status: {
        get: function() { return _status; },
        set: function(status) {
        
          if (status != _status) {
          
            _status = status;

            var statusChangedEvent = new jQuery.Event("statusChanged");
            statusChangedEvent.status = status;

            $this.trigger(statusChangedEvent);
          }
        }
      }
    };
    
    try {
    
      Object.defineProperties(this, properties);
    } catch (e) {
    
      for (var property in properties) {
      
        this[property] = properties[property];
      }
    }
  }).call(this);
}
Transaction.id = 0;
// Constant definition of transaction statuses
Transaction.STARTED = "started";
Transaction.DONE = "done";
Transaction.FAILED = "failed";
