/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.apps.quickbug.model',
  name: 'QProject',

  tableProperties: [
    'projectName',
    'baseURL'
  ],

  requires: [
    'Binding',
    'foam.dao.CachingDAO',
    'foam.dao.EasyDAO',
    'foam.dao.IDBDAO',
    'foam.dao.LazyCacheDAO',
    'foam.dao.ContextualizingDAO',
    'MDAO',
    'PersistentContext',
    'foam.util.Timer',
    'foam.apps.quickbug.dao.IssueRestDAO',
    'foam.apps.quickbug.dao.QIssueCommentNetworkDAO',
    'foam.apps.quickbug.dao.QIssueCommentUpdateDAO',
    'foam.apps.quickbug.dao.SyncManager',
    'foam.apps.quickbug.model.DefaultQuery',
    'foam.apps.quickbug.model.LabelArrayProperty',
    'foam.apps.quickbug.model.LabelStringEnumProperty',
    'foam.apps.quickbug.model.LabelStringProperty',
    'foam.apps.quickbug.model.QIssueComment',
    'foam.apps.quickbug.model.QIssueCommentUpdate',
    'foam.apps.quickbug.model.QIssueLabel',
    'foam.apps.quickbug.model.QIssueStatus',
    'foam.apps.quickbug.model.imported.Issue',
    'foam.apps.quickbug.model.imported.IssuePerson',
    'foam.apps.quickbug.model.LabelCompleter',
    'foam.apps.quickbug.model.PersonCompleter',
    'foam.apps.quickbug.model.StatusCompleter',
    'foam.core.dao.MigrationRule',
    'foam.lib.bookmarks.Bookmark',
    'foam.core.dao.WhenIdleDAO',
    'foam.metrics.Metric',
    'foam.ui.CSSImageBooleanView'
  ],

  exports: [
    'IssueCommentDAO as qIssueCommentDAO',
    'IssueCommentDAO as QIssueCommentDAO',
    'StatusDAO as issueStatusDAO',
    'StatusDAO as StatusDAO',
    'LabelDAO as issueLabelDAO',
    'LabelDAO as LabelDAO',
    'PersonDAO as issuePersonDAO',
    'PersonDAO as PersonDAO',
    'openPredicate',
    'QueryParser'
  ],

  imports: [
    'metricDAO'
  ],

  properties: [
    {
      name: 'qbug',
    },
    {
      name: 'project',
    },
    {
      name: 'baseURL',
      defaultValue: 'https://code.google.com/p/'
    },
    {
      name: 'projectName',
      scope: 'project',
      defaultValueFn: function() { return this.project.name; }
    },
    {
      name: 'summary',
      scope: 'project',
      defaultValueFn: function() { return this.project.summary; }
    },
    {
      name: 'user',
      scope: 'qbug',
      defaultValueFn: function() { return this.qbug.user; }
    },
    {
      name: 'openPredicate',
      lazyFactory: function() {
        var ss = this.project.issuesConfig.statuses;
        var os = [];
        for ( var i = 0 ; i < ss.length ; i++ ) {
          var s = ss[i];
          if ( ! s.meansOpen ) os.push(s.status);
        }
        return '-status=' + os.join(',');
      }
    },
    {
      name: 'LabelDAO',
      help: 'DAO of known labels.',
      factory: function() {
        var dao = this.MDAO.create({model: this.QIssueLabel});
        this.project.issuesConfig && this.project.issuesConfig.labels.select(dao);
        return dao;
      }
    },
    {
      name: 'StatusDAO',
      help: 'DAO of known statuses.',
      factory: function() {
        var dao = this.MDAO.create({model: this.QIssueStatus});
        this.project.issuesConfig && this.project.issuesConfig.statuses.select(dao);
        return dao;
      }
    },
    {
      name: 'PersonDAO',
      help: 'DAO of known people.',
      factory: function() {
        var dao = this.MDAO.create({ model: this.IssuePerson });
        this.project.members.select(dao);
        return dao;
      }
    },
    {
      name: 'IssueMDAO',
      lazyFactory: function() {
        var dao  = this.MDAO.create({model: this.QIssueModel}, this.Y);
        var auto = this.X.AutoIndex.create(dao, this.Y);

        auto.addIndex(this.QIssueModel.STATUS);
        dao.addRawIndex(auto);

        return dao;
      }
    },
    {
      name: 'IssueIDBDAO',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.QIssueModel,
          name: this.projectName + '_' + this.QIssueModel.plural,
          migrationRules: [
            this.MigrationRule.create({
              modelName: 'QIssue',
              version: 120,
              migration: function(ret, dao) {
                console.log("Migrating to ", this.version);
                dao.removeAll()(ret);
              }
            })
          ]
        });
      },
      transient: true
    },
    {
      name: 'IssueCachingDAO',
      lazyFactory: function() {
        console.log('Creating IssueCachingDAO.');
        return this.CachingDAO.create({cache: this.IssueMDAO, src: this.IssueIDBDAO}, this.Y);
      },
      transient: true
    },
    {
      name: 'IssueCommentNetworkDAO',
      lazyFactory: function() {
        return this.LazyCacheDAO.create({
          model: this.QIssueComment,
          cacheOnSelect: true,
          cache: this.IDBDAO.create({
            model: this.QIssueComment,
            name: this.projectName + '_' + this.QIssueComment.plural,
            useSimpleSerialization: false }).addIndex(this.QIssueComment.ISSUE_ID),
          delegate: this.QIssueCommentNetworkDAO.create({
            model: this.QIssueComment,
            url: 'https://www.googleapis.com/projecthosting/v2/projects/' + this.projectName + '/issues',
          })
        });
      }
    },
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'IssueCommentDAO',
      factory: function() {
        return this.WhenIdleDAO.create({
          delegate: this.QIssueCommentUpdateDAO.create({
            delegate: this.IssueCommentNetworkDAO
          })
        });
      }
    },
    {
      name: 'IssueNetworkDAO',
      lazyFactory: function() {
        return this.IssueRestDAO.create({
          url: 'https://www.googleapis.com/projecthosting/v2/projects/' + this.projectName + '/issues',
          IssueCommentDAO: this.IssueCommentNetworkDAO,
          model: this.QIssueModel,
          batchSize: 500
        });
      },
      postSet: function(_, v) {
        this.IssueCommentDAO.delegate.IssueNetworkDAO = v;
      },
      transient: true
    },
    {
      name: 'IssueDAO',
      lazyFactory: function() {
        return this.ContextualizingDAO.create({
          delegate: this.IssueCachingDAO
        });
      },
      transient: true
    },
    {
      name: 'bookmarkDAO',
      lazyFactory: function() {
        var newDAO = this.EasyDAO.create({
          model:   this.Bookmark,
          name:    this.projectName + '_' + this.Bookmark.plural,
          cache:   true,
          guid:    true,
          daoType: 'SYNC'
        }).orderBy(this.Bookmark.ID);

        // Migrate bookmarks from old DAO to new SyncStorage.
        // TODO: Remove this code after a couple versions when all data has been translated.
        var oldDAO = this.EasyDAO.create({
          model:   this.Bookmark,
          name:    this.projectName + '_' + this.Bookmark.plural,
          cache:   true
        });

        oldDAO.select({put: function(bookmark) {
          bookmark.id = '';
          newDAO.put(bookmark);
        }})(function() {
          oldDAO.removeAll();
        });

        return newDAO;
      },
      transient: true
    },
    {
      mode_: 'IntProperty',
      name: 'issueCount'
    },
    {
      name: 'url',
      defaultValueFn: function() { return this.baseURL + this.projectName; }
    },
    {
      name: 'timer',
      factory: function() { return this.Timer.create(); },
      transient: true
    },
    {
      name: 'persistentContext',
      lazyFactory: function() {
        return this.PersistentContext.create({
          dao: this.IDBDAO.create({
            model: this.Binding,
            name: 'QProject-' + this.projectName + '-Bindings'
          }),
          predicate: NOT_TRANSIENT,
          context: this
        });
      }
    },
    {
      name: 'syncManager'
    },
    {
      name: 'syncManagerFuture',
      factory: function() { return afuture(); }
    },
    {
      name: 'defaultSortChoices',
      lazyFactory: function() {
        return [
          [ DESC(this.QIssueModel.MODIFIED), 'Last modified' ],
          [ this.QIssueModel.PRI,            'Priority' ],
          [ DESC(this.QIssueModel.ID),       'Issue ID' ]
        ];
      }
    },
    {
      name: 'defaultFilterChoices',
      factory: function() {
        var open = 'status=Accepted,Assigned,Available,New,Started,Unconfirmed,Untriaged';

        return [
//          ['',                     'ALL ISSUES'],
          [open,                   'OPEN ISSUES'],
          [open + ' is:starred',   'STARRED'],
          [open + ' owner=me',     'OWNED BY ME']
//          [open + ' reporter=me',  'Open and reported by me'],
//          [open + ' commentby:me', 'Open and comment by me'],
//          ['status=New',           'New issues'],
//          ['status=Fixed,Done',    'Issues to verify']
        ];
      }
    },
    {
      name: 'QIssueModel',
      factory: function() {
        var labelToProperty = {
          App:          'app',
          Type:         'type',
          Milestone:    'm',
          Mstone:       'm',
          M:            'm',
          Cr:           'cr',
          Iteration:    'iteration',
          Week:         'week',
          ReleaseBlock: 'releaseBlock',
          OS:           'OS',
          MovedFrom:    'movedFrom',
          Pri:          'pri',
          Priority:     'priority'
        };

        var propertyLabels_ = {};

        function isPropertyLabel(l) {
          if ( l in propertyLabels_ ) return propertyLabels_[l];

          var keyValue = l.match(/([^\-]*)\-(.*)/) || l.match(/(\D*)(.*)/);
          if ( keyValue ) {
            var key   = labelToProperty[keyValue[1]];
            var value = keyValue[2];

            if ( key ) {
              var kv = [key, value.intern()];
              propertyLabels_[l] = kv;
              return kv;
            }
          }

          return propertyLabels_[l] = false;
        }

        var model = Model.create({
          extendsModel: 'foam.apps.quickbug.model.imported.Issue',

          name: 'QIssue',
          requires: [
            'foam.ui.CSSImageBooleanView',
          ],

          tableProperties: [
            'starred',
            'id',
            //      'app',
            'priority',
            'm',
            'iteration',
            'releaseBlock',
            'cr',
            'status',
            'owner',
            'summary',
            'OS',
            'modified'
          ],

          ids: ['id'],

          properties: [
            {
              model_: 'IntProperty',
              name: 'id',
              shortName: 'i',
              label: 'ID',
              required: true,
              tableWidth: '48px',
              tableFormatter: function(value, row, table) {
                var id = table.nextID();

                table.on('mouseover', function(e) {
                  table.browser.preview(e, value);
                }, id);

                return '<div id="' + id + '">' + value + '</div>';
              }
            },
            {
              name: 'labels',
              shortName: 'l',
              aliases: ['label'],
              type: 'String',
              view: 'foam.apps.quickbug.ui.QIssueLabelsView',
              autocompleter: 'foam.apps.quickbug.model.LabelCompleter',
              tableFormatter: function(a, row) {
                var s = '';
                for ( var i = 0 ; i < a.length ; i++ ) {
                  if ( ! isPropertyLabel(a[i]) ) {
                    s += ' <span class="label">' + a[i] + '</span>';
                  }
                }
                return s;
              },
              preSet: function(_, a) {
                for ( var i = 0; i < a.length; i++ ) a[i] = a[i].intern();
                return a.sort();
              },
              postSet: function(_, a) {
                // Reset all label properties to initial values.
                var newValues = {};

                // Create initial values
                var properties = this.model_.getRuntimeProperties();
                for ( var i = 0 ; i < properties.length ; i++ ) {
                  var p = properties[i];
                  
                  if ( p.name_ === 'LabelArrayProperty' ) {
                    newValues[p.name] = [];
                  } else if ( p.name_ === 'LabelStringProperty' ) {
                    newValues[p.name] = '';
                  }
                }

                // Add values from new list
                for ( var i = 0 ; i < a.length ; i++ ) {
                  var kv = isPropertyLabel(a[i]);
                  if ( kv ) {
                    // Cleanup 'movedFrom' labels
                    if ( kv[0] === 'movedFrom' ) {
                      kv[1] = typeof kv[1] === 'string' ? parseInt(kv[1].charAt(0) === 'M' ? kv[1].substring(1) : kv[1]) : kv[1];
                    }

                    if ( Array.isArray(newValues[kv[0]]) ) {
                      newValues[kv[0]] = newValues[kv[0]].binaryInsert(kv[1]);
                    } else {
                      newValues[kv[0]] = kv[1];
                    }
                  }
                }

                // Set new values back to object, but only if they've changed
                for ( var key in newValues ) {
                  var value = newValues[key];
                  if ( this[key].compareTo(value) ) this[key] = value;
                }
              }
            },
            {
              name: 'author',
              preSet: function(_, a) { return a.intern(); },
              aliases: ['reporter']
            },
            {
              model_: 'foam.apps.quickbug.model.LabelStringEnumProperty',
              name: 'priority',
              tableLabel: 'Priority',
              tableWidth: '60px',
              compareProperty: function(p1, p2) {
                var priorities = ['Low', 'Medium', 'High', 'Critical'];
                var i1 = priorities.indexOf(p1);
                var i2 = priorities.indexOf(p2);
                if ( i1 < 0 && i2 < 0 ) {
                  // Neither is a proper priority - return normal string order.
                  return p1 === p2 ? 0 : p1 < p2 ? -1 : 1;
                } else if ( i1 < 0 ) {
                  return 1; // Nonstandard come after standard priorities
                } else if ( i2 < 0 ) {
                  return -1;  // Likewise.
                } else {
                  var r = i2 - i1;
                  return r === 0 ? 0 : r < 0 ? -1 : 1;
                }
              },
              choices: ['Low', 'Medium', 'High', 'Critical'],
            },
            {
              model_: 'foam.apps.quickbug.model.LabelStringEnumProperty',
              name: 'pri',
              tableLabel: 'Pri',
              tableWidth: '60px',
              compareProperty: function(p1, p2) {
                if ( p1.length === 0 && p2.length != 0 ) return 1;
                else if ( p2.length === 0 && p1.length != 0 ) return -1;
                return p1.compareTo(p2);
              },
              choices: [
                ['0', 'Priority 0 -- Critical'],
                ['1', 'Priority 1 -- High'],
                ['2', 'Priority 2 -- Medium'],
                ['3', 'Priority 3 -- Low']
              ]
            },
            {
              model_: 'StringProperty',
              name: 'metaPriority',
              compareProperty: (this.projectName == "chromium") ?
                (function(p1, p2) {
                  if ( p1.length === 0 && p2.length != 0 ) return 1;
                  else if ( p2.length === 0 && p1.length != 0 ) return -1;
                  return p1.compareTo(p2);
                }) : (function(p1, p2) {
                  var priorities = ['Low', 'Medium', 'High', 'Critical'];
                  var i1 = priorities.indexOf(p1);
                  var i2 = priorities.indexOf(p2);
                  if ( i1 < 0 && i2 < 0 ) {
                    // Neither is a proper priority - return normal string order.
                    return p1 === p2 ? 0 : p1 < p2 ? -1 : 1;
                  } else if ( i1 < 0 ) {
                    return 1; // Nonstandard come after standard priorities
                  } else if ( i2 < 0 ) {
                    return -1;  // Likewise.
                  } else {
                    var r = i2 - i1;
                    return r === 0 ? 0 : r < 0 ? -1 : 1;
                  }
                }),
              getter: (this.projectName == "chromium") ?
                (function() { return this.pri; }) :
                (function() { return this.priority; }),
              setter: (this.projectName == "chromium") ?
                (function(value) { this.pri = value; }) :
                (function(value) { this.priority = value; })
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'app',
              tableWidth: '70px'
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'type',
              tableWidth: '70px'
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'm',
              label: 'Milestone',
              aliases: ['mstone', 'milestone'],
              tableLabel: 'M',
              tableWidth: '70px'
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'iteration',
              shortName: 'it',
              aliases: ['iter'],
              tableWidth: '69px'
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'week',
              tableWidth: '69px'
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'releaseBlock',
              shortName: 'rb',
              aliases: ['rBlock', 'release'],
              type: 'String',
              tableWidth: '103px',
              defaultValue: ''
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'cr',
              shortName: 'c',
              aliases: ['cat', 'cr'],
              label: 'Cr',
              tableWidth: '87px',
            },
            {
              model_: 'StringProperty',
              name: 'status',
              shortName: 's',
              aliases: ['stat'],
              type: 'String',
              preSet: function(_, s) { return s.capitalize(); },
              tableWidth: '58px',
              autocompleter: 'foam.apps.quickbug.model.StatusCompleter',
              defaultValue: ''
            },
            {
              model_: 'StringArrayProperty',
              name: 'cc',
              autocompleter: 'foam.apps.quickbug.model.PersonCompleter',
              displayWidth: 70
            },
            {
              name: 'owner',
              shortName: 'o',
              tableWidth: '181px',
              type: 'String',
              autocompleter: 'foam.apps.quickbug.model.PersonCompleter',
              preSet: function(_, a) { return a.intern(); }
            },
            {
              name: 'summary',
              shortName: 'su',
              label: 'Summary',
              type: 'String',
              tableWidth: '350px',
              displayWidth: 70,
              //          tableWidth: '100%'
              tableFormatter: function(value, row, tableView) {
                return tableView.strToHTML(value);
              }
            },
            {
              name: 'description',
              displayHeight: 20,
              displayWidth: 70
            },
            {
              name: 'summaryPlusLabels',
              label: 'Summary + Labels',
              tableLabel: 'Summary + Labels',
              type: 'String',
              tableWidth: '100%',
              tableFormatter: function(value, row, tableView) {
                return tableView.strToHTML(row.summary) +
                  value.model_.LABELS.tableFormatter(row.labels, row);
              }
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'OS',
              tableWidth: '61px',
              type: 'String'
            },
            {
              model_: 'BooleanProperty',
              name: 'blocked',
              tableWidth: '20px',
              getter: function() { return !! this.blockedOn.length; }
            },
            {
              model_: 'DateTimeProperty',
              name: 'modified',
              shortName: 'mod',
              mode: 'read-write',
              required: true,
              tableWidth: '100px',
              factory: function() { return new Date(); }
            },
            {
              name: 'updated',
              hidden: true,
              setter: function(v) { this.modified = v; }
            },
            {
              model_: 'BooleanProperty',
              name: 'starred',
              tableLabel: '',
              tableWidth: '18px',
              tableFormatter: function(val, obj, tableView) {
                var view = obj.CSSImageBooleanView.create({
                  className: 'star-image',
                  data: obj.starred
                });

                view.data$.addListener(function() {
                  var tmp = obj.clone();
                  tmp.starred = view.data;
                  tableView.browser.IssueDAO.put(tmp);
                });

                tableView.addChild(view);
                return view.toHTML();
              },
              view: { factory_: 'foam.ui.CSSImageBooleanView', className: 'star-image' },
              help: 'Whether the authenticated user has starred this issue.'
            },
            {
              model_: 'IntProperty',
              name: 'stars',
              tableWidth: '20px',
              help: 'Number of stars this issue has.',
              compareProperty: function(o2, o1) {
                return o1 === o2 ? 0 : o1 > o2 ? 1 : -1;
              }
            },
            {
              model_: 'foam.apps.quickbug.model.LabelArrayProperty',
              name: 'movedFrom',
              tableWidth: '100px'
            },
            {
              name: 'movedTo',
              preSet: function(_, v) {
                // TODO: looks like we're getting bad values, investigate and fix
                /*
                  if ( v.length ) console.log('movedTo: ', v);
                  return v;
                */
                return [];
              }
            },
            {
              model_: 'StringProperty',
              name: 'content',
              displayHeight: 4
            }
          ],

          methods: {
            replaceLabels: function(label, values) {
              var prefix = label + '-';
              var labels = this.labels.filter(function(l) { return ! l.startsWith(prefix); });
              if ( Array.isArray(values) ) {
                for ( var i = 0 ; i < values.length ; i++ ) {
                  labels.binaryInsert(label + '-' + values[i]);
                }
              } else {
                labels.binaryInsert(label + '-' + values);
              }

              if ( this.labels.compareTo(labels) ) this.labels = labels;
            },
            isOpen: function() {
              return !! ({
                'New':       true,
                'Accepted':  true,
                'Started':   true,
                'Untriaged': true
              }[this.status]);
            },
            writeActions: function(other, out) {
              var diff = this.diff(other);
              delete diff.starred;

              if ( Object.keys(diff).length == 0 ) return;

              delete diff.stars;

              function convertArray(key) {
                if ( ! diff[key] ) {
                  diff[key] = [];
                  return;
                }

                var delta = diff[key].added;
                for ( var i = 0; i < diff[key].removed.length; i++ )
                  delta.push("-" + diff[key].removed[i]);
                diff[key] = delta;
              }

              convertArray('blockedOn');
              convertArray('blocking');
              convertArray('cc');
              convertArray('labels');
              convertArray('m');
              convertArray('iteration');
              convertArray('week');

              var empty = true;
              for ( var key in diff ) {
                if ( ! Array.isArray(diff[key]) || diff[key].length > 0 ){
                  empty = false;
                  break;
                }
              }

              if ( empty ) return;

              var comment = this.X.lookup('foam.apps.quickbug.model.QIssueComment').create({
                issueId: this.id,
                content: other.content,
                updates: this.X.lookup('foam.apps.quickbug.model.QIssueCommentUpdate').create(diff)
              }, this.Y);

              out(comment);
            },
            newComment: function() {
              return this.X.lookup('foam.apps.quickbug.model.QIssueComment').create({
                issueId: this.id,
                updates: this.X.lookup('foam.apps.quickbug.model.QIssueCommentUpdate').create({
                  labels: this.labels.clone(),
                  owner: this.owner,
                  blockedOn: this.blockedOn.clone(),
                  blocking: this.blocking.clone(),
                  cc: this.cc.clone(),
                  mergedInto: this.mergedInto,
                  status: this.status,
                  summary: this.summary
                })
              }, this.Y)
            }
          },
          relationships: [
            {
              name: 'comments',
              label: 'Comments on this issue.',
              relatedModel: 'foam.apps.quickbug.model.QIssueComment',
              relatedProperty: 'issueId'
            }
          ]
        });

        model.getPrototype();

        model.getRuntimeProperties().forEach(function(p) {
          if ( ! p["tableFormatter"] ) {
            p["tableFormatter"] = function(v) {
              return v || '----';
            };
          }
        });

        model.LABELS.toMQL = function() { return 'label'; };
        model.AUTHOR.toMQL = function() { return 'reporter'; };
        return model;
      },
      postSet: function(_, m) {
        this.X.registerModel(m);
      }
    },
    {
      name: 'QueryParser',
      factory: function() {
        var parser = {
          X: this.Y,
          __proto__: QueryParserFactory(this.QIssueModel),

          isOpen: literal_ic('is:open'),

          stars: seq(literal_ic('stars:'), sym('number')),

          labelMatch: seq(sym('fieldname'), alt(':', '='), sym('valueList')),

          summary: str(plus(notChar(' ')))

        }.addActions({
          isOpen: function(v) {
            return this.X.QueryParser.parseString(this.X.openPredicate);
          },
          stars: function(v) {
            return GTE({
              f: function(i) { return i.stars; },
              partialEval: function() {return this;},
              toSQL: function() { return 'stars > ' + v[1]; },
              toMQL: function() { return 'stars > ' + v[1]; }
            }, v[1]);
          },

          labelMatch: function(v) {
            var a = [];
            for ( var i = 2 ; i < v.length ; i++ ) {
              a.push(v[0] + '-' + v[i]);
            }

            return IN(this.X.QIssue.LABELS, a).partialEval();
            /*
              var or = OR();
              var values = v[2];
              for ( var i = 0 ; i < values.length ; i++ ) {
              or.args.push(CONTAINS_IC(QIssue.LABELS, v[0] + '-' + values[i]));
              }
              return or;
            */
          },

          summary: function(v) {
            return this.X.lookup('foam.apps.quickbug.model.DefaultQuery').create({arg1: v});
          }
        });
        parser.expr = alt(
          sym('isOpen'),
          parser.export('expr'),
          sym('stars'),
          sym('labelMatch'),
          sym('summary')
        );

        return parser;
      }
    }
  ],

  listeners: [
    {
      model_: 'Method',
      name: 'onDAOUpdate',
      isMerged: 16,
      code: function() {
        this.IssueMDAO.select(COUNT())(function (c) { this.issueCount = c.count; }.bind(this));

        var self = this;
        this.IssueMDAO.select(GROUP_BY(this.QIssueModel.CC, COUNT()))(function(g) {
          Object.keys(g.groups).forEach(function(key) {
            self.PersonDAO.find(key, { error: function() {
              self.PersonDAO.put(self.IssuePerson.create({ name: key }));
            }});
          });
        });
      }
    }
  ],

  methods: {
    init: function(args) {
      this.SUPER(args);

      if ( this.projectName !== 'chromium' ) {
        this.defaultSortChoices = [
          [ DESC(this.QIssueModel.MODIFIED), 'Last modified' ],
          [ this.QIssueModel.PRIORITY,       'Priority' ],
          [ DESC(this.QIssueModel.ID),       'Issue ID' ]
        ];
      }

      if ( ! this.X.DontSyncProjectData ) {
        this.IssueDAO.listen(this.onDAOUpdate);

        this.persistentContext.bindObject('syncManager', this.SyncManager.xbind({
          syncInterval: 60*5,
          batchSize: 500,
        }), {
          queryParser: this.QueryParser,
          srcDAO: this.IssueNetworkDAO,
          dstDAO: this.IssueCachingDAO,
          modifiedProperty: this.QIssueModel.MODIFIED
        }, 2)(function(manager) {
          this.syncManager = manager;
          this.syncManagerFuture.set(manager);
          manager.start();
        }.bind(this));
      }
    },

    /** Open a Browser in a Window for a Chome Packaged App. **/
    launchBrowser: function(opt_url) {
      var self = this;

      chrome.app.window.create('empty.html', {width: 1000, height: 800}, function(w) {
        w.contentWindow.onload = function() {
          var window = self.window = w.contentWindow;

          self.X.arequire('foam.apps.quickbug.ui.ChromeAppBrowser')(
            function (model) {
              $addWindow(window);
              var Y = self.Y.subWindow(window, 'Browser Window');
              var b = model.create({project: self}, Y);
              Y.touchManager = Y.foam.input.touch.TouchManager.create({}, Y);
              window.document.body.outerHTML = b.toHTML()
              b.initHTML();
              if ( opt_url ) b.maybeSetLegacyUrl(opt_url);
              w.focus();
              self.metricDAO.put(self.Metric.create({
                name: 'browserLaunched'
              }));
            });
        };

        w.onClosed.addListener(function() {
          $removeWindow(window);
        });
      });
    },

    /** Create a Browser for use in a hosted app. **/
    createBrowser: function(window) {
      var b = this.X.Browser.create({project: this, window: window}, this.Y);
      window.browser = b;
      return b;
    },

    launchSync: function() {
      var self = this;

      this.syncManagerFuture.get((function(syncManager) {
        chrome.app.window.create('empty.html', {width: 600, height: 600}, function(w) {
          var window = w.contentWindow;
          w.contentWindow.onload = function() {
            $addWindow(window);
            var Y = self.Y.subWindow(window, 'Sync Config')
            var b = Y.DetailView.create({
              model: Y.SyncManager,
              title: '<img style="vertical-align:bottom;" src="images/refresh.png"> Sync Config: ' + self.projectName,
              data: syncManager,
              showActions: true });
            window.document.body.innerHTML = '<div>' + b.toHTML() + '</div>';
            b.initHTML();
            var extrax = window.outerWidth - window.innerWidth + 16;
            var extray = window.outerHeight - window.innerHeight + 16;

            window.resizeTo(
              window.document.body.firstChild.firstChild.firstChild.offsetWidth + extrax,
              window.document.body.firstChild.offsetHeight + extray);
            window.resizeTo(
              window.document.body.firstChild.firstChild.firstChild.offsetWidth + extrax,
              window.document.body.firstChild.offsetHeight + extray);
            w.focus();
          };
          w.onClosed.addListener(function() {
            $removeWindow(window);
          });
        });
      }).bind(this));
    },

    launchConfigProjects: function() {
      var self = this;

      chrome.app.window.create('empty.html', {width: 430, height: 440}, function(w) {
        var window = w.contentWindow;
        w.contentWindow.onload = function() {
          $addWindow(window);

          var view = ConfigureProjectsView.create({ model: QUser });
          view.data = self.user;

          window.document.body.innerHTML = view.toHTML();
          view.initHTML();

          w.focus();
        };
        w.onClosed.addListener(function() {
          $removeWindow(window);
        });
      });
    }
  },

});
