(function(root, factory) {
  // Set up Backform appropriately for the environment. Start with AMD.
  if (typeof define === 'function' && define.amd) {
    define([
      'underscore', 'jquery', 'backbone', 'backform', 'backgrid', 'alertify',
      'pgadmin', 'pgadmin.browser.node', 'pgadmin.browser.node.ui'
      ],
     function(_, $, Backbone, Backform, Backgrid, Alertify, pgAdmin, pgNode) {
      // Export global even in AMD case in case this script is loaded with
      // others that may still expect a global Backform.
      return factory(root, _, $, Backbone, Backform, Alertify, pgAdmin, pgNode);
    });

  // Next for Node.js or CommonJS. jQuery may not be needed as a module.
  } else if (typeof exports !== 'undefined') {
    var _ = require('underscore') || root._,
      $ = root.jQuery || root.$ || root.Zepto || root.ender,
      Backbone = require('backbone') || root.Backbone,
      Backform = require('backform') || root.Backform;
      Alertify = require('alertify') || root.Alertify;
      pgAdmin = require('pgadmin') || root.pgAdmin,
      pgNode = require('pgadmin.browser.node') || root.pgAdmin.Browser.Node;

    factory(root, _, $, Backbone, Backform, Alertify, pgAdmin, pgNode);

  // Finally, as a browser global.
  } else {
    factory(
        root, root._, (root.jQuery || root.Zepto || root.ender || root.$),
        root.Backbone, root.Backform, root.pgAdmin.Browser.Node
        );
  }
} (this, function(root, _, $, Backbone, Backform, Alertify, pgAdmin, pgNode) {

  /*
   * cellFunction for variable control.
   * This function returns cell class depending on vartype.
   */
  var cellFunction = function(model) {
    var self = this,
      name = model.get("name"),
      availVariables = {};

    self.collection.each(function(col) {
      if (col.get("name") == "name") {
        availVariables = col.get('availVariables');
      }
    });


    var variable = name ? availVariables[name]: undefined,
      value = model.get("value");

    switch(variable && variable.vartype) {
      case "bool":
        /*
         * bool cell and variable cannot be stateless (i.e undefined).
         * It should be either true or false.
         */

        model.set("value", !!model.get("value"), {silent: true});

        return Backgrid.Extension.SwitchCell;
      break;
      case "enum":
        model.set({'value': value}, {silent:true});
        var options = [],
            enumVals = variable.enumvals;

        _.each(enumVals, function(enumVal) {
          options.push([enumVal, enumVal]);
        });

        return Backgrid.Extension.Select2Cell.extend({optionValues: options});
      break;
      case "integer":
        if (!_.isNaN(parseInt(value))) {
          model.set({'value': parseInt(value)}, {silent:true});
        } else {
          model.set({'value': undefined}, {silent:true});
        }
        return Backgrid.IntegerCell;
        break;
      case "real":
        if (!_.isNaN(parseFloat(value))) {
          model.set({'value': parseFloat(value)}, {silent:true});
        } else {
          model.set({'value': undefined}, {silent:true});
        }
        return Backgrid.NumberCell.extend({decimals: 0});
      break;
      case "string":
        return Backgrid.StringCell;
      break;
      default:
        model.set({'value': undefined}, {silent:true});
        return Backgrid.Cell;
      break;
    }
    model.set({'value': undefined}, {silent:true});
    return Backgrid.Cell;
  }

  /*
   * This row will define behaviour or value column cell depending upon
   * variable name.
   */
  var VariableRow = Backgrid.Row.extend({
    modelDuplicateColor: "lightYellow",

    modelUniqueColor: "#fff",

    initialize: function () {
      Backgrid.Row.prototype.initialize.apply(this, arguments);
      var self = this;
      self.model.on("change:name", function() {
        setTimeout(function() {
          self.columns.each(function(col) {
            if (col.get('name') == 'value') {

              var idx = self.columns.indexOf(col),
                cf = col.get("cellFunction"),
                cell = new (cf.apply(col, [self.model]))({
                  column: col,
                  model: self.model
                }),
                  oldCell = self.cells[idx];
                oldCell.remove();
                self.cells[idx] = cell;
                self.render();
            }

          });
        }, 10);
      });
      self.listenTo(self.model, 'pgadmin-session:model:duplicate', self.modelDuplicate);
      self.listenTo(self.model, 'pgadmin-session:model:unique', self.modelUnique);
    },
    modelDuplicate: function() {
      $(this.el).removeClass("new");
      this.el.style.backgroundColor = this.modelDuplicateColor;
    },
    modelUnique: function() {
      this.el.style.backgroundColor = this.modelUniqueColor;
    }

  })
  /**
   *  VariableModel used to represent configuration parameters (variables tab)
   *  for database objects.
   **/
  var VariableModel = pgNode.VariableModel = pgNode.Model.extend({
    keys: ['name'],
    defaults: {
      name: undefined,
      value: undefined,
      role: null,
      database: null,
    },
    schema: [
      {
        id: 'name', label:'Name', type:'text', editable: true, cellHeaderClasses: 'width_percent_30',
        editable: function(m) {
          return (m instanceof Backbone.Collection) ? true : m.isNew();
        },
        cell: Backgrid.Extension.NodeAjaxOptionsCell.extend({
          initialize: function() {
            Backgrid.Extension.NodeAjaxOptionsCell.prototype.initialize.apply(this, arguments);

            // Immediately process options as we need them before render.

            var opVals = _.clone(this.optionValues ||
                (_.isFunction(this.column.get('options')) ?
                    (this.column.get('options'))(this) :
                    this.column.get('options')));

            this.column.set('options', opVals);
          }
        }),
        url: 'vopts',
        select2: { allowClear: false },
        transform: function(vars, cell) {
          var self = this,
              res = [],
              availVariables = {};

          _.each(vars, function(v) {
            res.push({
              'value': v.name,
              'image': undefined,
              'label': v.name
            });
            availVariables[v.name] = v;
          });

          cell.column.set("availVariables", availVariables);
          return res;
        }
      },
      {
        id: 'value', label:'Value', type: 'text', editable: true,
        cellFunction: cellFunction, cellHeaderClasses: 'width_percent_40'
      },
      {id: 'database', label:'Database', type: 'text', editable: true,
      node: 'database', cell: Backgrid.Extension.NodeListByNameCell
      },
      {id: 'role', label:'Role', type: 'text', editable: true,
      node: 'role', cell: Backgrid.Extension.NodeListByNameCell}
    ],
    toJSON: function() {
      var d = Backbone.Model.prototype.toJSON.apply(this);

      // Remove not defined values from model values.
      // i.e.
      // role, database
      if (_.isUndefined(d.database) || _.isNull(d.database)) {
        delete d.database;
      }

      if (_.isUndefined(d.role) || _.isNull(d.role)) {
        delete d.role;
      }

      return d;
    },
    validate: function() {
      if (_.isUndefined(this.get('name')) ||
          _.isNull(this.get('name')) ||
          String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
        var msg = 'Please select a parameter name.';

        this.errorModel.set('name', msg);

        return msg;
      } else if (_.isUndefined(this.get('value')) ||
          _.isNull(this.get('value')) ||
          String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') {
        var msg = 'Please enter a value for the parameter.';

        this.errorModel.set('value', msg);

        return msg;
      } else {
        this.errorModel.unset('name');
        this.errorModel.unset('value');
      }

      return null;
    }
  });

  /**
   * Variable Tab Control to set/update configuration values for database object.
   *
   **/
  var VariableCollectionControl =  Backform.VariableCollectionControl =
    Backform.UniqueColCollectionControl.extend({

    hasDatabase: false,
    hasRole: false,

    initialize: function(opts) {
      var self = this,
          keys = ['name'];

      /*
       * Read from field schema whether user wants to use database and role
       * fields in Variable control.
       */
      self.hasDatabase = opts.field.get('hasDatabase');
      self.hasRole = opts.field.get('hasRole');

      // Update unique coll field based on above flag status.
      if (self.hasDatabase) {
        keys.push('database');
      } else if (self.hasRole) {
        keys.push('role');
      }
      // Overriding the uniqueCol in the field
      if (opts && opts.field) {
        if (opts.field instanceof Backform.Field) {
          opts.field.set({
            model: pgNode.VariableModel.extend({keys:keys})
          },
          {
            silent: true
          });
        } else {
          opts.field.extend({
            model: pgNode.VariableModel.extend({keys:keys})
          });
        }
      }

      Backform.UniqueColCollectionControl.prototype.initialize.apply(
          self, arguments
          );

      self.availVariables = {};

      var node = self.field.get('node').type,
          gridCols = ['name', 'value'];

      if (self.hasDatabase) {
        gridCols.push('database');
      }

      if (self.hasRole) {
        gridCols.push('role');
      }

      self.gridSchema = Backform.generateGridColumnsFromModel(
          self.field.get('node_info'), VariableModel.extend({keys:keys}), 'edit', gridCols, self.field.get('schema_node')
          );

      // Make sure - we do have the data for variables
      self.getVariables();
    },
    /*
     * Get the variable data for this node.
     */
    getVariables: function() {
      var self = this,
          url = this.field.get('url'),
          m = self.model;

      if (!this.field.get('version_compatible'))
        return;

      if (url && !m.isNew()) {
        var node = self.field.get('node'),
            node_data = self.field.get('node_data'),
            node_info = self.field.get('node_info'),
            full_url = node.generate_url.apply(
              node, [
                null, url, node_data, true, node_info
              ]),
            data,
            isTracking = self.collection.trackChanges;

        if (isTracking) {
          self.collection.stopSession();
        }
        m.trigger('pgadmin-view:fetching', m, self.field);

        $.ajax({
          async: false,
          url: full_url,
          success: function (res) {
            data = res.data;
          },
          error: function() {
            m.trigger('pgadmin-view:fetch:error', m, self.field);
          }
        });
        m.trigger('pgadmin-view:fetched', m, self.field);

        if (data && _.isArray(data)) {
          self.collection.reset(data, {silent: true});
        }
        /*
         * Make sure - new data will be taken care by the session management
         */
        if (isTracking) {
          self.collection.startNewSession();
        }
      }
    },

    showGridControl: function(data) {

      var self = this,
          titleTmpl = _.template([
            "<div class='subnode-header'>",
            "<label class='control-label'><%-label%></label>",
            "<button class='btn-sm btn-default add fa fa-plus' <%=canAdd ? '' : 'disabled=\"disabled\"'%>></button>",
            "</div>"].join("\n")),
          $gridBody =
            $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>").append(
              titleTmpl(data)
            );

      // Clean up existing grid if any (in case of re-render)
      if (self.grid) {
        self.grid.remove();
      }

      var gridSchema = _.clone(this.gridSchema);

      _.each(gridSchema.columns, function(col) {
        if (col.name == 'value') {
          col.availVariables = self.availVariables;
        }
      });

      // Insert Delete Cell into Grid
      if (data.disabled == false && data.canDelete) {
          gridSchema.columns.unshift({
            name: "pg-backform-delete", label: "",
            cell: Backgrid.Extension.DeleteCell,
            editable: false, cell_priority: -1
          });
      }

      // Change format of each of the data
      // Because - data coming from the server is in string format
      self.collection.each(function(model) {
        var name = model.get("name");

        if (name in self.availVariables) {
          switch(self.availVariables[name].vartype) {
            case 'real':
              var v = parseFloat(model.get('value'));
              model.set('value', (isNaN(v) ? undefined : v), {silent: true});

              break;
            case 'integer':
              var v = parseInt(model.get('value'));
              model.set('value', (isNaN(v) ? undefined : v), {silent: true});

              break;
            default:
              break;
          }
        }
      });

      // Initialize a new Grid instance
      var grid = self.grid = new Backgrid.Grid({
        columns: gridSchema.columns,
        collection: self.collection,
        row: VariableRow,
        className: "backgrid table-bordered"
      });
      self.$grid = grid.render().$el;

      $gridBody.append(self.$grid);

      // Add button callback
      if (!(data.disabled || data.canAdd == false)) {
        $gridBody.find('button.add').first().click(function(e) {
          e.preventDefault();
          var canAddRow = _.isFunction(data.canAddRow) ?
                            data.canAddRow.apply(self, [self.model]) : true;
          if (canAddRow) {

              var allowMultipleEmptyRows = !!self.field.get('allowMultipleEmptyRows');

              // If allowMultipleEmptyRows is not set or is false then don't allow second new empty row.
              // There should be only one empty row.
              if (!allowMultipleEmptyRows && self.collection) {
                var isEmpty = false;
                self.collection.each(function(model) {
                  var modelValues = [];
                  _.each(model.attributes, function(val, key) {
                    modelValues.push(val);
                  })
                  if(!_.some(modelValues, _.identity)) {
                    isEmpty = true;
                  }
                });
                if(isEmpty) {
                  return false;
                }
              }

              $(grid.body.$el.find($("tr.new"))).removeClass("new")
              var m = new (data.model) (null, {
                silent: true,
                handler: self.collection,
                top: self.model.top || self.model,
                collection: self.collection,
                node_info: self.model.node_info
              });
              self.collection.add(m);

              var idx = self.collection.indexOf(m),
                  newRow = grid.body.rows[idx].$el;

              newRow.addClass("new");
              $(newRow).pgMakeVisible('backform-tab');

              return false;
          }
        });
      }

      // Render node grid
      return $gridBody;
    },

    addVariable: function(ev) {
      ev.preventDefault();

      var self = this,
          m = new (self.field.get('model'))(
                self.headerData.toJSON(), {
                  silent: true, top: self.collection.top,
                  handler: self.collection
                }),
          coll = self.model.get(self.field.get('name'));

      coll.add(m);

      var idx = coll.indexOf(m);

      // idx may not be always > -1 because our UniqueColCollection may
      // remove 'm' if duplicate value found.
      if (idx > -1) {
        self.$grid.find('.new').removeClass('new');

        var newRow = self.grid.body.rows[idx].$el;

        newRow.addClass("new");
        $(newRow).pgMakeVisible('backform-tab');
      } else {
        delete m;
      }

      return false;
    }
  });

  return VariableModel;
}));
