/**
* FancyUpload - Flash meets Ajax for powerful and elegant uploads.
*
* @version    2.1
*
* @license    MIT License
*
* @author    Harald Kirschner <mail [at] digitarald [dot] de>
* @copyright  Authors
*/
 
var FancyUpload2 = new Class({
 
  Extends: Swiff.Uploader,
 
  options: {
    limitSize: false,
    limitFiles: 5,
    instantStart: false,
    allowDuplicates: false,
    validateFile: $lambda(true), // provide a function that returns true for valid and false for invalid files.
    debug: false,
 
    fileInvalid: null, // called for invalid files with error stack as 2nd argument
    fileCreate: null, // creates file element after select
    fileUpload: null, // called when file is opened for upload, allows to modify the upload options (2nd argument) for every upload
    fileComplete: null, // updates the file element to completed state and gets the response (2nd argument)
    fileRemove: null // removes the element
    /**
     * Events:
     * onBrowse, onSelect, onAllSelect, onCancel, onBeforeOpen, onOpen, onProgress, onComplete, onError, onAllComplete
     */
  },
 
  initialize: function(status, list, options) {
    this.status = $(status);
    this.list = $(list);
 
    this.files = [];
 
    if (options.callBacks) {
      this.addEvents(options.callBacks);
      options.callBacks = null;
    }
 
    this.parent(options);
    this.render();
  },
 
  render: function() {
    this.overallTitle = this.status.getElement('.overall-title');
    this.currentTitle = this.status.getElement('.current-title');
    this.currentText = this.status.getElement('.current-text');
 
    var progress = this.status.getElement('.overall-progress');
    this.overallProgress = new Fx.ProgressBar(progress, {
      text: new Element('span', {'class': 'progress-text'}).inject(progress, 'after')
    });
    progress = this.status.getElement('.current-progress')
    this.currentProgress = new Fx.ProgressBar(progress, {
      text: new Element('span', {'class': 'progress-text'}).inject(progress, 'after')
    });
  },
 
  onLoad: function() {
    this.log('Uploader ready!');
  },
 
  onBeforeOpen: function(file, options) {
    this.log('Initialize upload for "{name}".', file);
    var fn = this.options.fileUpload;
    var obj = (fn) ? fn.call(this, this.getFile(file), options) : options;
    return obj;
  },
 
  onOpen: function(file, overall) {
    this.log('Starting upload "{name}".', file);
    file = this.getFile(file);
    file.element.addClass('file-uploading');
    this.currentProgress.cancel().set(0);
    this.currentTitle.set('html', 'File Progress "{name}"'.substitute(file) );
  },
 
  onProgress: function(file, current, overall) {
    this.overallProgress.start(overall.bytesLoaded, overall.bytesTotal);
    this.currentText.set('html', 'Upload with {rate}/s. Time left: ~{timeLeft}'.substitute({
      rate: (current.rate) ? this.sizeToKB(current.rate) : '- B',
      timeLeft: Date.fancyDuration(current.timeLeft || 0)
    }));
    this.currentProgress.start(current.bytesLoaded, current.bytesTotal);
  },
 
  onSelect: function(file, index, length) {
    var errors = [];
    if (this.options.limitSize && (file.size > this.options.limitSize)) errors.push('size');
    if (this.options.limitFiles && (this.countFiles() >= this.options.limitFiles)) errors.push('length');
    if (!this.options.allowDuplicates && this.getFile(file)) errors.push('duplicate');
    if (!this.options.validateFile.call(this, file, errors)) errors.push('custom');
    if (errors.length) {
      var fn = this.options.fileInvalid;
      if (fn) fn.call(this, file, errors);
      return false;
    }
    (this.options.fileCreate || this.fileCreate).call(this, file);
    this.files.push(file);
    return true;
  },
 
  onAllSelect: function(files, current, overall) {
    this.log('Added ' + files.length + ' files, now we have (' + current.bytesTotal + ' bytes).', arguments);
    this.updateOverall(current.bytesTotal);
    this.status.removeClass('status-browsing');
    if (this.files.length && this.options.instantStart) this.upload.delay(10, this);
  },
 
  onComplete: function(file, response) {
    this.log('Completed upload "' + file.name + '".', arguments);
    this.currentText.set('html', 'Upload complete!');
    this.currentProgress.start(100);
    (this.options.fileComplete || this.fileComplete).call(this, this.finishFile(file), response);
  },
 
  onError: function(file, error, info) {
    this.log('Upload "' + file.name + '" failed. "{1}": "{2}".', arguments);
    (this.options.fileError || this.fileError).call(this, this.finishFile(file), error, info);
  },
 
  onCancel: function() {
    this.log('Filebrowser cancelled.', arguments);
    this.status.removeClass('file-browsing');
  },
 
  onAllComplete: function(current) {
    this.log('Completed all files, ' + current.bytesTotal + ' bytes.', arguments);
    this.updateOverall(current.bytesTotal);
    this.overallProgress.start(100);
    this.status.removeClass('file-uploading');
  },
 
  browse: function(fileList) {
    var ret = this.parent(fileList);
    if (ret !== true){
      if (ret) this.log('An error occured: ' + ret);
      else this.log('Browse in progress.');
    } else {
      this.log('Browse started.');
      this.status.addClass('file-browsing');
    }
  },
 
  upload: function(options) {
    var ret = this.parent(options);
    if (ret !== true) {
      this.log('Upload in progress or nothing to upload.');
      if (ret) alert(ret);
    } else {
      this.log('Upload started.');
      this.status.addClass('file-uploading');
      this.overallProgress.set(0);
    }
  },
 
  removeFile: function(file) {
    var remove = this.options.fileRemove || this.fileRemove;
    if (!file) {
      this.files.each(remove, this);
      this.files.empty();
      this.updateOverall(0);
    } else {
      if (!file.element) file = this.getFile(file);
      this.files.erase(file);
      remove.call(this, file);
      this.updateOverall(this.bytesTotal - file.size);
    }
    this.parent(file);
  },
 
  getFile: function(file) {
    var ret = null;
    this.files.some(function(value) {
      if ((value.name != file.name) || (value.size != file.size)) return false;
      ret = value;
      return true;
    });
    return ret;
  },
 
  countFiles: function() {
    var ret = 0;
    for (var i = 0, j = this.files.length; i < j; i++) {
      if (!this.files[i].finished) ret++;
    }
    return ret;
  },
 
  updateOverall: function(bytesTotal) {
    this.bytesTotal = bytesTotal;
    this.overallTitle.set('html', 'Overall Progress (' + this.sizeToKB(bytesTotal) + ')');
  },
 
  finishFile: function(file) {
    file = this.getFile(file);
    file.element.removeClass('file-uploading');
    file.finished = true;
    return file;
  },
 
  fileCreate: function(file) {
    file.info = new Element('span', {'class': 'file-info'});
    file.element = new Element('li', {'class': 'file'}).adopt(
      new Element('span', {'class': 'file-size', 'html': this.sizeToKB(file.size)}),
      new Element('a', {
        'class': 'file-remove',
        'href': '#',
        'html': 'Remove',
        'events': {
          'click': function() {
            this.removeFile(file);
            return false;
          }.bind(this)
        }
      }),
      new Element('span', {'class': 'file-name', 'html': file.name}),
      file.info
    ).inject(this.list);
  },
 
  fileComplete: function(file, response) {
    this.options.processResponse || this
    var json = $H(JSON.decode(response, true));
    if (json.get('result') == 'success') {
      file.element.addClass('file-success');
      file.info.set('html', json.get('size'));
    } else {
      file.element.addClass('file-failed');
      file.info.set('html', json.get('error') || response);
    }
  },
 
  fileError: function(file, error, info) {
    file.element.addClass('file-failed');
    file.info.set('html', '<strong>' + error + '</strong><br />' + info);
  },
 
  fileRemove: function(file) {
    file.element.fade('out').retrieve('tween').chain(Element.destroy.bind(Element, file.element));
  },
 
  sizeToKB: function(size) {
    var unit = 'B';
    if ((size / 1048576) > 1) {
      unit = 'MB';
      size /= 1048576;
    } else if ((size / 1024) > 1) {
      unit = 'kB';
      size /= 1024;
    }
    return size.round(1) + ' ' + unit;
  },
 
  log: function(text, args) {
    if (this.options.debug && window.console) console.log(text.substitute(args || {}));
  }
 
});
 
/**
* @todo Clean-up, into Date.js
*/
Date.parseDuration = function(sec) {
  var units = {}, conv = Date.durations;
  for (var unit in conv) {
    var value = Math.floor(sec / conv[unit]);
    if (value) {
      units[unit] = value;
      if (!(sec -= value * conv[unit])) break;
    }
  }
  return units;
};
 
Date.fancyDuration = function(sec) {
  var ret = [], units = Date.parseDuration(sec);
  for (var unit in units) ret.push(units[unit] + Date.durationsAbbr[unit]);
  return ret.join(', ');
};
 
Date.durations = {years: 31556926, months: 2629743.83, days: 86400, hours: 3600, minutes: 60, seconds: 1, milliseconds: 0.001};
Date.durationsAbbr = {
  years: 'j',
  months: 'm',
  days: 'd',
  hours: 'h',
  minutes: 'min',
  seconds: 'sec',
  milliseconds: 'ms'
};