new function() {
  var app = new App(window), book, enquire;
  
  app.start(function() {
    var node;
    if (node = document.getElementById('book'))
      book = app.load(node);
    if (node = document.getElementById('enquire'))
      enquire = app.load(node);
    if (node = document.getElementById('selectDates'))
      app.load(node);
  })
  
  
  // select dates:
  new function() {
    var courses;
    
    app.bind({
      
      selectDates: {
        init: function() {
          var com = this.apply('busy'), eventId = this.read('data-event-id'), event;
          xhr(url('/api/events/multicourse', { id: this.read('data-id') }), function(r) {
            var i = 0;
            com.clear('busy');
            if (r.ok)
              if (courses = r.object) {
                com.reset({
                  $loop: {
                    id: 'p', h: courses, f: function(name, events) {
                      event = event || events[eventId];
                      return {
                        label: name, 'select.eventSelector': {
                          'data-course-name': name, name: 'event_id_' + (++i)
                        }
                      }
                    }
                  }
                })
                com.each('eventSelector', function(i) {
                  this.update(i == 0 || (i == 1 && event['Start date']), event);
                })
              }
          });
        }
      },

      eventSelector: {
        init: function() {
          this.course = courses[this.read('data-course-name')];
          this.handle('change', function() {
            var com = this, m = this.course[value(this.node)];
            while (com = com.next)
              if (com.names.eventSelector) {
                com.update(!!m && m['Start date'], {});
                m = false;
              }
          })
        },
        
        empty: function() {
          var n = this.node.childNodes.length;
          for (var i = 0; i < n; i++) this.node.remove(i);
          return this;
        },
        
        update: function(v, selected) {
          var vs = this.empty().node.options, i = 0;
          if (this.seek(true))
            vs[i++] = new Option("I'll choose this later thanks", '');
          if (v) {
            var date = v !== true && new Date(v)
            for (var id in this.course) {
              var m = this.course[id];
              if (v === true || new Date(m['Start date']) > date || m.id == selected.id)
                vs[i++] = new Option(
                  m['Start date'].slice(0, m['Start date'].indexOf(m['Start date'].match(/\d\d\d\d/)[0]) + 4),
                  m.id,
                  m.id == selected.id
                )
            }
          }
        }
      }
    })
  }
  
  // forms:
  new function() {
    app.bind({

      book: {
        init: function() {
          this.error = this.first('error');

          var book = this, h = this.ctrls = {
            person: {}, payment: {}
          };
          this.each('ctrl', function() {
            var hh = h;
            if (this.parent != book)
              hh = h[this.parent.name];
            hh[this.node.name || this.node.getAttribute('data-name')] = this;
          })

          this.handle('click', function() {
            this.error.update('');
          });

          this.handle('click', this.ctrls.seperateBillingAddress.node, function() {
            book.toggle('seperateBillingAddress', this.ctrls.seperateBillingAddress.toJSON())
          })

          this.handle('submit', function() {
            if (!this.names.busy) {
              if (!this.ctrls.terms.toJSON())
                this.error.update('You must accept the terms & conditions');
              else {
                var book = this.apply('busy');
                var events = [];
                for (var ctrl, i = 1; i < 7; i++)
                  if (ctrl = this.ctrls['event id ' + i])
                    events.push(value(ctrl.node));
                xhr('/book', cp({ events: events }, this.ctrls), function(r) {
                  if (r.error)
                    book.clear('busy').error.update(r.reason);
                  else
                    window.location = r.location || '/pages/success';
                });
              }            
            }
            return false;
          });
        }
      },
      brochureFields: {},
      enquire: {
        init: function() {
          var h = {};
          this.each('ctrl', function() {
            h[this.node.name || this.read('data-name')] = this;
          })
          this.handle('submit', function() {

            if (!this.names.busy) {
              this.apply('busy');
              xhr('/enquire', h, function(r) {
                if (r.error)
                  enquire.clear('busy').first('error').update(r.reason);
                else
                  window.location = r.location || '/pages/enquired';
              });
            }
            return false;
          })
          var reqBrochure;
          this.handle('click', reqBrochure = document.getElementById('reqBrochure'), function() {
            this.last('brochureFields').node.style.display = reqBrochure.checked ? '' : 'none';
          })       
        }
      },

      radios: {
        toJSON: function() {
          for (var i = 0, node, nodes = this.node.getElementsByTagName('input'); node = nodes[i]; i++)
            if (node.checked) return node.value;
        }
      },

      error: {
        update: function(s) {
          return this.reset({ data: s });
        }
      },
      person: {}, payment: {},

      date: {
        toJSON: function() {
          var d = new Date(); selects = this.node.getElementsByTagName('select');
          d.setDate(value(selects[0]));
          d.setMonth(value(selects[1]));
          d.setYear(value(selects[2]));
          return d.toUTCString();
        } 
      },

      ctrl: {

        toJSON: function() {
          return value(this.node);
        }
      },
      option: {
        toJSON: function() {
          return this.node.checked;
        }
      }
    })
  }

  function value(node) {
    switch (node.tagName.toLowerCase()) {
      case 'select':
        node = node.options[node.selectedIndex];
        return node.value || node.text;
      default:
        if (node.type == 'checkbox') return node.checked && node.value
        return node.value;
    }
  }
}