var Widget = {}

// options:
//   (before|after)(Open|Close): a callback function
//   background: a css value for the background property.
//     defaults to 'url(/images/lib_widget/layer_bg.png)'
//   top_margin: a css value for the widget's top margin.
//     defauts to '80px'
//   duration: the duration argument for each appear/fade effect.
//     defauts to 0.5
//
Widget.Layer = function(widget, options) {
  options = options || {}
  this.widget = widget
  var ef = Prototype.emptyFunction
  this.callbacks = {
    beforeOpen: options.beforeOpen || ef,
    afterOpen: options.afterOpen || ef,
    beforeClose: options.beforeClose || ef,
    afterClose: options.afterClose || ef
  }
  this.top_margin = options.top_margin || '80px'
  this.duration = options.duration || 0.5
  this.background = new Element('div', {
    'class': 'widget_background widget_layer', 'style': 'position: fixed; top: 0; right: 0; bottom: 0; left: 0; display: none'
  })
  this.background.setStyle({ 'background': options.background || 'url(/images/lib_widget/layer_bg.png)' })
  this.foreground = new Element('div', {
    'class': 'widget_foreground widget_layer', 'style': 'display: none'
  })
  this.background.insert(this.foreground)
}


Widget.Layer.prototype.isOpen = function() { return !!this._open }
Widget.Layer.prototype.isClosed = function() { return !this._open }


;(function() {
  var z_index = 99

  Widget.Layer.prototype.open = function() {
    if (!this._open) {
      this._open = true
      this.background.setStyle({ 'zIndex': z_index += 1 })
      //NOTE: using update() here causes problems in IE7 (surprise surprise)
      $A(this.foreground.childNodes).each(Element.Methods.remove)
      this.callbacks.beforeOpen(this)
      this.foreground.insert(this.widget.element())
      this.foreground.firstDescendant().setStyle({ 'margin': this.top_margin + ' auto' })
      $(document.body).insert(this.background)
      var self = this
      this.background.appear({
        duration: this.duration,
        afterFinish: function() {
          self.foreground.appear({
            duration: this.duration,
            afterFinish: function() {
              self.callbacks.afterOpen(self)
              self.widget.start && self.widget.start()
            }
          })
        }
      })
    }
  }

  Widget.Layer.prototype.close = function() {
    if (this._open) {
      this._open = false
      z_index -= 1
      this.widget.stop && this.widget.stop()
      this.callbacks.beforeClose(this)
      var self = this
      this.foreground.fade({
        duration: this.duration,
        afterFinish: function() {
          self.background.fade({
            duration: this.duration,
            afterFinish: function() {
              self.background.remove()
              self.callbacks.afterClose(self)
            }
          })
        }
      })
    }
  }

})()


// options:
//   (outer|inner|button)_style: a hash of css style rules to be merged with the
//     default styles for each element (outer_frame, inner_frame, close_button)
//   layer: a hash of options for the frame's layer object.
//   duration: the duration argument for the openning effects of the frame.
//     defaults to 0.5
//
Widget.Frame = function(widget, options) {
  options = options || {}

  this.duration = options.duration || 0.5

  var layer = new Widget.Layer(this, options.layer)

  var outer_frame = new Element('div', {
    'class': 'outer_frame widget_frame'
  }).setStyle(Widget.Frame.outer_style.merge(options.outer_style).toObject())

  outer_frame.insert(new Element('div', {
    'class': 'close_frame widget_frame'
  }).setStyle(Widget.Frame.button_style.merge(options.button_style).toObject()))

  var inner_frame = new Element('div', {
    'class': 'inner_frame widget_frame'
  }).setStyle(Widget.Frame.inner_style.merge(options.inner_style).toObject())

  outer_frame.insert(inner_frame)
  outer_frame.firstDescendant().observe('click', function() {
    layer.close()
  })

  this.element = function() { return outer_frame }

  this.start = function() {
    var target = widget.element()
    var dim = { 'width': target.getStyle('width'), 'height': target.getStyle('height') }
    inner_frame.setStyle(dim)
    inner_frame.insert(target)
    outer_frame.morph('height:' + dim.height, {
      duration: this.duration,
      afterFinish: function() {
        outer_frame.morph('width:' + dim.width, {
          duration: this.duration,
          afterFinish: function() {
            inner_frame.appear({
              duration: 0.5,
              afterFinish: function() { widget.start && widget.start() }
            })
          }
        })
      }
    })
  }

  this.stop = function() { widget.stop && widget.stop() }

  this.open = function() {
    if (layer.isClosed()) {
      outer_frame.setStyle({ 'width': '0', 'height': '0' })
      inner_frame.setStyle({ 'display': 'none' })
      $A(inner_frame.childNodes).each(Element.Methods.remove)
      layer.open()
    }
  }

  this.close = function() { layer.close() }
  this.isOpen = function() { return layer.isOpen() }
  this.isClosed = function() { return layer.isClosed() }
}


Widget.Frame.outer_style = $H({
  'MozBorderRadius': '7px',
  'WebkitBorderRadius': '7px',
  'borderRadius': '7px',
  'MozBoxShadow': '0 2px 8px #000000',
  'WebkitBoxShadow': '0 2px 8px #000000',
  'boxShadow': '0 2px 8px #000000',
  'border': '1px solid #555',
  'background': 'url(/images/lib_widget/frame_bg.png)',
  'position': 'relative',
  'padding': '16px'
})

Widget.Frame.button_style = $H({
  'position': 'absolute',
  'top': '-16px',
  'right': '-16px',
  'cursor': 'pointer',
  'width': '34px',
  'height': '34px',
  'background': 'url(/images/lib_widget/frame_close.png) no-repeat'
})

Widget.Frame.inner_style = $H({
  'padding': '0',
  'margin': '0'
})


;(function() {
  var getDim = function(dim, e) {
    var w = e.getStyle(dim)
    if (w == '0px' || w == 'NaNpx') { return null }
    return w
  }
  Widget.getWidth = function(e) { return getDim('width', e) }
  Widget.getHeight = function(e) { return getDim('height', e) }
})()

// options:
//   (start|stop): functions to be set as this widget's start & stop functions.
//   (width|height): a css value for the dimention of the target element.
//     defaults to the element's dimention, then to the default_(width|height)
//     option, and finally to a predefined dimention of 600px by 400px.
//   default_(width|height): see (width|height) option.
//   overflow: a css value for the overflow rule of the target element.
//   frame: a hash of options for the widget's frame object.
//
Widget.Content = function(element, options) {
  element = $(element)
  element.remove()
  options = options || {}

  if (options.start) this.start = options.start
  if (options.stop) this.stop = options.stop

  options.overflow && element.setStyle({ 'overflow': options.overflow })

  element.setStyle({
    'width': options.width || Widget.getWidth(element) || options.default_width || '600px',
    'height': options.height || Widget.getHeight(element) || options.default_height || '400px'
  })

  var frame = new Widget.Frame(this, options.frame)

  this.element = function() { return element }
  this.open = function() { frame.open() }
  this.close = function() { frame.close() }
  this.isOpen = function() { return frame.isOpen() }
  this.isClosed = function() { return frame.isClosed() }
}


// options: (same as AjaxContent, but with different defaults)
//
Widget.Popup = function(element, options) {
  Widget.Content.call(this, element, $H(Widget.Popup.default_options).merge(options).toObject())
}

Widget.Popup.default_options = {
  'default_width': '400px',
  'default_height': '300px',
  'frame': {
    'outer_style': { 'background': 'white', 'border': 'none' },
    'layer': { 'duration': 0.25 },
    'duration': 0.3
  }
}


// options: (same as Content with the following added)
//   widget: the name of a Content class as a sting, sans the Widget namespace.
//     defaults to 'Content'.
//
Widget.Label = function(label, options) {
  label = $(label)
  options = options || {}
  var widget = new Widget[options.widget || 'Content'](label.readAttribute('for'), options)
  label.setStyle('cursor:pointer')
  label.observe('click', function() { widget.open() })
}


// options: (same as Label)
//   NOTE: If no widget option is passed, the class_name is used as the value of
//   the widget option.
//
Widget.Label.initLabels = function(class_name, options) {
  options = $H({ widget: class_name }).merge(options).toObject()
  $$('label.' + class_name).each(function(label) { Widget.Label(label, options) })
}


// options:
//   onLoad: a callback function that is passed this widget's element after the
//     ajax call has finished loading the content, but before the widget opens.
//   (start|stop): functions to be set as this widget's start & stop functions.
//   (width|height): a css value for the dimention of the content's container div.
//     defaults to the content's first element's dimention, then to the
//     default_(width|height) option, and finally to a predefined dimention of
//     600px by 400px.
//   default_(width|height): see (width|height) option.
//   overflow: a css value for the overflow rule of the content's container div.
//   frame: a hash of options for the widget's frame object.
//
Widget.AjaxContent = function(url, options) {
  options = options || {}
  if (options.start) this.start = options.start
  if (options.stop) this.stop = options.stop

  var content = new Element('div', { 'class': 'widget_content' })
  options.overflow && content.setStyle({ 'overflow': options.overflow })

  this.element = function() { return content }

  var frame = new Widget.Frame(this, options.frame)
  var openning = false

  this.open = function() {
    if (frame.isClosed() && !openning) {
      openning = true
      $(document.body).setStyle('cursor: progress')
      new Ajax.Request(url, {
        method: 'get',
        onSuccess: function(transport) {
          content.update(transport.responseText)
          content.setStyle({
            'width': (options.width || Widget.getWidth(content.firstDescendant()) || options.default_width || '600px'),
            'height': (options.height || Widget.getHeight(content.firstDescendant()) || options.default_height || '400px')
          })
          options.onLoad && options.onLoad(content)
          $(document.body).setStyle('cursor: auto')
          openning = false
          frame.open()
        },
        onFailure: function() {
          content.update('<p style="color:red;font-weight:bold;font-size:16px;margin:0">Error loading content!</p>')
          content.setStyle({ 'width': '200px', 'height': '30px' })
          $(document.body).setStyle('cursor: auto')
          openning = false
          frame.open()
        }
      })
    }
  }

  this.close = function() { frame.close() }
  this.isOpen = function() { return frame.isOpen() }
  this.isClosed = function() { return frame.isClosed() }
  this.isOpenning = function() { return openning }
}


// options: (same as AjaxContent, but without the onLoad callback)
//
Widget.AjaxYoutube = function(url, options) {
  this.stop = function() {
    vid = this.element().down('object')
    vid && vid.pauseVideo && vid.pauseVideo()
  }

  Widget.AjaxContent.call(this, url, $H(options).merge({
    onLoad: function(content) {
      onYouTubePlayerReady = function() { content.down('object').playVideo() }
    }
  }).toObject())
}


// options: (same as AjaxContent, but with different defaults)
//
Widget.AjaxPopup = function(url, options) {
  Widget.AjaxContent.call(this, url, $H(Widget.Popup.default_options).merge(options).toObject())
}


// options: (same as AjaxContent with the following added)
//   widget: the name of an AjaxContent class as a sting, sans the Widget namespace.
//     defaults to 'AjaxContent'.
//
Widget.AjaxLink = function(link, options) {
  link = $(link)
  options = options || {}
  var widget = new Widget[options.widget || 'AjaxContent'](link.href, options)
  link.href = 'javascript:void 0'
  link.observe('click', function() { widget.open() })
}


// options: (same as AjaxLink)
//   NOTE: If no widget option is passed, the class_name is used as the value of
//   the widget option.
//
Widget.AjaxLink.initLinks = function(class_name, options) {
  options = $H({ widget: class_name }).merge(options).toObject()
  $$('a.' + class_name).each(function(link) { Widget.AjaxLink(link, options) })
}


Widget.FrameTest = function(options) {
  var e = new Element('div', { 'style': 'width:360px;height:260px;background:#6666FF' })
  var n = new Element('div', { 'style': 'width:20px;height:20px;background:green' })
  n.observe('click', function() { new Widget.FrameTest(options).open() })
  e.insert(n)

  this.element = function() { return e }
  this.start = function() {
    e.morph('background:#66FF66')
  }
  this.stop = function() {
    e.morph('background:#6666FF', { duration: 0.25 })
  }

  frame = new Widget.Frame(this, options)

  this.open = function() { frame.open() }
  this.close = function() { frame.close() }
  this.isOpen = function() { return frame.isOpen() }
  this.isClosed = function() { return frame.isClosed() }
}

