/*
 *
 * This the grammar we use to generate the parser jhGrammar.js.
 *   We use the online tool http://pegjs.org/online to generate the parser.
 *   Modifying this file won't affect the program, this is just to have a more readable grammar.
 */

{
  function concat(array1, array2){
    if (array2) {
      for (var i = 0; i < array2.length; i++) {
        if (array1.indexOf(array2[i]) === -1) {
          array1.push(array2[i]);
        }
      }
    }
    return array1;
  }

  function flattenArray(array) {
    var newArray = [];

    array.forEach(function(element) {
      if (!element.length) {
        newArray.push(element);
      } else {
        element.forEach(function(internalElement) {
          newArray.push(internalElement);
        });
      }
    });

    return newArray;
  }

  function makeString(o) {
    return o.join("");
  }

  function addCardinalityToRelationships(cardinality, relationships) {
    if (!relationships) {
      return;
    }
    relationships.forEach(function(relationship) {
      relationship.cardinality = cardinality;
    });
  }

  var entities = [];
  var relationships = [];
  var enums = [];
  var dto = {};
  var pagination = {};
  var service = {};
  var microservice = {};
  var searchEngine = {};
  var noClient = { list: [], excluded: [] };
  var noServer = { list: [], excluded: [] };
  var angularSuffix = {};
  var noFluentMethod = { list: [], excluded: [] };
}

start = p:prog { return p; }

prog
  = space* ed:entityDecl space* p:prog {return {"entities" : concat([ed],p.entities) , "relationships" : p.relationships, "enums" : p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* rd:relationDecl space* p:prog {return {"entities" : p.entities, "relationships" : flattenArray(concat([rd],p.relationships)), "enums" : p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* end:enumDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": concat([end], p.enums), "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* dto:dtoDecl space* p:prog {return {"entities": p.entities, "relationships": p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* pagination:pagiDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* service:serviceDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* Comment space* p:prog {return {"entities" : p.entities,"relationships": p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* JDLComment space* p:prog {return {"entities" : p.entities,"relationships": p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* microservice:microserviceDecl1 space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* searchEngine:searchEngineDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* noClient:noClientDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* noServer:noServerDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* angularSuffix:angularSuffixDecl space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / space* noFluentMethod:noFluentMethod space* p:prog {return {"entities" : p.entities, "relationships" : p.relationships, "enums": p.enums, "dto": p.dto, "pagination": p.pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}
  / "" {return {"entities" : entities,"relationships": relationships, "enums": enums, "dto": dto, "pagination": pagination, "service": service, "microservice": microservice, "searchEngine": searchEngine, "noClient": noClient, "noServer": noServer, "angularSuffix": angularSuffix, "noFluentMethod": noFluentMethod};}

//Entities
entityDecl
  = jd:Comment? space* JDLComment? space* "entity" space* e:ENTITYNAME space* tableName:entityTableNameDecl? space* eb:entityBody? space* JDLComment? {return {"name" : e, tableName: tableName ? tableName : e, "body":eb, "javadoc":jd};}
  / "entity" space* e:ENTITYNAME space* eb:entityBody? {return {"name" : e, "body":eb, "javadoc":""};}

entityTableNameDecl
  = '(' space* name:[A-z0..9_-]+ space* ')' {return name.join('');}

entityBody
  = "{" space* JDLComment? space* fdl:fieldDeclList space* JDLComment? space* "}" {return fdl;}
  / "" {return [];}

fieldDeclList
  = JDLComment? space* com:Comment? space* JDLComment? space* f:FIELDNAME space t:type space* vl:validationList? space* com2:Comment? space* ","? space* JDLComment? space* fdl:fieldDeclList {return concat([{"name" : f, "type" : t, "validations" : vl, "javadoc": com||com2}], fdl );}
  / com:Comment? space* f:FIELDNAME space t:type space* vl:validationList {return [{"name" : f, "type" : t, "validations" : vl, "javadoc": com}];}
  / "" {return [];}

validationList
  = v:validation space* vl:validationList {return concat([v], vl);}
  / "" {return [];}

//Relationship
relationDecl
  = 'relationship' space rt:relationshipType space* "{" space* JDLComment? space* bodies:relationshipBodies space* JDLComment? space* "}" {
        addCardinalityToRelationships(rt, bodies);
        return concat([], bodies);
    }

relationshipBodies
  = rb:relationshipBody space* ',' space* morerb:relationshipBodies { return concat([rb], morerb); }
  / rb:relationshipBody { return [rb]; }

relationshipBody
  = from:relationshipSide space* JDLComment? space* "to" space* JDLComment? space* to:relationshipSide space* JDLComment? {return {"from" : from , "to" : to};}

relationshipSide
  = jd:Comment? space* e:ENTITYNAME space* "{" space* i:INJECTEDFIELDNAME space* required:'required'? "}" space* {return {"name" :e, "injectedfield" : i, "javadoc": jd, 'required': required ? true : false};}
  / jd:Comment? space* e:ENTITYNAME space* ("{" space* "}")? space* {return {"name" :e, "injectedfield" : null, "javadoc": jd};}
  / e:ENTITYNAME space {return {"name" :e, "injectedfield" : null, "javadoc": ''};}


//Enum
enumDecl
  = 'enum' space e:ENUMNAME space* '{' space* JDLComment? space* el:enumPropList space* JDLComment? space* '}' {return {"name":e, "values" : el };}

enumPropList
  = e:ENUMPROP space* ',' space* JDLComment? space* el:enumPropList {return concat([e],el);}
  / e:ENUMPROP {return [e];}

exclusion
  = 'except' space+ sub:exclusionSub {return sub;}

exclusionSub
  = e:ENTITYNAME space* ',' space* sub:exclusionSub {return concat([e], sub);}
  / e:ENTITYNAME {return [e];}

// noFluentMethod
noFluentMethod
  = 'noFluentMethod for' space+ decl:subNoFluentMethod space* ex:exclusion? space* JDLComment? {
    concat(noFluentMethod.list, decl);
    if (ex) {
        concat(noFluentMethod.excluded, ex);
    }
  }

subNoFluentMethod
  = e:ENTITYNAME space* ',' space* sub:subNoFluentMethod {return concat([e], sub);}
  / '*' {return ['*'];}
  / 'all'{return ['*'];}
  / e:ENTITYNAME {return [e];}

// DTO
dtoDecl
  = 'dto' space+ decl:entityList space* ex:exclusion? space* JDLComment? {
    dto[decl[decl.length - 1]] = dto[decl[decl.length - 1]] || { list: [], excluded: [] };
    concat(dto[decl[decl.length - 1]].list, decl.slice(0, decl.length - 1));
    if (ex) {
    	concat(dto[decl[decl.length - 1]].excluded, ex);
    }
  }

// Pagination
pagiDecl
  = 'paginate' space+ decl:entityList space* ex:exclusion? space* JDLComment? {
  	pagination[decl[decl.length - 1]] = pagination[decl[decl.length - 1]] || { list: [], excluded: [] };
    concat(pagination[decl[decl.length - 1]].list, decl.slice(0, decl.length - 1));
    if (ex) {
    	concat(pagination[decl[decl.length - 1]].excluded, ex);
    }
  }

// Service
serviceDecl
  = 'service' space+ decl:entityList space* ex:exclusion? space* JDLComment? {
  	service[decl[decl.length - 1]] = service[decl[decl.length - 1]] || { list: [], excluded: [] };
    concat(service[decl[decl.length - 1]].list, decl.slice(0, decl.length - 1));
    if (ex) {
    	concat(service[decl[decl.length - 1]].excluded, ex);
    }
  }

// first way of declaring microservices
microserviceDecl1
  = 'microservice' space+ decl:entityList space* ex:exclusion? space* JDLComment? {
    microservice[decl[decl.length - 1]] = microservice[decl[decl.length - 1]] || { list: [], excluded: [] };
    concat(microservice[decl[decl.length - 1]].list, decl.slice(0, decl.length - 1));
    if (ex) {
        concat(microservice[decl[decl.length - 1]].excluded, ex);
    }
  }

// searchEngine
searchEngineDecl
  = 'search' space+ decl:entityList space* ex:exclusion? space* JDLComment? {
    searchEngine[decl[decl.length - 1]] = searchEngine[decl[decl.length - 1]] || { list: [], excluded: [] };
    concat(searchEngine[decl[decl.length - 1]].list, decl.slice(0, decl.length - 1));
    if (ex) {
        concat(searchEngine[decl[decl.length - 1]].excluded, ex);
    }
  }

// skipClient option
noClientDecl
  = 'skipClient for' space* decl:subNoClientDecl space* ex:exclusion? space* JDLComment? {
    concat(noClient.list, decl);
    if (ex) {
        concat(noClient.excluded, ex);
    }
  }

subNoClientDecl
  = e:ENTITYNAME space* ',' space* sub:subNoClientDecl {return concat([e], sub);}
  / '*' {return ['*'];}
  / 'all'{return ['*'];}
  / e:ENTITYNAME {return [e];}


// skipServer option
noServerDecl
  = 'skipServer for' space* decl:subNoServerDecl space* ex:exclusion? space* JDLComment? {
    concat(noServer.list, decl);
    if (ex) {
        concat(noServer.excluded, ex);
    }
  }

subNoServerDecl
  = e:ENTITYNAME space* ',' space* sub:subNoServerDecl {return concat([e], sub);}
  / '*' {return ['*'];}
  / 'all'{return ['*'];}
  / e:ENTITYNAME {return [e];}


// angularSuffix option
angularSuffixDecl
  = 'angularSuffix' space* decl:entityList space* ex:exclusion? space* JDLComment? {
    angularSuffix[decl[decl.length - 1]] = angularSuffix[decl[decl.length - 1]] || { list: [], excluded: [] };
    concat(angularSuffix[decl[decl.length - 1]].list, decl.slice(0, decl.length - 1));
    if (ex) {
        concat(angularSuffix[decl[decl.length - 1]].excluded, ex);
    }
  }

// common way to declare an entity list
entityList
  = e:ENTITYNAME space* ',' space* sub:entityList {return concat([e], sub);}
  / '*' space* 'with' space* method:([A-z0-9-]+) {return ['*', method.toString().replace(/,/g,'')];}
  / 'all' space* 'with' space* method:([A-z0-9-]+) {return ['*', method.toString().replace(/,/g,'')];}
  / e:ENTITYNAME space* 'with' space* method:([A-z0-9-]+) {return [e, method.toString().replace(/,/g,'')];}


relationshipType = "OneToOne" {return "one-to-one";} / "OneToMany" {return "one-to-many";} / "ManyToOne" {return "many-to-one";} / "ManyToMany" {return "many-to-many";}

type "a type" = head:[A-Z]tail:[a-zA-Z0-9]* {return head + tail.join('');}

validation
  = "required" {return {key:'required', value:''};}
  / "minlength" space* "(" space* int:INTEGER space* ")" {return {key:'minlength', value:int};}
  / "maxlength" space* "(" space* int:INTEGER space* ")" {return {key:'maxlength', value:int};}
  / "minbytes" space* "(" space* int:INTEGER space* ")" {return {key:'minbytes', value:int};}
  / "maxbytes" space* "(" space* int:INTEGER space* ")" {return {key:'maxbytes', value:int};}
  / "max" space* "(" space* int:INTEGER space* ")" {return {key:'max', value:int};}
  / "min" space* "(" space* int:INTEGER space* ")" {return {key:'min', value:int};}
  / "pattern" space* "("  apostrophe regexp:REGEXP  apostrophe space* ")" {return {key:'pattern' , value:regexp};}



apostrophe "apostrophe"
  = a:["|']

REGEXP "a regular expression"
  = word:[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':\\|,.<>\/? ]* {return makeString(word);}

ENUMNAME "an enum name"
  = head:[A-Z]tail:[a-zA-Z0-9]* {return head + tail.join('');}

ENUMPROP "an enum property"
  = underscore:[_]*head:[A-Z0-9]tail:[A-Z0-9_]* {
  	return underscore.join('') + head + tail.join('');
  }

INTEGER "integer"
  = negative:'-'?int:[0-9]+ {return parseInt((negative ? negative : '') + int.join(""), 10);}

INJECTEDFIELDNAME "an injected field"
  = head:[a-zA-Z]tail:[a-zA-Z0-9()]* {return head + tail.join('');}

ENTITYNAME "an entity name"
  = head:[A-Z]tail:[a-zA-Z0-9]* {return head + tail.join('');}

FIELDNAME "a field name"
  = head:[a-zA-Z]tail:[a-zA-Z0-9]* {return head + tail.join('');}

space "a space"
  = space:['\n'|'\t'|'\r'|' '|\u2028|\u2029]+

/******************               COMMENT                 ***********************/
Comment
  = ComStart nc:Not_Com* ComStop {return makeString(nc);}

ComStart = "/*" [*]*

ComStop = [*]+ "/"

// a completely ignored comment, will not be a Javadoc comment
JDLComment = "//" [^\n\r]*

Not_Com = !ComStop !ComStart char:. {return char;}
