'use strict'
const EventEmitter = require('events')
const NOTIFICATION_TYPE = require('./api/utils/enums').NOTIFICATION_TYPE
const DEVICE_TYPE = require('./api/utils/enums').DEVICE_TYPE

module.exports = (function () {
  //private
  let app

  /**
   * Get native stack
   * @returns array of javascript calls
   */
  const getStack = function () {
    // Save original Error.prepareStackTrace
    const origPrepareStackTrace = Error.prepareStackTrace
    // Override with function that just returns `stack`
    Error.prepareStackTrace = (_, stack) => stack

    // Create a new `Error`, which automatically gets `stack`
    const err = new Error()
    // Evaluate `err.stack`, which calls our new `Error.prepareStackTrace`
    const stack = err.stack
    // Restore original `Error.prepareStackTrace`
    Error.prepareStackTrace = origPrepareStackTrace
    // Remove superfluous function call on stack
    stack.shift() // getStack --> Error
    return stack
  }

  /**
   * Get path of the current plugin
   * @returns path
   */
  const getCaller = () => {
    const stack = getStack()
    stack.shift()
    let obj
    for (let i = 0; i < stack.length; i++) {
      obj = stack[i]
      if (obj.getFileName().toLowerCase().indexOf('plugin-') !== -1) {
        break
      }
    }
    // Return caller's caller
    return obj.getFileName()
  }

  /**
   * Get name of the current plugin
   * @returns string name
   */
  const getCurrentPlugin = () => {
    const pathString = getCaller()
    const parts = pathString.split('/')
    let part = parts.find(part => part.indexOf('lisa-plugin') !== -1)
    if (!part) {
      part = 'unknown'
    }
    const name = part.replace(/lisa\-/, '').replace(/plugin\-/, '').toCamelCase()
    return app.packs.pluginsManager.plugins[name] ? app.packs.pluginsManager.plugins[name].fullName : 'unknown'
  }

  return class LISA extends EventEmitter {
    get NOTIFICATION_TYPE() {
      return NOTIFICATION_TYPE
    }

    get DEVICE_TYPE() {
      return DEVICE_TYPE
    }

    constructor(currentApp) {
      super()
      app = currentApp
    }

    toCamelCase(input) {
      return input.toCamelCase()
    }

    getRooms() {
      return app.orm.Room.findAll().then(rooms => {
        return Promise.resolve(rooms.map(room => room.toJSON()))
      })
    }

    createRoom(name) {
      return app.orm.Room.create({ name: name }).then(room => {
        return Promise.resolve(room.toJSON())
      })
    }

    createOrUpdateDevices(device, criteria) {
      const plugin = getCurrentPlugin()
      //this.log.debug(plugin)
      let promise

      if (Array.isArray(device)) {
        const toCreate = device.filter(element => !element.id).map(device => {
          device.pluginName = plugin
          return device
        })
        const toUpdate = device.filter(element => element.id)
        const todo = []

        if (toCreate.length > 0) {
          todo.push(app.orm.Device.bulkCreate(toCreate))
        }
        if (toUpdate.length > 0) {
          for (const deviceToUpdate of toUpdate) {
            todo.push(this.createOrUpdateDevices(deviceToUpdate))
          }
        }
        promise = Promise.all(todo)
      }
      else {
        device.pluginName = plugin
        if (device.id) {
          promise = app.orm.Device.update(device, {
            where: {
              id: device.id
            }
          })
        }
        else if (criteria) {
          criteria.pluginName = plugin
          promise = app.orm.Device.update(device, {
            where: criteria
          })
        }
        else {
          promise = app.orm.Device.create(device)
        }
      }

      return promise.then(device => {
        if (Array.isArray(device)) {
          return device.map(item => {
            if (item.toRawData) {
              return item.toRawData()
            }
            return item
          })
        }
        else {
          if (device.toRawData) {
            return device.toRawData()
          }
          return device
        }
      })
    }

    /**
     *
     * @param criteria to retrieve specific devices
     * @returns Promise
     */
    findDevices(criteria) {
      criteria = criteria || {}
      const plugin = getCurrentPlugin()
      //this.log.debug(plugin)
      criteria.pluginName = plugin

      const promise = criteria.id ? app.orm.Device.find({
        where: criteria
      }) : app.orm.Device.findAll({
        where: criteria
      })

      return promise.then(devices => {
        if (Array.isArray(devices)) {
          return devices.map(device => device.toRawData())
        }
        else {
          return devices.toRawData()
        }
      })
    }

    /**
     * Send notification to the user(s)
     * @param to @optional user id to send the notif to
     * @param title of the notif
     * @param type
     * @param desc of the notif
     * @param image of the notif
     * @param defaultAction of the notif
     * @param action of the notif
     * @param lang of the notif
     * @returns Promise - notif data
     */
    sendNotification(to, title, type, desc, image, defaultAction, action, lang) {
      const plugin = getCurrentPlugin()
      return app.services.NotificationService.sendNotification(to, plugin, title, type, desc, image, defaultAction,
        action, lang, 'default').then(notification => notification.toJSON())
    }

    /**
     * Retrieve plugin preferences
     * @returns {Promise} preferences or error
     */
    getPreferences() {
      const plugin = getCurrentPlugin()
      this.log.debug(plugin)
      return app.orm.Preference.findById(plugin + '_prefs').then(preferences => {
        return preferences ? preferences.value : {}
      })
    }

    /**
     * Set plugin preferences
     * @param preferences to save
     * @returns {Promise} saved preferences or error
     */
    setPreferences(preferences) {
      const plugin = getCurrentPlugin()
      this.log.debug(plugin)
      return app.orm.Preference.upsert({
        key: plugin + '_prefs',
        value: preferences
      }).then(() => {
        return preferences
      })
    }

    addChatBot(botId, botData) {
      const plugin = getCurrentPlugin()
      botData.pluginName = plugin
      return app.services.ChatBotService.addBot(botId, botData).then(chatBot => Promise.resolve(chatBot.toJSON()))
    }

    getChatBot(botId = null) {
      const plugin = getCurrentPlugin()
      const where = {
        pluginName: plugin
      }
      if (botId) {
        where.name = botId
      }
      return app.orm.ChatBot.findAll({
        where: where
      }).then(chatBots => Promise.resolve(chatBots.map(bot => bot.toJSON())))
    }

    updateChatBot(botId, botData) {
      const plugin = getCurrentPlugin()
      botData.pluginName = plugin
      return app.services.ChatBotService.updateBot(botId, botData).then(_ => Promise.resolve())
    }

    deleteChatBot(botId) {
      //const plugin = getCurrentPlugin()
      return app.services.ChatBotService.deleteBot(botId).then(_ => Promise.resolve())
    }

    get log() {
      const getArguments = (args) => {
        const plugin = getCurrentPlugin()
        const mainArguments = Array.prototype.slice.call(args)
        return [plugin + ':'].concat(mainArguments)
      }
      return {
        debug: function () {
          app.config.log.pluginLogger.debug.apply(this, getArguments(arguments))
        },
        info: function () {
          app.config.log.pluginLogger.info.apply(this, getArguments(arguments))
        },
        error: function () {
          app.config.log.pluginLogger.error.apply(this, getArguments(arguments))
        },
        silly: function () {
          app.config.log.pluginLogger.silly.apply(this, getArguments(arguments))
        },
        verbose: function () {
          app.config.log.pluginLogger.verbose.apply(this, getArguments(arguments))
        },
        warn: function () {
          app.config.log.pluginLogger.warn.apply(arguments)
        }
      }
    }

    get _() {
      return app._
    }

    get i18n() {
      return app._
    }

    get bonjour() {
      return app.bonjour
    }

    get mdns() {
      return app.services.MdnsService.mdns
    }

    get serialPort() {
      return app.serialPort
    }

    get ir() {
      return {
        send: (remote, action) => {
          return app.services.IRService.send(remote, action)
        }
      }
    }
  }
})()


var argle = require('argle');
var isFunction = require('lodash.isfunction');
var random = require('randomstring');

var unhooks = {};

/* Public */

/**
 * Executes the provided function with the output
 * on the provided streams. Accepts options to silence
 * the output going to the console.
 *
 * @param streams a stream or list of streams.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {Array} a list of stream outputs.
 */
function capture(streams, opts, exec) {
  var args = _shift(opts, exec);

  opts = args[0];
  exec = args[1];

  if (!Array.isArray(streams)) {
    streams = [ streams ];
  }

  var outputs = [];

  streams.forEach(function (stream, index) {
    outputs[index] = '';

    startCapture(stream, opts, function (output) {
      outputs[index] += output;
    });
  });

  exec();

  streams.forEach(stopCapture);

  return outputs;
}

/**
 * Captures stdout and stderr into an object for
 * the provided execution scope.
 *
 * @param opts to set flags on the capture.
 * @param exec the function execute inside the capture.
 * @returns {{stdout: String, stderr: String}}
 */
function captureStdio(opts, exec) {
  var streams = [
    process.stdout,
    process.stderr
  ];

  var outputs = capture(streams, opts, exec);

  return {
    stdout: outputs.shift(),
    stderr: outputs.shift()
  };
}

/**
 * Captures stderr for the provided execution scope.
 *
 * @param opts to set flags on the capture.
 * @param exec the function execute inside the capture.
 * @returns {String}
 */
function captureStderr(opts, exec) {
  return _baseCapture(process.stderr, opts, exec);
}

/**
 * Captures stdout for the provided execution scope.
 *
 * @param opts to set flags on the capture.
 * @param exec the function execute inside the capture.
 * @returns {String}
 */
function captureStdout(opts, exec) {
  return _baseCapture(process.stdout, opts, exec);
}

/**
 * Listens to a provided stream, and executes the provided
 * function for every write call. Accepts options to silence
 * the output going to the console.
 *
 * Returns a function to call when you wish to stop listening
 * to the call.
 *
 * @param stream a stream to listen on.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {Function}
 */
function hook(stream, opts, exec) {
  var args = _shift(opts, exec);

  opts = args[0];
  exec = args[1];

  var old_write = stream.write;

  stream.write = (function override(stream, writer) {
    return function write(string, encoding, fd) {
      exec(string, encoding, fd);

      if (!opts['quiet']) {
        writer.apply(stream, [ string, encoding, fd ]);
      }
    }
  })(stream, stream.write);

  return function unhook() {
    stream.write = old_write;
    return true;
  }
}

/**
 * Delegate to #capture with a quiet passthrough.
 *
 * @param stream a stream to listen on.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {Array} a list of stream outputs.
 */
function intercept(stream, opts, exec) {
  return _wrapIntercept(capture, stream, opts, exec);
}

/**
 * Delegate to #captureStdio with a quiet passthrough.
 *
 * @param opts to set flags on the capture.
 * @param exec the function execute inside the capture.
 * @returns {String}
 */
function interceptStdio(opts, exec) {
  return _wrapIntercept(captureStdio, opts, exec);
}

/**
 * Delegate to #captureStderr with a quiet passthrough.
 *
 * @param opts to set flags on the capture.
 * @param exec the function execute inside the capture.
 * @returns {String}
 */
function interceptStderr(opts, exec) {
  return _wrapIntercept(captureStderr, opts, exec);
}

/**
 * Delegate to #captureStdout with a quiet passthrough.
 *
 * @param opts to set flags on the capture.
 * @param exec the function execute inside the capture.
 * @returns {String}
 */
function interceptStdout(opts, exec) {
  return _wrapIntercept(captureStdout, opts, exec);
}

/**
 * Starts a capture on the provided stream using the
 * provided options and stream execution.
 *
 * @param stream a stream to listen on.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {boolean}
 */
function startCapture(stream, opts, exec) {
  var unhook = hook(stream, opts, exec);
  var str_id = random.generate();

  unhooks[str_id] = unhook;
  stream._id = str_id;

  return true;
}

/**
 * Stops a capture on the provided stream.
 *
 * @param stream a stream to stop the capture on.
 * @returns {boolean}
 */
function stopCapture(stream) {
  return !!(unhooks[stream._id] && unhooks[stream._id]());
}

/**
 * Delegate to #startCapture with a quiet passthrough.
 *
 * @param stream a stream to listen on.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {boolean}
 */
function startIntercept(stream, opts, exec) {
  return _wrapIntercept(startCapture, stream, opts, exec);
}

/**
 * Delegate to #stopCapture with a quiet passthrough.
 *
 * @param stream a stream to stop the capture on.
 * @returns {boolean}
 */
function stopIntercept(stream) {
  return stopCapture(stream);
}

/* Exports */

module.exports.hook = hook;
module.exports.capture = capture;
module.exports.captureStdio = captureStdio;
module.exports.captureStderr = captureStderr;
module.exports.captureStdout = captureStdout;
module.exports.intercept = intercept;
module.exports.interceptStdio = interceptStdio;
module.exports.interceptStderr = interceptStderr;
module.exports.interceptStdout = interceptStdout;
module.exports.startCapture = startCapture;
module.exports.stopCapture = stopCapture;
module.exports.startIntercept = startIntercept;
module.exports.stopIntercept = stopIntercept;

/* Private */

/**
 * Captures a given stream into a string.
 *
 * @param stream a stream to listen on.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {String}
 * @private
 */
function _baseCapture(stream, opts, exec) {
  return capture(stream, opts, exec).pop();
}

/**
 * Shifts an optional options argument against a
 * function to return defaults if not provided.
 *
 * @param opts options to verify.
 * @param exec a function to verify against.
 * @returns {{Object}, {Function}}
 * @private
 */
function _shift(opts, exec) {
  return argle.shift(
    [ opts, exec ],
    { defaults: [ {} ] },
    isFunction
  );
}

/**
 * Wraps capturing functions with quiet flags to
 * allow for interception.
 *
 * @param func the function to delegate to.
 * @param stream a stream to listen on.
 * @param opts to set flags on the capture.
 * @param exec the function to call with the output.
 * @returns {*}
 * @private
 */
function _wrapIntercept(func, stream, opts, exec) {
  var idex = Number(arguments.length > 3);
  var args = _shift(arguments[idex + 1], arguments[idex + 2]);

  opts = args[0];
  exec = args[1];

  opts.quiet = true;

  return idex
    ? func(stream, opts, exec)
    : func(opts, exec);
}


/**
* Combodate - 1.0.7
* Dropdown date and time picker.
* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
* Uses momentjs as datetime library http://momentjs.com.
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
*
* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
* In combodate:
* 12:00 pm --> 12:00 (24-h format, midday)
* 12:00 am --> 00:00 (24-h format, midnight, start of day)
*
* Differs from momentjs parse rules:
* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
*
*
* Author: Vitaliy Potapov
* Project page: http://github.com/vitalets/combodate
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
**/
(function ($) {

    var Combodate = function (element, options) {
        this.$element = $(element);
        if(!this.$element.is('input')) {
            $.error('Combodate should be applied to INPUT element');
            return;
        }
        this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
        this.init();
     };

    Combodate.prototype = {
        constructor: Combodate,
        init: function () {
            this.map = {
                //key   regexp    moment.method
                day:    ['D',    'date'],
                month:  ['M',    'month'],
                year:   ['Y',    'year'],
                hour:   ['[Hh]', 'hours'],
                minute: ['m',    'minutes'],
                second: ['s',    'seconds'],
                ampm:   ['[Aa]', '']
            };

            this.$widget = $('').html(this.getTemplate());

            this.initCombos();

            //update original input on change
            this.$widget.on('change', 'select', $.proxy(function(e) {
                this.$element.val(this.getValue()).change();
                // update days count if month or year changes
                if (this.options.smartDays) {
                    if ($(e.target).is('.month') || $(e.target).is('.year')) {
                        this.fillCombo('day');
                    }
                }
            }, this));

            this.$widget.find('select').css('width', 'auto');

            // hide original input and insert widget
            this.$element.hide().after(this.$widget);

            // set initial value
            this.setValue(this.$element.val() || this.options.value);
        },

        /*
         Replace tokens in template with <select> elements
        */
        getTemplate: function() {
            var tpl = this.options.template;
            var customClass = this.options.customClass;

            //first pass
            $.each(this.map, function(k, v) {
                v = v[0];
                var r = new RegExp(v+'+'),
                    token = v.length > 1 ? v.substring(1, 2) : v;

                tpl = tpl.replace(r, '{'+token+'}');
            });

            //replace spaces with
            tpl = tpl.replace(/ /g, ' ');

            //second pass
            $.each(this.map, function(k, v) {
                v = v[0];
                var token = v.length > 1 ? v.substring(1, 2) : v;

                tpl = tpl.replace('{'+token+'}', '<select class="'+k+' '+customClass+'"></select>');
            });

            return tpl;
        },

        /*
         Initialize combos that presents in template
        */
        initCombos: function() {
            for (var k in this.map) {
                var $c = this.$widget.find('.'+k);
                // set properties like this.$day, this.$month etc.
                this['$'+k] = $c.length ? $c : null;
                // fill with items
                this.fillCombo(k);
            }
        },

        /*
         Fill combo with items
        */
        fillCombo: function(k) {
            var $combo = this['$'+k];
            if (!$combo) {
                return;
            }

            // define method name to fill items, e.g `fillDays`
            var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1);
            var items = this[f]();
            var value = $combo.val();

            $combo.empty();
            for(var i=0; i<items.length; i++) {
                $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
            }

            $combo.val(value);
        },

        /*
         Initialize items of combos. Handles `firstItem` option
        */
        fillCommon: function(key) {
            var values = [],
                relTime;

            if(this.options.firstItem === 'name') {
                //need both to support moment ver < 2 and  >= 2
                relTime = moment.relativeTime || moment.langData()._relativeTime;
                var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
                //take last entry (see momentjs lang files structure)
                header = header.split(' ').reverse()[0];
                values.push(['', header]);
            } else if(this.options.firstItem === 'empty') {
                values.push(['', '']);
            }
            return values;
        },

        /*
        fill day
        */
        fillDay: function() {
            var items = this.fillCommon('d'), name, i,
                twoDigit = this.options.template.indexOf('DD') !== -1,
                daysCount = 31;

            // detect days count (depends on month and year)
            // originally https://github.com/vitalets/combodate/pull/7
            if (this.options.smartDays && this.$month && this.$year) {
                var month = parseInt(this.$month.val(), 10);
                var year = parseInt(this.$year.val(), 10);

                if (!isNaN(month) && !isNaN(year)) {
                    daysCount = moment([year, month]).daysInMonth();
                }
            }

            for (i = 1; i <= daysCount; i++) {
                name = twoDigit ? this.leadZero(i) : i;
                items.push([i, name]);
            }
            return items;
        },

        /*
        fill month
        */
        fillMonth: function() {
            var items = this.fillCommon('M'), name, i,
                longNames = this.options.template.indexOf('MMMM') !== -1,
                shortNames = this.options.template.indexOf('MMM') !== -1,
                twoDigit = this.options.template.indexOf('MM') !== -1;

            for(i=0; i<=11; i++) {
                if(longNames) {
                    //see https://github.com/timrwood/momentjs.com/pull/36
                    name = moment().date(1).month(i).format('MMMM');
                } else if(shortNames) {
                    name = moment().date(1).month(i).format('MMM');
                } else if(twoDigit) {
                    name = this.leadZero(i+1);
                } else {
                    name = i+1;
                }
                items.push([i, name]);
            }
            return items;
        },

        /*
        fill year
        */
        fillYear: function() {
            var items = [], name, i,
                longNames = this.options.template.indexOf('YYYY') !== -1;

            for(i=this.options.maxYear; i>=this.options.minYear; i--) {
                name = longNames ? i : (i+'').substring(2);
                items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
            }

            items = this.fillCommon('y').concat(items);

            return items;
        },

        /*
        fill hour
        */
        fillHour: function() {
            var items = this.fillCommon('h'), name, i,
                h12 = this.options.template.indexOf('h') !== -1,
                h24 = this.options.template.indexOf('H') !== -1,
                twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
                min = h12 ? 1 : 0,
                max = h12 ? 12 : 23;

            for(i=min; i<=max; i++) {
                name = twoDigit ? this.leadZero(i) : i;
                items.push([i, name]);
            }
            return items;
        },

        /*
        fill minute
        */
        fillMinute: function() {
            var items = this.fillCommon('m'), name, i,
                twoDigit = this.options.template.indexOf('mm') !== -1;

            for(i=0; i<=59; i+= this.options.minuteStep) {
                name = twoDigit ? this.leadZero(i) : i;
                items.push([i, name]);
            }
            return items;
        },

        /*
        fill second
        */
        fillSecond: function() {
            var items = this.fillCommon('s'), name, i,
                twoDigit = this.options.template.indexOf('ss') !== -1;

            for(i=0; i<=59; i+= this.options.secondStep) {
                name = twoDigit ? this.leadZero(i) : i;
                items.push([i, name]);
            }
            return items;
        },

        /*
        fill ampm
        */
        fillAmpm: function() {
            var ampmL = this.options.template.indexOf('a') !== -1,
                ampmU = this.options.template.indexOf('A') !== -1,
                items = [
                    ['am', ampmL ? 'am' : 'AM'],
                    ['pm', ampmL ? 'pm' : 'PM']
                ];
            return items;
        },

        /*
         Returns current date value from combos.
         If format not specified - `options.format` used.
         If format = `null` - Moment object returned.
        */
        getValue: function(format) {
            var dt, values = {},
                that = this,
                notSelected = false;

            //getting selected values
            $.each(this.map, function(k, v) {
                if(k === 'ampm') {
                    return;
                }
                var def = k === 'day' ? 1 : 0;

                values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;

                if(isNaN(values[k])) {
                   notSelected = true;
                   return false;
                }
            });

            //if at least one visible combo not selected - return empty string
            if(notSelected) {
               return '';
            }

            //convert hours 12h --> 24h
            if(this.$ampm) {
                //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
                if(values.hour === 12) {
                    values.hour = this.$ampm.val() === 'am' ? 0 : 12;
                } else {
                    values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
                }
            }

            dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);

            //highlight invalid date
            this.highlight(dt);

            format = format === undefined ? this.options.format : format;
            if(format === null) {
               return dt.isValid() ? dt : null;
            } else {
               return dt.isValid() ? dt.format(format) : '';
            }
        },

        setValue: function(value) {
            if(!value) {
                return;
            }

            // parse in strict mode (third param `true`)
            var dt = typeof value === 'string' ? moment(value, this.options.format, true) : moment(value),
                that = this,
                values = {};

            //function to find nearest value in select options
            function getNearest($select, value) {
                var delta = {};
                $select.children('option').each(function(i, opt){
                    var optValue = $(opt).attr('value'),
                    distance;

                    if(optValue === '') return;
                    distance = Math.abs(optValue - value);
                    if(typeof delta.distance === 'undefined' || distance < delta.distance) {
                        delta = {value: optValue, distance: distance};
                    }
                });
                return delta.value;
            }

            if(dt.isValid()) {
                //read values from date object
                $.each(this.map, function(k, v) {
                    if(k === 'ampm') {
                       return;
                    }
                    values[k] = dt[v[1]]();
                });

                if(this.$ampm) {
                    //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
                    if(values.hour >= 12) {
                        values.ampm = 'pm';
                        if(values.hour > 12) {
                            values.hour -= 12;
                        }
                    } else {
                        values.ampm = 'am';
                        if(values.hour === 0) {
                            values.hour = 12;
                        }
                    }
                }

                $.each(values, function(k, v) {
                    //call val() for each existing combo, e.g. this.$hour.val()
                    if(that['$'+k]) {

                        if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
                           v = getNearest(that['$'+k], v);
                        }

                        if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
                           v = getNearest(that['$'+k], v);
                        }

                        that['$'+k].val(v);
                    }
                });

                // update days count
                if (this.options.smartDays) {
                    this.fillCombo('day');
                }

               this.$element.val(dt.format(this.options.format)).change();
            }
        },

        /*
         highlight combos if date is invalid
        */
        highlight: function(dt) {
            if(!dt.isValid()) {
                if(this.options.errorClass) {
                    this.$widget.addClass(this.options.errorClass);
                } else {
                    //store original border color
                    if(!this.borderColor) {
                        this.borderColor = this.$widget.find('select').css('border-color');
                    }
                    this.$widget.find('select').css('border-color', 'red');
                }
            } else {
                if(this.options.errorClass) {
                    this.$widget.removeClass(this.options.errorClass);
                } else {
                    this.$widget.find('select').css('border-color', this.borderColor);
                }
            }
        },

        leadZero: function(v) {
            return v <= 9 ? '0' + v : v;
        },

        destroy: function() {
            this.$widget.remove();
            this.$element.removeData('combodate').show();
        }

        //todo: clear method
    };

    $.fn.combodate = function ( option ) {
        var d, args = Array.apply(null, arguments);
        args.shift();

        //getValue returns date as string / object (not jQuery object)
        if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
          return d.getValue.apply(d, args);
        }

        return this.each(function () {
            var $this = $(this),
            data = $this.data('combodate'),
            options = typeof option == 'object' && option;
            if (!data) {
                $this.data('combodate', (data = new Combodate(this, options)));
            }
            if (typeof option == 'string' && typeof data[option] == 'function') {
                data[option].apply(data, args);
            }
        });
    };

    $.fn.combodate.defaults = {
         //in this format value stored in original input
        format: 'DD-MM-YYYY HH:mm',
        //in this format items in dropdowns are displayed
        template: 'D / MMM / YYYY   H : mm',
        //initial value, can be `new Date()`
        value: null,
        minYear: 1970,
        maxYear: 2015,
        yearDescending: true,
        minuteStep: 5,
        secondStep: 1,
        firstItem: 'empty', //'name', 'empty', 'none'
        errorClass: null,
        customClass: '',
        roundTime: true, // whether to round minutes and seconds if step > 1
        smartDays: false // whether days in combo depend on selected month: 31, 30, 28
    };

}(window.jQuery));


"use strict";

function _(message, opts) {
    return `${opts && opts.context ? opts.context : "Value"} ${message}.`;
}

function type(V) {
    if (V === null) {
        return "Null";
    }
    switch (typeof V) {
        case "undefined":
            return "Undefined";
        case "boolean":
            return "Boolean";
        case "number":
            return "Number";
        case "string":
            return "String";
        case "symbol":
            return "Symbol";
        case "object":
            // Falls through
        case "function":
            // Falls through
        default:
            // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for
            // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for
            // such cases. So treat the default case as an object.
            return "Object";
    }
}

// Round x to the nearest integer, choosing the even integer if it lies halfway between two.
function evenRound(x) {
    // There are four cases for numbers with fractional part being .5:
    //
    // case |     x     | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 |   example
    //   1  |  2n + 0.5 |  2n      |  2n + 1  |  2n      |   >    |  0.5  |   0   |  0.5 ->  0
    //   2  |  2n + 1.5 |  2n + 1  |  2n + 2  |  2n + 2  |   >    |  0.5  |   1   |  1.5 ->  2
    //   3  | -2n - 0.5 | -2n - 1  | -2n      | -2n      |   <    | -0.5  |   0   | -0.5 ->  0
    //   4  | -2n - 1.5 | -2n - 2  | -2n - 1  | -2n - 2  |   <    | -0.5  |   1   | -1.5 -> -2
    // (where n is a non-negative integer)
    //
    // Branch here for cases 1 and 4
    if ((x > 0 && (x % 1) === +0.5 && (x & 1) === 0) ||
        (x < 0 && (x % 1) === -0.5 && (x & 1) === 1)) {
        return censorNegativeZero(Math.floor(x));
    }

    return censorNegativeZero(Math.round(x));
}

function integerPart(n) {
    return censorNegativeZero(Math.trunc(n));
}

function sign(x) {
    return x < 0 ? -1 : 1;
}

function modulo(x, y) {
    // https://tc39.github.io/ecma262/#eqn-modulo
    // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos
    const signMightNotMatch = x % y;
    if (sign(y) !== sign(signMightNotMatch)) {
        return signMightNotMatch + y;
    }
    return signMightNotMatch;
}

function censorNegativeZero(x) {
    return x === 0 ? 0 : x;
}

function createIntegerConversion(bitLength, typeOpts) {
    const isSigned = !typeOpts.unsigned;

    let lowerBound;
    let upperBound;
    if (bitLength === 64) {
        upperBound = Math.pow(2, 53) - 1;
        lowerBound = !isSigned ? 0 : -Math.pow(2, 53) + 1;
    } else if (!isSigned) {
        lowerBound = 0;
        upperBound = Math.pow(2, bitLength) - 1;
    } else {
        lowerBound = -Math.pow(2, bitLength - 1);
        upperBound = Math.pow(2, bitLength - 1) - 1;
    }

    const twoToTheBitLength = Math.pow(2, bitLength);
    const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1);

    return (V, opts) => {
        if (opts === undefined) {
            opts = {};
        }

        let x = +V;
        x = censorNegativeZero(x); // Spec discussion ongoing: https://github.com/heycam/webidl/issues/306

        if (opts.enforceRange) {
            if (!Number.isFinite(x)) {
                throw new TypeError(_("is not a finite number", opts));
            }

            x = integerPart(x);

            if (x < lowerBound || x > upperBound) {
                throw new TypeError(_(
                    `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, opts));
            }

            return x;
        }

        if (!Number.isNaN(x) && opts.clamp) {
            x = Math.min(Math.max(x, lowerBound), upperBound);
            x = evenRound(x);
            return x;
        }

        if (!Number.isFinite(x) || x === 0) {
            return 0;
        }
        x = integerPart(x);

        // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if
        // possible. Hopefully it's an optimization for the non-64-bitLength cases too.
        if (x >= lowerBound && x <= upperBound) {
            return x;
        }

        // These will not work great for bitLength of 64, but oh well. See the README for more details.
        x = modulo(x, twoToTheBitLength);
        if (isSigned && x >= twoToOneLessThanTheBitLength) {
            return x - twoToTheBitLength;
        }
        return x;
    };
}

exports.any = V => {
    return V;
};

exports.void = function () {
    return undefined;
};

exports.boolean = function (val) {
    return !!val;
};

exports.byte = createIntegerConversion(8, { unsigned: false });
exports.octet = createIntegerConversion(8, { unsigned: true });

exports.short = createIntegerConversion(16, { unsigned: false });
exports["unsigned short"] = createIntegerConversion(16, { unsigned: true });

exports.long = createIntegerConversion(32, { unsigned: false });
exports["unsigned long"] = createIntegerConversion(32, { unsigned: true });

exports["long long"] = createIntegerConversion(64, { unsigned: false });
exports["unsigned long long"] = createIntegerConversion(64, { unsigned: true });

exports.double = (V, opts) => {
    const x = +V;

    if (!Number.isFinite(x)) {
        throw new TypeError(_("is not a finite floating-point value", opts));
    }

    return x;
};

exports["unrestricted double"] = V => {
    const x = +V;

    return x;
};

exports.float = (V, opts) => {
    const x = +V;

    if (!Number.isFinite(x)) {
        throw new TypeError(_("is not a finite floating-point value", opts));
    }

    if (Object.is(x, -0)) {
        return x;
    }

    const y = Math.fround(x);

    if (!Number.isFinite(y)) {
        throw new TypeError(_("is outside the range of a single-precision floating-point value", opts));
    }

    return y;
};

exports["unrestricted float"] = V => {
    const x = +V;

    if (isNaN(x)) {
        return x;
    }

    if (Object.is(x, -0)) {
        return x;
    }

    return Math.fround(x);
};

exports.DOMString = function (V, opts) {
    if (opts === undefined) {
        opts = {};
    }

    if (opts.treatNullAsEmptyString && V === null) {
        return "";
    }

    if (typeof V === "symbol") {
        throw new TypeError(_("is a symbol, which cannot be converted to a string", opts));
    }

    return String(V);
};

exports.ByteString = (V, opts) => {
    const x = exports.DOMString(V, opts);
    let c;
    for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) {
        if (c > 255) {
            throw new TypeError(_("is not a valid ByteString", opts));
        }
    }

    return x;
};

exports.USVString = (V, opts) => {
    const S = exports.DOMString(V, opts);
    const n = S.length;
    const U = [];
    for (let i = 0; i < n; ++i) {
        const c = S.charCodeAt(i);
        if (c < 0xD800 || c > 0xDFFF) {
            U.push(String.fromCodePoint(c));
        } else if (0xDC00 <= c && c <= 0xDFFF) {
            U.push(String.fromCodePoint(0xFFFD));
        } else if (i === n - 1) {
            U.push(String.fromCodePoint(0xFFFD));
        } else {
            const d = S.charCodeAt(i + 1);
            if (0xDC00 <= d && d <= 0xDFFF) {
                const a = c & 0x3FF;
                const b = d & 0x3FF;
                U.push(String.fromCodePoint((2 << 15) + ((2 << 9) * a) + b));
                ++i;
            } else {
                U.push(String.fromCodePoint(0xFFFD));
            }
        }
    }

    return U.join("");
};

exports.object = (V, opts) => {
    if (type(V) !== "Object") {
        throw new TypeError(_("is not an object", opts));
    }

    return V;
};

// Not exported, but used in Function and VoidFunction.

// Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so
// handling for that is omitted.
function convertCallbackFunction(V, opts) {
    if (typeof V !== "function") {
        throw new TypeError(_("is not a function", opts));
    }
    return V;
}

[
    Error,
    ArrayBuffer, // The IsDetachedBuffer abstract operation is not exposed in JS
    DataView, Int8Array, Int16Array, Int32Array, Uint8Array,
    Uint16Array, Uint32Array, Uint8ClampedArray, Float32Array, Float64Array
].forEach(func => {
    const name = func.name;
    const article = /^[AEIOU]/.test(name) ? "an" : "a";
    exports[name] = (V, opts) => {
        if (!(V instanceof func)) {
            throw new TypeError(_(`is not ${article} ${name} object`, opts));
        }

        return V;
    };
});

// Common definitions

exports.ArrayBufferView = (V, opts) => {
    if (!ArrayBuffer.isView(V)) {
        throw new TypeError(_("is not a view on an ArrayBuffer object", opts));
    }

    return V;
};

exports.BufferSource = (V, opts) => {
    if (!(ArrayBuffer.isView(V) || V instanceof ArrayBuffer)) {
        throw new TypeError(_("is not an ArrayBuffer object or a view on one", opts));
    }

    return V;
};

exports.DOMTimeStamp = exports["unsigned long long"];

exports.Function = convertCallbackFunction;

exports.VoidFunction = convertCallbackFunction;


var Decimal = require("decimal.js");

function addFrameAfter(time, value, delay, array, step, resolution)
{
	time = getAfterTime(time, step, resolution);

	addFrameAt(time, value, delay, array);
}

// `delay` only used for debugging
function addFrameAt(time, value, delay, array)
{
	array.push({ time:time, value:value, delay:delay });
}

function addFrameBefore(time, value, delay, array, step, resolution)
{
	time = getBeforeTime(time, step, resolution);

	addFrameAt(time, value, delay, array);
}

/*
	Insert extra keyframe to preserve frame-by-frame (keyframing) effect,
	because CSS is spec'ed to attempt "tweening" on all properties, even those that cannot be,
	undesirably resulting in changes occuring immediately instead of on the following keyframe
*/
function addPreservationFrameAt(time, value, delay, array)
{
	addFrameAt(time, value, delay, array);
}

function addPreservationFrameBefore(time, value, delay, array, step, resolution)
{
	addFrameBefore(time, value, delay, array, step, resolution);
}

function applyResolution(time, resolution, round)
{
	round = round ? Decimal.ROUND_HALF_UP : Decimal.ROUND_FLOOR;

	time = new Decimal(time).toFixed(resolution, Decimal.ROUND_FLOOR);
	time = parseFloat(time);

	return time;
}

function getAfterTime(time, step, resolution)
{
	time = new Decimal(time).plus(step);

	return applyResolution(time, resolution);
}

function getBeforeTime(time, step, resolution)
{
	time = new Decimal(time).minus(step);

	return applyResolution(time, resolution);
}

function getResolution($animationElement)
{
	var extendedDur = $animationElement.data("extendedDur");

	// If extendedDur is 1000, step would be .001
	// If extendedDur is 6000, step would be .00017 from .0001(6)
	return extendedDur.toString().length+1;
}

function getStep($animationElement)
{
	var extendedDur = $animationElement.data("extendedDur");
	var resolution  = getResolution($animationElement);

	var time = new Decimal(1).dividedBy(extendedDur);

	return applyResolution(time, resolution, true);
}

/*
	FROM OLD CODE -- here in case it's ever needed

	Was used for keyTimes, which is 0-1 based
*/
/*function oneLess()
{
	var minimumPerFrame = new Decimal(1).dividedBy(duration);	// percent

	// Ensure that there are never two identical keyframe times
	// by going even smaller than the calculated minimum
	var resolution  = duration.toString().length;
	var divisor     = new Decimal(10).toPower(resolution+1).dividedBy(10);
	var subtrahend  = new Decimal(1).dividedBy(divisor);
	var beforeFrame = new Decimal(minimumPerFrame).minus(subtrahend).toFixed(resolution);
	var time = nextKeyTime.minus(beforeFrame).toNumber();
}*/

function relateTime(time, extendedDur, resolution)
{
	// Relative to sibling animations
	time = new Decimal(time).dividedBy(extendedDur);

	// Clean up any repeating decimals
	time = applyResolution(time, resolution);

	return time;
}

module.exports =
{
	after:          addFrameAfter,
	at:             addFrameAt,
	before:         addFrameBefore,
	preserveAt:     addPreservationFrameAt,
	preserveBefore: addPreservationFrameBefore,

	resolute:   applyResolution,
	resolution: getResolution,
	relate:     relateTime,
	step:       getStep
};


/*
	See test/special/css-delay/looping-multiple.svg
	See test/special/css-delay/repeatCount-loop-with-syncbases.svg
*/
function delay(data, definedLoop)
{
	var preDelay = data.sequence[0][ definedLoop ? "begin" : "beginStatic" ];

	data.sequence.forEach( function(value, i)
	{
		data.sequence[i].begin = value.begin - preDelay;
	});

	return preDelay;
}

function sort(data)
{
	data.sequence.sort( function(a, b)
	{
		if (a.begin > b.begin) return 1;
		if (a.begin < b.begin) return -1;

		return 0;
	});
}

module.exports =
{
	delay: delay,
	sort: sort
};


var definitions = require("./definitions");
var error       = require("./error");

/*
	Check if supported animation elements exist
*/
function isCompatible(instance)
{
	var supported   = instance.$root.find(definitions.selectors.supported);
	var unsupported = instance.$root.find(definitions.selectors.unsupported);

	if (unsupported.length)
	{
		// See test/erroneous/unsupported-elements.svg
		error.incompatible("Unsupported animation elements detected", "https://github.com/stevenvachon/smil2css/wiki/Current-Status#animation-elements", instance);
	}
	else if (!supported.length)
	{
		// See test/erroneous/non-animating.svg
		error.unnecessary("No animation elements detected", null, instance);
	}

	return true;
}

function isSVG(instance)
{
	if (!instance.$root.length || instance.$root[0].name!="svg")
	{
		// See test/erroneous/non-svg.png
		// See test/erroneous/non-svg.txt
		error.invalid("Not an SVG document", null, instance);
	}

	return true;
}

module.exports =
{
	isCompatible: isCompatible,
	isSVG: isSVG
};


var cheerio = require("cheerio");
require("object.assign").shim();

var process = require("./process");

/*
	Usage: instance=new smil2css(); instance.convert();
*/
function smil2css(options)
{
	var defaults =
	{
		compress: true,
		force: false,
		targetBrowsers: null	// use autoprefixer defaults
	};

	this.options = Object.assign(defaults, options || {});
}

/*
	Usage: smil2css.convert();
*/
smil2css.convert = function(svg, options)
{
	var instance = new smil2css(options);

	return instance.convert(svg);
}

smil2css.prototype.convert = function(svg, options)
{
	options = Object.assign({}, this.options, options || {});

	// As of cheerio 0.15, normalizing will cause issues with any `content:"  asdf"` styles,
	// but the chances of having such are very unlikely
	var $ = cheerio.load(svg,
	{
		normalizeWhitespace: options.compress,
		xmlMode: true
	});

	try
	{
		process(
		{
			options: options,
			$: $,
			$root: $(":root"),
			$currentElement: null
		});
	}
	catch (error)
	{
		if (error.smil2css)
		{
			// Safely return smil2css error
			return error;
		}
		else
		{
			// Throw any other error for debugging
			throw error;
		}
	}

	return $.xml();
}

module.exports = smil2css;


'use strict'

var visit = require('unist-util-visit')
var nlcstToString = require('nlcst-to-string')
var polarities = require('./index.json')

module.exports = sentiment

var own = {}.hasOwnProperty

var neutral = 'neutral'
var positive = 'positive'
var negative = 'negative'

// Patch `polarity` and `valence` properties on nodes with a value and
// word-nodes.  Then, patch the same properties on their parents.
function sentiment(options) {
  return transformer

  function transformer(node) {
    var concatenate = concatenateFactory()

    visit(node, any(options))
    visit(node, concatenate)

    concatenate.done()
  }
}

// Factory to gather parents and patch them based on their childrens
// directionality.
function concatenateFactory() {
  var queue = []

  concatenate.done = done

  return concatenate

  // Gather a parent if not already gathered.
  function concatenate(node, index, parent) {
    if (parent && parent.type !== 'WordNode' && queue.indexOf(parent) === -1) {
      queue.push(parent)
    }
  }

  // Patch all words in `parent`.
  function one(node) {
    var children = node.children
    var length = children.length
    var polarity = 0
    var index = -1
    var child
    var hasNegation

    while (++index < length) {
      child = children[index]

      if (child.data && child.data.polarity) {
        polarity += (hasNegation ? -1 : 1) * child.data.polarity
      }

      // If the value is a word, remove any present negation.  Otherwise, add
      // negation if the node contains it.
      if (child.type === 'WordNode') {
        if (hasNegation) {
          hasNegation = false
        } else if (isNegation(child)) {
          hasNegation = true
        }
      }
    }

    patch(node, polarity)
  }

  // Patch all parents.
  function done() {
    var length = queue.length
    var index = -1

    queue.reverse()

    while (++index < length) {
      one(queue[index])
    }
  }
}

// Factory to patch based on the bound `config`.
function any(config) {
  return setter

  // Patch data-properties on `node`s with a value and words.
  function setter(node) {
    var value
    var polarity

    if ('value' in node || node.type === 'WordNode') {
      value = nlcstToString(node)

      if (config && own.call(config, value)) {
        polarity = config[value]
      } else if (own.call(polarities, value)) {
        polarity = polarities[value]
      }

      if (polarity) {
        patch(node, polarity)
      }
    }
  }
}

// Patch a `polarity` and valence property on `node`s.
function patch(node, polarity) {
  var data = node.data || {}

  data.polarity = polarity || 0
  data.valence = classify(polarity)

  node.data = data
}

// Detect if a value is used to negate something.
function isNegation(node) {
  var value

  value = nlcstToString(node).toLowerCase()

  if (
    value === 'not' ||
    value === 'neither' ||
    value === 'nor' ||
    /n['’]t/.test(value)
  ) {
    return true
  }

  return false
}

// Classify, from a given `polarity` between `-5` and `5`, if the polarity is
// negative, neutral, or positive.
function classify(polarity) {
  if (polarity > 0) {
    return positive
  }

  return polarity < 0 ? negative : neutral
}


/**
 * Create a circularly-linked list
 *
 * Adapted from original version by James Coglan.
 *
 * @fileOverview
 * @codingstandard ftlabs-jsv2
 * @copyright The Financial Times Limited [All Rights Reserved]
 */

this.CircularList = (function () {
	'use strict';

	/**
	 * @constructor
	 */
	function CircularList() {

		/**
		 * The length of the linked list
		 *
		 * @type number
		 */
		this.length = 0;

		/**
		 * The first item in the linked list
		 *
		 * @type Object
		 */
		this.first = null;

		/**
		 * The last item in the linked list
		 *
		 * @type Object
		 */
		this.last = null;
	}

	/**
	 * Explicit item object to allow items to belong to more than linked list at a time
	 *
	 * To hold a reference to a CircularList.Item within a completely different CircularList the CircularList.Item should be passed as the data to a new CircularList.Item to be used in the new CircularList.
	 * If you don't wrap the reference in a new Item, then if you append an already existing reference to a different CircularList the behaviour is undefined.
	 *
	 * @example
	 * myList.append(new CircularList.Item(someObject));
	 *
	 * @constructor
	 * @param {Object} data
	 */
	CircularList.Item = function (data) {
		this.prev = null;
		this.next = null;
		this.list = null;
		this.data = data;
	};

	/**
	 * Append an object to the linked list
	 *
	 * @param {Object} item The item to append
	 */
	CircularList.prototype.append = function (item) {
		if (this.first === null) {
			item.prev = item;
			item.next = item;
			this.first = item;
			this.last = item;
		} else {
			item.prev = this.last;
			item.next = this.first;
			this.first.prev = item;
			this.last.next = item;
			this.last = item;
		}

		item.list = this;
		this.length++;
	};

	/**
	 * Remove an item from the linked list
	 *
	 * @param {Object} item The item to remove
	 */
	CircularList.prototype.remove = function (item) {

		// Exit early if the item isn't in the list
		if (!this.length || this !== item.list) {
			return;
		}

		if (this.length > 1) {
			item.prev.next = item.next;
			item.next.prev = item.prev;

			if (item === this.first) {
				this.first = item.next;
			}

			if (item === this.last) {
				this.last = item.prev;
			}
		} else {
			this.first = null;
			this.last = null;
		}

		item.prev = null;
		item.next = null;
		this.length--;
	};

	/**
	 * Convert the linked list to an Array
	 *
	 * The first item in the list is the first item in the array.
	 *
	 * @return {Array}
	 */
	CircularList.prototype.toArray = function () {
		var i, item, array, length = this.length;

		array = new Array(length);
		item = this.first;

		for (i = 0; i < length; i++) {
			array[i] = item;
			item = item.next;
		}

		return array;
	};

	/**
	 * Insert an item after one already in the linked list
	 *
	 * @param {Object} item The reference item
	 * @param {Object} newItem The item to insert
	 */
	CircularList.prototype.insertAfter = function (item, newItem) {
		newItem.prev = item;
		newItem.next = item.next;
		item.next.prev = newItem;
		item.next = newItem;

		if (newItem.prev === this.last) {
			this.last = newItem;
		}

		newItem.list = this;
		this.length++;
	};

	return CircularList;

}());

/**
 * Create a DOM event delegator
 *
 * @fileOverview
 * @codingstandard ftlabs-jsv2
 * @copyright The Financial Times Limited [All Rights Reserved]
 */

this.Delegate = (function(that) {
	"use strict";

	var

		/**
		 * Event listener separator
		 *
		 * @private
		 * @type string
		 */
		SEPARATOR = ' ',

		/**
		 * Event object property used to signal that event should be ignored by handler
		 *
		 * @private
		 * @type string
		 */
		EVENT_IGNORE = 'ftLabsDelegateIgnore',

		/**
		 * Circular list constructor
		 *
		 * @private
		 * @type function()
		 */
		CircularList = that.CircularList,

		/**
		 * Whether tag names are case sensitive (as in XML or XHTML documents)
		 *
		 * @type boolean
		 */
		tagsCaseSensitive = document.createElement('i').tagName === 'i',

		/**
		 * Check whether an element matches a generic selector
		 *
		 * @private
		 * @type function()
		 * @param {string} selector A CSS selector
		 */
		matches = (function(p) {
			return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
		}(HTMLElement.prototype)),

		/**
		 * Check whether an element matches a tag selector
		 *
		 * Tags are NOT case-sensitive, except in XML (and XML-based languages such as XHTML).
		 *
		 * @private
		 * @param {string} tagName The tag name to test against
		 * @param {Element} element The element to test with
		 */
		matchesTag = function(tagName, element) {
			return tagName === element.tagName;
		},

		/**
		 * Check whether the ID of the element in 'this' matches the given ID
		 *
		 * IDs are case-sensitive.
		 *
		 * @private
		 * @param {string} id The ID to test against
		 * @param {Element} element The element to test with
		 */
		matchesId = function(id, element) {
			return id === element.id;
		};

	/**
	 * Fire a listener on a target
	 *
	 * @private
	 * @param {Event} event
	 * @param {Node} target
	 * @param {Object} listener
	 */
	function fire(event, target, listener) {
		var returned, oldData;

		if (listener.d !== null) {
			oldData = event.data;
			event.data = listener.d;
			returned = listener.h.call(target, event, target);
			event.data = oldData;
		} else {
			returned = listener.h.call(target, event, target);
		}

		return returned;
	}

	/**
	 * Internal function proxied by Delegate#on
	 *
	 * @private
	 * @param {Object} lisenerList
	 * @param {Node|DOMWindow} root
	 * @param {Event} event
	 */
	function handle(listenerList, root, event) {
		var listener, returned, specificList, target;

		if (event[EVENT_IGNORE] === true) {
			return;
		}

		target = event.target;
		if (target.nodeType === Node.TEXT_NODE) {
			target = target.parentNode;
		}
		specificList = listenerList[event.type];

		// If the fire function actually causes the specific list to be destroyed,
		// Need check that the specific list is still populated
		while (target && specificList.length > 0) {
			listener = specificList.first;
			do {

				// Check for match and fire the event if there's one
				// TODO:MCG:20120117: Need a way to check if event#stopImmediateProgagation was called. If so, break both loops.
				if (listener.m.call(target, listener.p, target)) {
					returned = fire(event, target, listener);
				}

				// Stop propagation to subsequent callbacks if the callback returned false
				if (returned === false) {
					event[EVENT_IGNORE] = true;
					return;
				}

				listener = listener.next;

			// If the fire function actually causes the specific list object to be destroyed,
			// need a way of getting out of here so check listener is set
			} while (listener !== specificList.first && listener);

			// TODO:MCG:20120117: Need a way to check if event#stopProgagation was called. If so, break looping through the DOM.
			// Stop if the delegation root has been reached
			if (target === root) {
				break;
			}

			target = target.parentElement;
		}
	}

	/**
	 * Internal function proxied by Delegate#on
	 *
	 * @private
	 * @param {Delegate} that
	 * @param {Object} listenerList
	 * @param {Node|DOMWindow} root
	 */
	function on(that, listenerList, root, eventType, selector, eventData, handler) {
		var matcher, matcherParam;

		if (!eventType) {
			throw new TypeError('Invalid event type: ' + eventType);
		}

		if (!selector) {
			throw new TypeError('Invalid selector: ' + selector);
		}

		// Support a separated list of event types
		if (eventType.indexOf(SEPARATOR) !== -1) {
			eventType.split(SEPARATOR).forEach(function(eventType) {
				on.call(that, that, listenerList, root, eventType, selector, eventData, handler);
			});

			return;
		}

		if (handler === undefined) {
			handler = eventData;
			eventData = null;

		// Normalise undefined eventData to null
		} else if (eventData === undefined) {
			eventData = null;
		}

		if (typeof handler !== 'function') {
			throw new TypeError("Handler must be a type of Function");
		}

		// Add master handler for type if not created yet
		if (!listenerList[eventType]) {
			root.addEventListener(eventType, that.handle, (eventType === 'error'));
			listenerList[eventType] = new CircularList();
		}

		// Compile a matcher for the given selector
		if (/^[a-z]+$/i.test(selector)) {
			if (!tagsCaseSensitive) {
				matcherParam = selector.toUpperCase();
			} else {
				matcherParam = selector;
			}

			matcher = matchesTag;
		} else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
			matcherParam = selector.slice(1);
			matcher = matchesId;
		} else {
			matcherParam = selector;
			matcher = matches;
		}

		// Add to the list of listeners
		listenerList[eventType].append({
			s: selector,
			d: eventData,
			h: handler,
			m: matcher,
			p: matcherParam
		});
	}

	/**
	 * Internal function proxied by Delegate#off
	 *
	 * @private
	 * @param {Delegate} that
	 * @param {Object} listenerList
	 * @param {Node|DOMWindow} root
	 */
	function off(that, listenerList, root, eventType, selector, handler) {
		var listener, nextListener, firstListener, specificList, singleEventType;

		if (!eventType) {
			for (singleEventType in listenerList) {
				if (listenerList.hasOwnProperty(singleEventType)) {
					off.call(that, that, listenerList, root, singleEventType, selector, handler);
				}
			}
			return;
		}
		specificList = listenerList[eventType];

		if (!specificList) {
			return;
		}

		// Support a separated list of event types
		if (eventType.indexOf(SEPARATOR) !== -1) {
			eventType.split(SEPARATOR).forEach(function(eventType) {
				off.call(that, that, listenerList, root, eventType, selector, handler);
			});
			return;
		}

		// Remove only parameter matches if specified
		listener = firstListener = specificList.first;
		do {
			if ((!selector || selector === listener.s) && (!handler || handler === listener.h)) {

				// listener.next will be undefined after listener is removed, so save a reference here
				nextListener = listener.next;
				specificList.remove(listener);
				listener = nextListener;
			} else {
				listener = listener.next;
			}
		} while (listener && listener !== firstListener);

		// All listeners removed
		if (!specificList.length) {
			delete listenerList[eventType];

			// Remove the main handler
			root.removeEventListener(eventType, that.handle, false);
		}
	}

	/**
	 * DOM event delegator
	 *
	 * The delegator will listen for events that bubble up to the root node.
	 *
	 * @constructor
	 * @param {Node|DOMWindow|string} root The root node, a window object or a selector string
	 */
	function Delegate(root) {
		var

			/**
			 * Keep a reference to the current instance
			 *
			 * @internal
			 * @type Delegate
			 */
			that = this,

			/**
			 * Maintain a list of listeners, indexed by event name
			 *
			 * @internal
			 * @type Object
			 */
			listenerList = {};

		if (typeof root === 'string') {
			root = document.querySelector(root);
		}

		if (!root || !root.addEventListener) {
			throw new TypeError('Root node not specified');
		}

		/**
		 * Attach a handler to one event for all elements that match the selector, now or in the future
		 *
		 * The handler function receives three arguments: the DOM event object, the node that matched the selector while the event was bubbling
		 * and a reference to itself. Within the handler, 'this' is equal to the second argument.
		 * The node that actually received the event can be accessed via 'event.target'.
		 *
		 * @param {string} eventType Listen for these events (in a space-separated list)
		 * @param {string} selector Only handle events on elements matching this selector
		 * @param {Object} [eventData] If this parameter is not specified, the third parameter must be the handler
		 * @param {function()} handler Handler function - event data passed here will be in event.data
		 * @returns {Delegate} This method is chainable
		 */
		this.on = function() {
			Array.prototype.unshift.call(arguments, that, listenerList, root);
			on.apply(that, arguments);
			return this;
		};

		/**
		 * Remove an event handler for elements that match the selector, forever
		 *
		 * @param {string} eventType Remove handlers for events matching this type, considering the other parameters
		 * @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
		 * @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
		 * @returns {Delegate} This method is chainable
		 */
		this.off = function() {
			Array.prototype.unshift.call(arguments, that, listenerList, root);
			off.apply(that, arguments);
			return this;
		};

		/**
		 * Handle an arbitrary event
		 *
		 * @private
		 * @param {Event} event
		 */
		this.handle = function(event) {
			handle.call(that, listenerList, root, event);
		};
	}

	return Delegate;

}(this));


/**
 * Usage
 */

// Create the fruitmachine View
var layout = new LayoutA();
var masthead = new Masthead({ model: { title: 'Article Viewer Alt' }});
var apple = new Apple();
var orange = new Orange();
var articles = database.getSync();

layout
	.add(masthead, 1)
	.add(apple, 2)
	.add(orange, 3);

// Set some data
// on module apple.
apple.model.set({ items: articles });

// Render the view,
// inject it into the
// DOM and call setup.
layout
	.render()
	.inject(document.getElementById('app'))
	.setup();

// Make an async call for the first article data
setArticle(articles[0].id);

// Setup a listener on the 'apple' view.
apple.on('itemclick', setArticle);

/**
 * Methods
 */

function setArticle(id) {
	database.getAsync(id, function(article) {
		orange.model.set(article);
		orange
			.render()
			.setup();
	});
}


/*jshint browser:true, node:true*/

'use strict';

/**
 * Module Dependencies
 */

var util = require('utils');
var events = require('./events');
var extend = require('extend');
var mixin = util.mixin;

/**
 * Exports
 */

module.exports = function(fm) {

  // Alias prototype for optimum
  // compression via uglifyjs
  var proto = Module.prototype;

  /**
   * Module constructor
   *
   * Options:
   *
   *  - `id {String}` a unique id to query by
   *  - `model {Object|Model}` the data with which to associate this module
   *  - `tag {String}` tagName to use for the root element
   *  - `classes {Array}` list of classes to add to the root element
   *  - `template {Function}` a template to use for rendering
   *  - `helpers {Array}`a list of helper function to use on this module
   *  - `children {Object|Array}` list of child modules
   *
   * @constructor
   * @param {Object} options
   * @api public
   */
  function Module(options) {

    // Shallow clone the options
    options = mixin({}, options);

    // Various config steps
    this._configure(options);
    this._add(options.children);

    // Fire before initialize event hook
    this.fireStatic('before initialize', options);

    // Run initialize hooks
    if (this.initialize) this.initialize(options);

    // Fire initialize event hook
    this.fireStatic('initialize', options);
  }

  /**
   * Configures the new Module
   * with the options passed
   * to the constructor.
   *
   * @param  {Object} options
   * @api private
   */
  proto._configure = function(options) {

    // Setup static properties
    this._id = options.id || util.uniqueId();
    this._fmid = options.fmid || util.uniqueId('fmid');
    this.tag = options.tag || this.tag || 'div';
    this.classes = options.classes || this.classes || [];
    this.helpers = options.helpers || this.helpers || [];
    this.template = this._setTemplate(options.template || this.template);
    this.slot = options.slot;

    // Create id and module
    // lookup objects
    this.children = [];
    this._ids = {};
    this._modules = {};
    this.slots = {};

    // Use the model passed in,
    // or create a model from
    // the data passed in.
    var model = options.model || options.data || {};
    this.model = util.isPlainObject(model)
      ? new this.Model(model)
      : model;

    // Attach helpers
    // TODO: Fix this for non-ES5 environments
    this.helpers.forEach(this.attachHelper, this);

    // We fire and 'inflation' event here
    // so that helpers can make some changes
    // to the view before instantiation.
    if (options.fmid) {
      fm.fire('inflation', this, options);
      this.fireStatic('inflation', options);
    }
  };

  /**
   * A private add method
   * that accepts a list of
   * children.
   *
   * @param {Object|Array} children
   * @api private
   */
  proto._add = function(children) {
    if (!children) return;

    var isArray = util.isArray(children);
    var child;

    for (var key in children) {
      child = children[key];
      if (!isArray) child.slot = key;
      this.add(child);
    }
  },

  /**
   * Instantiates the given
   * helper on the Module.
   *
   * @param  {Function} helper
   * @return {Module}
   * @api private
   */
  proto.attachHelper = function(helper) {
    if (helper) helper(this);
  },

  /**
   * Returns the template function
   * for the view.
   *
   * For template object like Hogan
   * templates with a `render` method
   * we need to bind the context
   * so they can be called without
   * a reciever.
   *
   * @return {Function}
   * @api private
   */
  proto._setTemplate = function(fn) {
    return fn && fn.render
      ? util.bind(fn.render, fn)
      : fn;
  };

  /**
   * Adds a child view(s) to another Module.
   *
   * Options:
   *
   *  - `at` The child index at which to insert
   *  - `inject` Injects the child's view element into the parent's
   *  - `slot` The slot at which to insert the child
   *
   * @param {Module|Object} children
   * @param {Object|String|Number} options|slot
   */
  proto.add = function(child, options) {
    if (!child) return this;

    // Options
    var at = options && options.at;
    var inject = options && options.inject;
    var slot = ('object' === typeof options)
      ? options.slot
      : options;

    // Remove this view first if it already has a parent
    if (child.parent) child.remove({ fromDOM: false });

    // Assign a slot (prefering defined option)
    slot = child.slot = slot || child.slot;

    // Remove any module that already occupies this slot
    var resident = this.slots[slot];
    if (resident) resident.remove({ fromDOM: false });

    // If it's not a Module, make it one.
    if (!(child instanceof Module)) child = fm(child);

    util.insert(child, this.children, at);
    this._addLookup(child);

    // We append the child to the parent view if there is a view
    // element and the users hasn't flagged `append` false.
    if (inject) this._injectEl(child.el, options);

    // Allow chaining
    return this;
  };

  /**
   * Removes a child view from
   * its current Module contexts
   * and also from the DOM unless
   * otherwise stated.
   *
   * Options:
   *
   *  - `fromDOM` Whether the element should be removed from the DOM (default `true`)
   *
   * Example:
   *
   *   // The following are equal
   *   // apple is removed from the
   *   // the view structure and DOM
   *   layout.remove(apple);
   *   apple.remove();
   *
   *   // Apple is removed from the
   *   // view structure, but not the DOM
   *   layout.remove(apple, { el: false });
   *   apple.remove({ el: false });
   *
   * @return {FruitMachine}
   * @api public
   */
  proto.remove = function(param1, param2) {

    // Don't do anything if the first arg is undefined
    if (arguments.length === 1 && !param1) return this;

    // Allow view.remove(child[, options])
    // and view.remove([options]);
    if (param1 instanceof Module) {
      param1.remove(param2 || {});
      return this;
    }

    // Options and aliases
    var options = param1 || {};
    var fromDOM = options.fromDOM !== false;
    var parent = this.parent;
    var el = this.el;
    var parentNode = el && el.parentNode;
    var index;

    // Unless stated otherwise,
    // remove the view element
    // from its parent node.
    if (fromDOM && parentNode) {
      parentNode.removeChild(el);
      this._unmount();
    }

    if (parent) {

      // Remove reference from views array
      index = parent.children.indexOf(this);
      parent.children.splice(index, 1);

      // Remove references from the lookup
      parent._removeLookup(this);
    }

    return this;
  };

  /**
   * Creates a lookup reference for
   * the child view passed.
   *
   * @param {Module} child
   * @api private
   */
  proto._addLookup = function(child) {
    var module = child.module();

    // Add a lookup for module
    (this._modules[module] = this._modules[module] || []).push(child);

    // Add a lookup for id
    this._ids[child.id()] = child;

    // Store a reference by slot
    if (child.slot) this.slots[child.slot] = child;

    child.parent = this;
  };

  /**
   * Removes the lookup for the
   * the child view passed.
   *
   * @param {Module} child
   * @api private
   */
  proto._removeLookup = function(child) {
    var module = child.module();

    // Remove the module lookup
    var index = this._modules[module].indexOf(child);
    this._modules[module].splice(index, 1);

    // Remove the id and slot lookup
    delete this._ids[child._id];
    delete this.slots[child.slot];
    delete child.parent;
  };

  /**
   * Injects an element into the
   * Module's root element.
   *
   * By default the element is appended.
   *
   * Options:
   *
   *  - `at` The index at which to insert.
   *
   * @param  {Element} el
   * @param  {Object} options
   * @api private
   */
  proto._injectEl = function(el, options) {
    var at = options && options.at;
    var parent = this.el;
    if (!el || !parent) return;

    if (typeof at !== 'undefined') {
      parent.insertBefore(el, parent.children[at]);
    } else {
      parent.appendChild(el);
    }
  };

  /**
   * Returns a decendent module
   * by id, or if called with no
   * arguments, returns this view's id.
   *
   * Example:
   *
   *   myModule.id();
   *   //=> 'my_view_id'
   *
   *   myModule.id('my_other_views_id');
   *   //=> Module
   *
   * @param  {String|undefined} id
   * @return {Module|String}
   * @api public
   */
  proto.id = function(id) {
    if (!arguments.length) return this._id;

    var child = this._ids[id];
    if (child) return child;

    return this.each(function(view) {
      return view.id(id);
    });
  };

  /**
   * Returns the first descendent
   * Module with the passed module type.
   * If called with no arguments the
   * Module's own module type is returned.
   *
   * Example:
   *
   *   // Assuming 'myModule' has 3 descendent
   *   // views with the module type 'apple'
   *
   *   myModule.modules('apple');
   *   //=> Module
   *
   * @param  {String} key
   * @return {Module}
   */
  proto.module = function(key) {
    if (!arguments.length) return this._module || this.name;

    var view = this._modules[key];
    if (view && view.length) return view[0];

    return this.each(function(view) {
      return view.module(key);
    });
  };

  /**
   * Returns a list of descendent
   * Modules that match the module
   * type given (Similar to
   * Element.querySelectorAll();).
   *
   * Example:
   *
   *   // Assuming 'myModule' has 3 descendent
   *   // views with the module type 'apple'
   *
   *   myModule.modules('apple');
   *   //=> [ Module, Module, Module ]
   *
   * @param  {String} key
   * @return {Array}
   * @api public
   */
  proto.modules = function(key) {
    var list = this._modules[key] || [];

    // Then loop each child and run the
    // same opperation, appending the result
    // onto the list.
    this.each(function(view) {
      list = list.concat(view.modules(key));
    });

    return list;
  };

  /**
   * Calls the passed function
   * for each of the view's
   * children.
   *
   * Example:
   *
   *   myModule.each(function(child) {
   *     // Do stuff with each child view...
   *   });
   *
   * @param  {Function} fn
   * @return {[type]}
   */
  proto.each = function(fn) {
    var l = this.children.length;
    var result;

    for (var i = 0; i < l; i++) {
      result = fn(this.children[i]);
      if (result) return result;
    }
  };

  /**
   * Templates the view, including
   * any descendent views returning
   * an html string. All data in the
   * views model is made accessible
   * to the template.
   *
   * Child views are printed into the
   * parent template by `id`. Alternatively
   * children can be iterated over a a list
   * and printed with `{{{child}}}}`.
   *
   * Example:
   *
   *
{{{<slot>}}}

   *
{{{<slot>}}}

   *
   *   // or
   *
   *   {{#children}}
   *     {{{child}}}
   *   {{/children}}
   *
   * @return {String}
   * @api public
   */
  proto.toHTML = function() {
    var html = this._innerHTML();

    // Wrap the html in a FruitMachine
    // generated root element and return.
    return this._wrapHTML(html);
  };

  /**
   * Get the view's innerHTML
   *
   * @return {String}
   */
  proto._innerHTML = function() {
    this.fireStatic('before tohtml');
    var data = {};
    var html;
    var tmp;

    // Create an array for view
    // children data needed in template.
    data[fm.config.templateIterator] = [];

    // Loop each child
    this.each(function(child) {
      if (!child.model) {
        var err = new Error("Child without a model");
        err.context = child;
        throw err;
      }
      tmp = {};
      html = child.toHTML();
      data[child.slot || child.id()] = html;
      tmp[fm.config.templateInstance] = html;
      data.children.push(mixin(tmp, child.model.toJSON()));
    });

    // Run the template render method
    // passing children data (for looping
    // or child views) mixed with the
    // view's model data.
    return this.template
      ? this.template(mixin(data, this.model.toJSON()))
      : '';
  };

  /**
   * Wraps the module html in
   * a root element.
   *
   * @param  {String} html
   * @return {String}
   * @api private
   */
  proto._wrapHTML = function(html) {
    return '<' + this.tag + ' class="' + this._classes().join(' ') + '" id="' + this._fmid + '">' + html + '</' + this.tag + '>';
  };

  /**
   * Gets a space separated list
   * of all a view's classes
   *
   * @return {String}
   * @api private
   */
  proto._classes = function() {
    return [this.module()].concat(this.classes);
  };

  /**
   * Renders the view and replaces
   * the `view.el` with a freshly
   * rendered node.
   *
   * Fires a `render` event on the view.
   *
   * @return {Module}
   */
  proto.render = function() {
    this.fireStatic('before render');

    // If possible recycle outer element but
    // build from scratch if there is no
    // existing element or if tag changed
    var el = this.el;
    var classes;
    if (el && el.tagName === this.tag.toUpperCase()) {
      el.innerHTML = this._innerHTML();
      this._unmountChildren();
      classes = el.className.split(/\s+/);
      this._classes().forEach(function(add) {
        if (!~classes.indexOf(add)) el.className = el.className + ' ' + add;
      });

    // Sets a new element as a view's
    // root element (purging descendent
    // element caches).
    } else {
      this._setEl(util.toNode(this.toHTML()));
    }

    // Fetch all child module elements
    this._fetchEls(this.el);

    // Handy hook
    this.fireStatic('render');

    return this;
  };

  /**
   * Sets up a view and all descendent
   * views.
   *
   * Setup will be aborted if no `view.el`
   * is found. If a view is already setup,
   * teardown is run first to prevent a
   * view being setup twice.
   *
   * Your custom `setup()` method is called
   *
   * Options:
   *
   *  - `shallow` Does not recurse when `true` (default `false`)
   *
   * @param  {Object} options
   * @return {Module}
   */
  proto.setup = function(options) {
    var shallow = options && options.shallow;

    // Attempt to fetch the view's
    // root element. Don't continue
    // if no route element is found.
    if (!this._getEl()) return this;

    // If this is already setup, call
    // `teardown` first so that we don't
    // duplicate event bindings and shizzle.
    if (this.isSetup) this.teardown({ shallow: true });

    // Fire the `setup` event hook
    this.fireStatic('before setup');
    if (this._setup) this._setup();
    this.fireStatic('setup');

    // Flag view as 'setup'
    this.isSetup = true;

    // Call 'setup' on all subviews
    // first (top down recursion)
    if (!shallow) {
      this.each(function(child) {
        child.setup();
      });
    }

    // For chaining
    return this;
  };

  /**
   * Tearsdown a view and all descendent
   * views that have been setup.
   *
   * Your custom `teardown` method is
   * called and a `teardown` event is fired.
   *
   * Options:
   *
   *  - `shallow` Does not recurse when `true` (default `false`)
   *
   * @param  {Object} options
   * @return {Module}
   */
  proto.teardown = function(options) {
    var shallow = options && options.shallow;

    // Call 'setup' on all subviews
    // first (bottom up recursion).
    if (!shallow) {
      this.each(function(child) {
        child.teardown();
      });
    }

    // Only teardown if this view
    // has been setup. Teardown
    // is supposed to undo all the
    // work setup does, and therefore
    // will likely run into undefined
    // variables if setup hasn't run.
    if (this.isSetup) {
      this.fireStatic('before teardown');
      if (this._teardown) this._teardown();
      this.fireStatic('teardown');
      this.isSetup = false;
    }

    // For chaining
    return this;
  };

  /**
   * Completely destroys a view. This means
   * a view is torn down, removed from it's
   * current layout context and removed
   * from the DOM.
   *
   * Your custom `destroy` method is
   * called and a `destroy` event is fired.
   *
   * NOTE: `.remove()` is only run on the view
   * that `.destroy()` is directly called on.
   *
   * Options:
   *
   *  - `fromDOM` Whether the view should be removed from DOM (default `true`)
   *
   * @api public
   */
  proto.destroy = function(options) {
    options = options || {};

    var remove = options.remove !== false;
    var l = this.children.length;

    // Destroy each child view.
    // We don't waste time removing
    // the child elements as they will
    // get removed when the parent
    // element is removed.
    //
    // We can't use the standard Module#each()
    // as the array length gets altered
    // with each iteration, hense the
    // reverse while loop.
    while (l--) {
      this.children[l].destroy({ remove: false });
    }

    // Don't continue if this view
    // has already been destroyed.
    if (this.destroyed) return this;

    // .remove() is only run on the view that
    // destroy() was called on.
    //
    // It is a waste of time to remove the
    // descendent views as well, as any
    // references to them will get wiped
    // within destroy and they will get
    // removed from the DOM with the main view.
    if (remove) this.remove(options);

    // Run teardown
    this.teardown({ shallow: true });

    // Fire an event hook before the
    // custom destroy logic is run
    this.fireStatic('before destroy');

    // If custom destroy logic has been
    // defined on the prototype then run it.
    if (this._destroy) this._destroy();

    // Trigger a `destroy` event
    // for custom Modules to bind to.
    this.fireStatic('destroy');

    // Unbind any old event listeners
    this.off();

    // Set a flag to say this view
    // has been destroyed. This is
    // useful to check for after a
    // slow ajax call that might come
    // back after a view has been detroyed.
    this.destroyed = true;

    // Clear references
    this.el = this.model = this.parent = this._modules = this._ids = this._id = null;
  };

  /**
   * Destroys all children.
   *
   * Is this needed?
   *
   * @return {Module}
   * @api public
   */
  proto.empty = function() {
    var l = this.children.length;
    while (l--) this.children[l].destroy();
    return this;
  };

  /**
   * Fetches all descendant elements
   * from the given root element.
   *
   * @param  {Element} root
   * @return {undefined}
   * @api private
   */
  proto._fetchEls = function(root) {
    if (!root) return;
    this.each(function(child) {
      child.mount(util.byId(child._fmid, root));
      child._fetchEls(child.el || root);
    });
  };

  /**
   * Associate the view with an element.
   * Provide events and lifecycle methods
   * to fire when the element is newly
   * associated.
   *
   * @param {Element} el
   * @return {Element}
   */
  proto.mount = function(el) {
    if(this.el !== el) {
      this.fireStatic('before mount');
      this.el = el;
      if(this._mount) this._mount();
      this.fireStatic('mount');
    }

    return this.el;
  };

  /**
   * Recursively fire unmount events on
   * children. To be called when a view's
   * children are implicitly removed from
   * the DOM (e.g. setting innerHTML)
   *
   * @api private
   */
  proto._unmountChildren = function() {
    this.each(function(child) {
      child._unmount();
    });
  };

  /*_setEl   * Recursively fire unmount events on
   * a view and its children. To be
   * called when a view'is implicitly
   * removed from the DOM (e.g. _setEl)
   *
   * @api private
   */
  proto._unmount = function() {
    this._unmountChildren();
    this.fireStatic('unmount');
  }

  /**
   * Returns the Module's root element.
   *
   * If a cache is present it is used,
   * else we search the DOM, else we
   * find the closest element and
   * perform a querySelector using
   * the view._fmid.
   *
   * @return {Element|undefined}
   * @api private
   */
  proto._getEl = function() {
    if (!util.hasDom()) return;
    return this.mount(this.el || document.getElementById(this._fmid) || undefined);
  };

  /**
   * Sets a root element on a view.
   * If the view already has a root
   * element, it is replaced.
   *
   * IMPORTANT: All descendent root
   * element caches are purged so that
   * the new correct elements are retrieved
   * next time Module#getElement is called.
   *
   * @param {Element} el
   * @return {Module}
   * @api private
   */
  proto._setEl = function(el) {
    var existing = this.el;
    var parentNode = existing && existing.parentNode;

    // If the existing element has a context, replace it
    if (parentNode) {
      parentNode.replaceChild(el, existing);
      this._unmount();
    }

    // Update cache
    this.mount(el);

    return this;
  };

  /**
   * Empties the destination element
   * and appends the view into it.
   *
   * @param  {Element} dest
   * @return {Module}
   * @api public
   */
  proto.inject = function(dest) {
    if (dest) {
      dest.innerHTML = '';
      this.insertBefore(dest, null);
      this.fireStatic('inject');
    }

    return this;
  };

  /**
   * Appends the view element into
   * the destination element.
   *
   * @param  {Element} dest
   * @return {Module}
   * @api public
   */
  proto.appendTo = function(dest) {
    return this.insertBefore(dest, null);
  };

  /**
   * Inserts the view element before the
   * given child of the destination element.
   *
   * @param  {Element} dest
   * @param  {Element} beforeEl
   * @return {Module}
   * @api public
   */
  proto.insertBefore = function(dest, beforeEl) {
    if (this.el && dest && dest.insertBefore) {
      dest.insertBefore(this.el, beforeEl);

      // This badly-named event is for legacy reasons; perhaps 'insert' would be better here?
      this.fireStatic('appendto');
    }

    return this;
  };

  /**
   * Returns a JSON represention of
   * a FruitMachine Module. This can
   * be generated serverside and
   * passed into new FruitMachine(json)
   * to inflate serverside rendered
   * views.
   *
   * @return {Object}
   * @api public
   */
  proto.toJSON = function() {
    var json = {};
    json.children = [];

    // Recurse
    this.each(function(child) {
      json.children.push(child.toJSON());
    });

    json.id = this.id();
    json.fmid = this._fmid;
    json.module = this.module();
    json.model = this.model.toJSON();
    json.slot = this.slot;

    // Fire a hook to allow third
    // parties to alter the json output
    this.fireStatic('tojson', json);

    return json;
  };

  // Events
  proto.on = events.on;
  proto.off = events.off;
  proto.fire = events.fire;
  proto.fireStatic = events.fireStatic;

  // Allow Modules to be extended
  Module.extend = extend(util.keys(proto));

  // Adding proto.Model after
  // Module.extend means this
  // key can be overwritten.
  proto.Model = fm.Model;

  return Module;
};


/*jslint browser:true, node:true*/

/**
 * FruitMachine
 *
 * Renders layouts/modules from a basic layout definition.
 * If views require custom interactions devs can extend
 * the basic functionality.
 *
 * @version 0.3.3
 * @copyright The Financial Times Limited [All Rights Reserved]
 * @author Wilson Page <wilson.page@ft.com>
 */

'use strict';

/**
 * Module Dependencies
 */

var mod = require('./module');
var define = require('./define');
var utils = require('utils');
var events = require('evt');

/**
 * Creates a fruitmachine
 *
 * Options:
 *
 *  - `Model` A model constructor to use (must have `.toJSON()`)
 *
 * @param {Object} options
 */
module.exports = function(options) {

  /**
   * Shortcut method for
   * creating lazy views.
   *
   * @param  {Object} options
   * @return {Module}
   */
  function fm(options) {
    var Module = fm.modules[options.module];
    if (Module) {
      return new Module(options);
    }
    throw new Error("Unable to find module '" + options.module + "'");
  }

  fm.create = module.exports;
  fm.Model = options.Model;
  fm.Events = events;
  fm.Module = mod(fm);
  fm.define = define(fm);
  fm.util = utils;
  fm.modules = {};
  fm.config = {
    templateIterator: 'children',
    templateInstance: 'child'
  };

  // Mixin events and return
  return events(fm);
};