/**
 * Document group criterion
 */
/*
 * TODO: Let the UPDATE event bubble up through parents!
 */
"use strict";

define([
  'vc/jsonld',
  'vc/unspecified',
  'vc/doc',
  'vc/docgroupref',
  'util'
], function (jsonldClass,
	           unspecClass,
	           docClass,
             docGroupRefClass) {

  const _validGroupOpRE = new RegExp("^(?:and|or)$");

  const docGroupClass = {

    _ldType : "docGroup",

    create : function (parent, json) {
      const obj = Object.create(jsonldClass).upgradeTo(this);
      obj._operands = [];
      obj.fromJson(json);
      if (parent !== undefined)
	      obj._parent = parent;
      return obj;
    },
    

    newAfter : function (obj) {
      this._operands.forEach(function (op, i) {
	      if (op === obj) {
	        const operand = unspecClass.create(this);
	        this._operands.splice(i + 1, 0, operand);
	        return this.update();
	      };
      }, this);
    },


    // The doc is already set in the group
    _duplicate : function (operand) {

      // TODO:
      //   Also check for duplicate docGroupRefs!
      if (operand.ldType() !== 'doc')
	      return null;

      const f = this._operands.find(
        op => 
	        op.ldType() === 'doc'
	        && operand.key() === op.key()
	        && operand.matchop() === op.matchop()
	        && operand.value() === op.value()
      );

      if (f)
        return f;

      return null;
    },
    

    append : function (operand) {
      
      // Append unspecified object
      if (operand === undefined) {

	      // Be aware of cyclic structures!
	      operand = unspecClass.create(this);
	      this._operands.push(operand);
	      return operand;
      };
      
      switch (operand["@type"]) {
	      
      case undefined:
	      // No @type defined
	      if (operand["ldType"] !== undefined) {
	        if (operand.ldType() !== 'doc' &&
	            operand.ldType() !== 'docGroup' &&
              operand.ldType() !== 'docGroupRef') {
	          KorAP.log(812, "Operand not supported in document group");
	          return;
	        };

	        // Be aware of cyclic structures!
	        operand.parent(this);

	        const dupl = this._duplicate(operand);
	        if (dupl === null) {
	          this._operands.push(operand);
	          return operand;
	        };
	        return dupl;
	      };

	      KorAP.log(701, "JSON-LD group has no @type attribute");
	      return;

      case "koral:doc":
	      // Be aware of cyclic structures!
	      const doc = docClass.create(this, operand);
	      if (doc === undefined)
	        return;
        
	      const dupl = this._duplicate(doc);
	      if (dupl === null) {
	        this._operands.push(doc);
	        return doc;
	      };
	      return dupl;

      case "koral:docGroup":

        // Be aware of cyclic structures!
	      const docGroup = docGroupClass.create(this, operand);
	      if (docGroup === undefined)
	        return;

	      // Flatten group
	      if (docGroup.operation() === this.operation()) {
          docGroup.operands().forEach(function(op) {
	          const dupl = this._duplicate(op);
	          if (dupl === null) {
	            this._operands.push(op);
	            op.parent(this);
	          };
	        }, this);
	        docGroup._operands = [];
	        docGroup.destroy();
	        return this;
	      };
	      this._operands.push(docGroup);
	      return docGroup;

      case "koral:docGroupRef":
      
        const docGroupRef = docGroupRefClass.create(this, operand);
      
        if (docGroupRef === undefined)
          return

        // TODO:
        //   Currently this doesn't do anything meaningful,
        //   as duplicate only checks on docs for the moment
        /*
	      var dupl = this._duplicate(doc);
	      if (dupl === null) {
	        this._operands.push(doc);
	        return doc;
	      };
	      return dupl;
        */
	      this._operands.push(docGroupRef);
        return docGroupRef;

      default:
	      KorAP.log(812, "Operand not supported in document group");
	      return;
      };
    },


    update : function () {
      const t = this;

      // There is only one operand in group
      
      if (t._operands.length === 1) {
	      
	      const parent = t.parent();
	      const op = t.getOperand(0);
	      
	      // This will prevent destruction of
	      // the operand
	      t._operands = [];

	      // Parent is a group
	      if (parent.ldType() !== null)
	        return parent.replaceOperand(t, op).update();

	      // Parent is vc
	      else {
	        t.destroy();
	        // Cyclic madness
	        parent.root(op);
	        op.parent(parent);
	        return parent.root();
	      };
      };

      if (t._el === undefined)
	      return t;

      const group = t._el;
      group.setAttribute('data-operation', t.operation());

      _removeChildren(group);

      // Append operands
      t._operands.forEach(
        op => group.appendChild(op.element())
      );

      // Set operators
      var op = t.operators(
	      t.operation() == 'and' ? false : true,
	      t.operation() == 'or'  ? false : true,
	      true
      );

      group.appendChild(op.element());

      if (KorAP.vc) {
        KorAP.vc.element().dispatchEvent(
          new CustomEvent('vcChange', {'detail' : t})
        );
      };
      
      return t;
    },


    element : function () {
      const t = this;

      if (t._el !== undefined)
	      return t._el;

      const e = t._el = document.createElement('div');
      e.setAttribute('class', 'docGroup');

      // Update the object - including optimization
      t.update();

      return e;
    },


    operation : function (op) {
      if (arguments.length === 1) {
	      if (_validGroupOpRE.test(op)) {
	        this._op = op;
	      }
	      else {
	        KorAP.log(810, "Unknown operation type");
	        return;
	      };
      };
      return this._op || 'and';
    },


    operands : function () {
      return this._operands;
    },


    getOperand : function (index) {
      return this._operands[index];
    },


    // Replace operand
    replaceOperand : function (oldOp, newOp) {
      
      for (let i = 0; i < this._operands.length; i++) {

	      if (this._operands[i] === oldOp) {
	        
	        // Just insert a doc or ...
	        if (newOp.ldType() === "doc" ||
	            newOp.ldType() === "non" ||
              newOp.ldType() === 'docGroupRef' ||
	            // ... insert a group of a different operation
	            // (i.e. "and" in "or"/"or" in "and")
	            newOp.operation() != this.operation()) {
	          this._operands[i] = newOp;
	          newOp.parent(this);
	        }

	        // Flatten group
	        else {

            // Remove old group
	          this._operands.splice(i, 1);

	          // Inject new operands
            newOp.operands().reverse().forEach(
              function(op) {
	              this._operands.splice(i, 0, op);
	              op.parent(this);
	            },
              this
            );

	          // Prevent destruction of operands
	          newOp._operands = [];
	          newOp.destroy();
	        };

	        oldOp.destroy();
	        return this;
	      }
      };
      return false;
    },


    // Delete operand from group
    delOperand : function (obj) {

      for (let i = 0; i < this._operands.length; i++) {
	      if (this._operands[i] === obj) {
	        
	        // Delete identified operand
	        this._operands.splice(i,1);

	        // Destroy object for cyclic references
	        obj.destroy();

	        return this;
	      };
      };

      // Operand not found
      return undefined;
    },
    

    // Deserialize from json
    fromJson : function (json) {

      if (json === undefined)
	      return this;

      if (json["@type"] === undefined) {
	      KorAP.log(701, "JSON-LD group has no @type attribute");
	      return;
      };

      if (json["operation"] === undefined ||
	        typeof json["operation"] !== 'string') {
	      KorAP.log(811, "Document group expects operation");
	      return;
      };

      const operation = json["operation"];

      this.operation(operation.replace(/^operation:/,''));

      if (json["operands"] === undefined ||
	        !(json["operands"] instanceof Array)) {
	      KorAP.log(704, "Operation needs operand list")
	      return;
      };

      // Add all documents
      json["operands"].forEach(i => this.append(i));
      
      return this;
    },


    toJson : function () {
      const opArray = new Array();
      this._operands.forEach(function(op) {
	      if (op.ldType() !== 'non')
	        opArray.push(op.toJson());
      });

      return {
	      "@type"     : "koral:" + this.ldType(),
	      "operation" : "operation:" + this.operation(),
	      "operands"  : opArray
      };
    },


    toQuery : function (brackets) {
      var list = this._operands
	        .filter(function (op) {
            return !op.incomplete();
	        })
	        .map(function (op) {
	          return (op.ldType() === 'docGroup') ?
	            op.toQuery(true) :
	            op.toQuery();
	        });
      
      if (list.length === 1)
	      return list.join('');
      else {
	      const str = list.join(this.operation() === 'or' ? ' | ' : ' & ');
	      return brackets ? '(' + str + ')' : str;
      };
    }
  };
  
  return docGroupClass;
});
