var ProductForm = new JS.Class('ProductForm', {
  include: JS.State,
  
  /**
   * All the niggly user interface details are handled by the ProductForm's
   * associated Dialogue object, leaving the ProductForm to just manage
   * purchasing state
   */
  initialize: function(container, options) {
    options = options || {};
    
    var form = container.descendants('form').at(0);
    
    if (!form || form.length < 1) return;
    
    this._dialogue = new this.klass.Dialogue(container, form, options);
    this._counter  = options.counter || BasketCounter;
    
    this._dialogue.getForm().on('submit', function(form, evnt) {
      evnt.stopDefault();
      this.submitForm();
    }, this);
    
    this.setState('READY');
  },
  
  states: {
    /**
     * Products in the READY state are able to accept user input; the details
     * of the interface are handled by the associated Dialogue object.
     */
    READY: {
      /**
       * When the user submits the form, this function will serialize the form
       * data and POST it to the server. We post to a dummy endpoint, relying on
       *  the OO Action to return appropriate response.
       */
      submitForm: function() {
        var form = this._dialogue.getForm().node, self = this;
        
        this.setState('POSTING');
        
        Ojay.HTTP.POST("/blank.html", Ojay.Forms.getData(form), {
          onSuccess: function(response) {
            self._handleSuccess(response);
          },
          
          onFailure: function(response) {
            self._handleFailure(response);
          }
        });
      }
    },
    
    /**
     * Products which are in the process of sending a request to the server
     * should not react to user input until the request completes.
     */
    POSTING: {
      /**
       * Successful requests (i.e., ones that result in the product being added
       * to the user's basket) should trigger a transition to the ADDED state;
       * disable the dialogue and its toggle; update the basket counter; and
       * add a note to the product details informing the user that the product
       * is now in their basket.
       */
      _handleSuccess: function(response) {
        var json = response.responseText.parseJSON();
        
        if (json.success) {
          this.setState('ADDED');
          this._dialogue.addMessage(json.success.message, {className: 'added'});
          this._dialogue.disable();
          this._counter.update(json.success.itemCount, json.success.basketTotal);
        } else {
          this.setState('READY');
          this._dialogue.updateFields(json.errors);
        }
      },
      
      /**
       * Failed requests (i.e. ones where the server throws an error, times out
       * etc.) should be handled relatively gracefully.
       */
      _handleFailure: function(response) {
        this.setState('READY');
        this._dialogue.updateFields({
          network: 'An error occurred while trying to add the item to \
                    your basket. Please try again.'
        });
      }
    },
    
    /**
     * Products in the ADDED state should have most of their functionality
     * disabled; if the user wishes to amend their basket, they need to go to
     * the basket page.
     */
    ADDED: {}
  },
  
  extend: {
    Dialogue: new JS.Class('ProductForm.Dialogue', {
      include: JS.State,
      
      initialize: function(product, form, options) {
        this._options   = options || {};
        this._container = Ojay(product).setStyle({position: 'relative'});
        this._link      = this._container.descendants('a.wrapping-link');
        this._form      = form;
        
        this.setState('CREATED');
        this._addToggle();
      },
      
      getForm: function() {
        return this._form;
      },
      
      _addToggle: function() {
        this._toggle = Ojay(Ojay.HTML.p({className: 'product-details-link'}, function(H) {
          H.span('Add to basket');
        }));
        this._link.insert(this._toggle, 'after');
        this._toggle.on('click', this.toggle, this);
      },
      
      states: {
        CREATED: {
          setup: function() {
            this._makeDialogue();
            this.setState('READY');
          },
          
          toggle: function() {
              this.setup();
              this.toggle();
          },
          
          _makeDialogue: function() {
            var offset = this._options.offset || 0,
                x      = this._options.horizontalOffset || offset,
                y      = this._options.verticalOffset   || offset,
                w      = this._container.getWidth()  + (x * 2) + 'px',
                h      = this._container.getHeight() + (y * 2) + 'px';
            
            this._close    = Ojay(Ojay.HTML.span({className: 'close'}, 'Close'));
            var inner      = Ojay(Ojay.HTML.div({
              className: 'inner',
              style: {height: h}
            }));
            
            this._details = Ojay(this._container.descendants('.details').node.cloneNode(true));
            inner.insert(this._details, 'top');
            
            this._dialogue = Ojay(Ojay.HTML.div({
              className: 'dialogue',
              style: {
                position: 'absolute',
                left:     x > 0 ? -(x + 1) + 'px' : 0,
                top:      y > 0 ? -(y + 1) + 'px' : 0,
                opacity:  0,
                width:    w,
                height:   h
              }
            }, inner, this._close)).hide();
            
            this._close.on('click', this.toggle, this);
            this._container.insert(this._dialogue, 'bottom');
            inner.insert(this._form.setStyle({display: 'block'}), 'bottom');
          }          
        },
        
        READY: {
          toggle: function() {
            this.setState('ANIMATING');
            this._dialogue.show().animate({
              opacity: {
                from: 0,
                to:   1
              }
            }, 0.4)._(function() {
              this.setState('BUYING');
            }.bind(this));
          }
        },
        
        BUYING: {
          toggle: function() {
            this.setState('ANIMATING');
            this._dialogue.animate({
              opacity: {
                from: 1,
                to:   0
              }
            }, 0.4).hide()._(function() {
              this.setState('READY');
            }.bind(this));
          },
          
          /**
           * Replace existing form error messages with those in the errors
           * object.
           */
          updateFields: function(errors) {
            this._form.descendants('.error').remove();
            
            for (name in errors) {
              if (name != 'warnings' && !{}[name]) {
                this.addErrorMessage(errors[name]);
              }
            }
            
            if (errors.warnings)
              errors.warnings.forEach(function(error) {
                this.addErrorMessage(error);
              }, this);
          },
          
          /**
           * 
           */
          addMessage: function(text, attrs, insert) {
            insert = insert || {};
            var parent   = insert.parent   || this._details,
                position = insert.position || 'bottom';
            var html = Ojay.HTML.p(attrs, text);
            parent.insert(html, position);
          },
          
          /**
           * 
           */
          addErrorMessage: function(text) {
            this.addMessage(text, {className: 'error'}, {parent: this._form, position: 'bottom'});
          },
          
          /**
           * Once a product has been bought, the dialogue toggle should be
           * removed, and a link to the basket added in its place.
           */
          disable: function() {
            this._toggle.remove();
            this._form.remove();
            this._container.insert(Ojay.HTML.p({className: 'add-product-link', style: {marginTop: '7px'}}, function(H) {
              H.a({href: BasketCounter.getBasketURI()}, 'View your basket');
            }));
            this.setState('DISABLED');
            return this;
          }
        },
        
        ANIMATING: {},
        
        DISABLED: {
          /**
           * The dialogue still needs to be able to be closed when disabled.
           */
          toggle: function() {
            if (this._dialogue.getStyle('display') == 'hidden') return;
            this.setState('ANIMATING');
            this._dialogue.animate({
              opacity: {
                from: 1,
                to:   0
              }
            }, 0.4).hide();
          }
        }
      }
    })
  }
});

