module.exports = function (uri, db) {
  if (db) this.db = db;
  var url = require('url');
  function isObject(A) {
    return A === null || (A && A.toString() === '[object Object]');
  }
  var list_operator = ['=', '>', '<', '=>', '<=', 'or', 'and'];
  var list_fn = [
    'ASCII',
    'CHAR_LENGTH',
    'CHARACTER_LENGTH',
    'CONCAT',
    'CONCAT_WS',
    'FIELD',
    'FIND_IN_SET',
    'FORMAT',
    'INSERT',
    'INSTR',
    'LCASE',
    'LEFT',
    'LENGTH',
    'LOCATE',
    'LOWER',
    'LPAD',
    'LTRIM',
    'MID',
    'POSITION',
    'REPEAT',
    'REPLACE',
    'REVERSE',
    'RIGHT',
    'RPAD',
    'RTRIM',
    'SPACE',
    'STRCMP',
    'SUBSTR',
    'SUBSTRING',
    'SUBSTRING_INDEX',
    'TRIM',
    'UCASE',
    'UPPER',
    'ABS',
    'ACOS',
    'ASIN',
    'ATAN',
    'ATAN2',
    'AVG',
    'CEIL',
    'CEILING',
    'COS',
    'COT',
    'COUNT',
    'DEGREES',
    'DIV',
    'EXP',
    'FLOOR',
    'GREATEST',
    'LEAST',
    'LN',
    'LOG',
    'LOG10',
    'LOG2',
    'MAX',
    'MIN',
    'MOD',
    'PI',
    'POW',
    'POWER',
    'RADIANS',
    'RAND',
    'ROUND',
    'SIGN',
    'SIN',
    'SQRT',
    'SUM',
    'TAN',
    'TRUNCATE',
    'ADDDATE',
    'ADDTIME',
    'CURDATE',
    'CURRENT_DATE',
    'CURRENT_TIME',
    'CURRENT_TIMESTAMP',
    'CURTIME',
    'DATE',
    'DATE_ADD',
    'DATE_FORMAT',
    'DATE_SUB',
    'DATEDIFF',
    'DAY',
    'DAYNAME',
    'DAYOFMONTH',
    'DAYOFWEEK',
    'DAYOFYEAR',
    'EXTRACT',
    'FROM_DAYS',
    'HOUR',
    'LAST_DAY',
    'LOCALTIME',
    'LOCALTIMESTAMP',
    'MAKEDATE',
    'MAKETIME',
    'MICROSECOND',
    'MINUTE',
    'MONTH',
    'MONTHNAME',
    'NOW',
    'PERIOD_ADD',
    'PERIOD_DIFF',
    'QUARTER',
    'SEC_TO_TIME',
    'SECOND',
    'STR_TO_DATE',
    'SUBDATE',
    'SUBTIME',
    'SYSDATE',
    'TIME',
    'TIME_FORMAT',
    'TIME_TO_SEC',
    'TIMEDIFF',
    'TIMESTAMP',
    'TO_DAYS',
    'WEEK',
    'WEEKDAY',
    'WEEKOFYEAR',
    'YEAR',
    'YEARWEEK',
    'BIN',
    'BINARY',
    'CASE',
    'CAST',
    'COALESCE',
    'CONNECTION_ID',
    'CONV',
    'CONVERT',
    'CURRENT_USER',
    'DATABASE',
    'IF',
    'IFNULL',
    'ISNULL',
    'LAST_INSERT_ID',
    'NULLIF',
    'SESSION_USER',
    'SYSTEM_USER',
    'USER',
    'VERSION',
  ];
  var list_fnz = ['COUNT', 'YEAR'];
  function check(expr) {
    const holder = [];
    const openBrackets = ['(', '{', '['];
    const closedBrackets = [')', '}', ']'];
    for (let letter of expr) {
      // loop trought all letters of expr
      if (openBrackets.includes(letter)) {
        // if its oppening bracket
        holder.push(letter);
      } else if (closedBrackets.includes(letter)) {
        // if its closing
        const openPair = openBrackets[closedBrackets.indexOf(letter)]; // find its pair
        if (holder[holder.length - 1] === openPair) {
          // check if that pair is the last element in the array
          holder.splice(-1, 1); // if so, remove it
        } else {
          // if its not
          holder.push(letter);
          break; // exit loop
        }
      }
    }
    return holder.length === 0; // return true if length is 0, otherwise false
  }

  var me = this;
  this.q = '/';
  this.output = '-';
  this.map = '{}';

  if (isObject(uri)) {
    me.cx = uri;
    this.connect = function () {
      return me.cx;
    };
  } else {
    this.connect = function () {
      me.cx = mysql.createConnection(uri);
      return me.cx;
    };
    this.db = uri.substr(uri.lastIndexOf('/') + 1, uri.length);
  }
  function info(x) {
    if (this.output == '--json') return JSON.stringify(x);
    return x;
  }

  this.init = function (cb) {
    var cx = this.connect();
    var map = {
      fields: [],
      tables: [],
      links: {},
      tlinks: {},
      keys: {},
      tkeys: {},
      op: {},
    };
    var tables = [];
    function inventory_fields(tables, ndx, cb) {
      if (!tables[ndx]) {
        me.map = map;
        return cb(map);
      }
      var tb = tables[ndx];
    }
    /** select all constraints */
    cx.query(
      `SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = '${me.db}'`,
      function (e, r) {
        for (var i = 0; i < r.length; i++) {
          if (
            r[i].TABLE_NAME + '.' + r[i].COLUMN_NAME !=
            r[i].REFERENCED_TABLE_NAME + '.' + r[i].REFERENCED_COLUMN_NAME
          ) {
            map.links[r[i].TABLE_NAME + '.' + r[i].COLUMN_NAME] =
              r[i].REFERENCED_TABLE_NAME + '.' + r[i].REFERENCED_COLUMN_NAME;
            if (r[i].CONSTRAINT_NAME.indexOf('fk_') > -1) {
              var field_name =
                r[i].TABLE_NAME + '.' + r[i].CONSTRAINT_NAME.split('fk_')[1];
            } else field_name = r[i].TABLE_NAME + '.' + r[i].CONSTRAINT_NAME;
            map.fields.push(field_name);
            var tk = map.links[r[i].TABLE_NAME + '.' + r[i].COLUMN_NAME];
            tk = tk.split('.')[0];
            var linker = map.links[r[i].TABLE_NAME + '.' + r[i].COLUMN_NAME];
            linker = linker.split('.');
            linker = field_name.split('.')[1] + '.' + linker[1];
            map.tlinks[field_name] =
              tk +
              ' ' +
              field_name.split('.')[1] +
              ' ON ' +
              linker +
              '=' +
              r[i].TABLE_NAME +
              '.' +
              r[i].COLUMN_NAME;
          }
        }
        /** list keys */
        cx.query(
          `SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA. STATISTICS WHERE TABLE_SCHEMA = "${me.db}" and INDEX_NAME="PRIMARY";`,
          function (e, r) {
            for (var i = 0; i < r.length; i++) {
              if (map.tables.indexOf(r[i].TABLE_NAME) == -1)
                map.tables.push(r[i].TABLE_NAME);
              map.keys[r[i].COLUMN_NAME] = r[i].TABLE_NAME;
              map.tkeys[r[i].TABLE_NAME] = r[i].COLUMN_NAME;
            }
            /** list all fields */
            cx.query(
              `select * from information_schema.columns where table_name in (SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' and table_schema="${me.db}")
 and table_schema="${me.db}" order by table_name,column_name`,
              function (e, r) {
                for (var i = 0; i < r.length; i++) {
                  var field = r[i];
                  /** find a way for non innodb database */
                  /*if (map.keys[field.COLUMN_NAME]) {
                    if (field.TABLE_NAME != map.keys[field.COLUMN_NAME])
                      map.links[field.TABLE_NAME + '.' + field.COLUMN_NAME] =
                        map.keys[field.COLUMN_NAME] + '.' + field.COLUMN_NAME;
                  }*/
                  map.fields.push(field.TABLE_NAME + '.' + field.COLUMN_NAME);

                  if (!map.op[field.TABLE_NAME]) map.op[field.TABLE_NAME] = {};
                  map.op[field.TABLE_NAME][field.COLUMN_NAME] = {
                    type: field.DATA_TYPE,
                    is_null: field.IS_NULLABLE,
                    default: field.COLUMN_DEFAULT,
                    comment: field.COLUMN_COMMENT,
                  };
                  for (var el in map.tlinks) {
                    if (el.indexOf(field.TABLE_NAME + '.') > -1) {
                      var fk = map.tlinks[el].split('.')[0];
                      map.op[field.TABLE_NAME][
                        el.split(field.TABLE_NAME + '.')[1]
                      ] = map.tlinks[el];
                    }
                  }
                }
                if (cx.close) cx.close();
                if (cb) cb(map);
                me.map = map;
              }
            );
          }
        );
      }
    );
  };
  this.err = [];
  this.query = function (str, cbo) {
    var _output, _params, _cmdq, _querystring;
    function error(j) {
      me.err.push(info({ err: j }));
      return me.err[0];
    }

    function querycommander(o) {
      var map = me.map;
      var TABLE = '';
      var SQL = ['SELECT DISTINCT'];
      var ORDERBY = [];
      var FIELDS = [];
      var JOINS = [];
      var GROUP_BY = [];
      var WHERE = [];
      var SPECIAL = [];
      var FROM = [];
      var SIMPLE_FIELDS = [];
      var FORMULA_FIELDS = [];
      function locate_fields(txt) {
        var p = txt.split('.');
        var table = TABLE;
        for (var i = 0; i < p.length; i++) {
          if (map.tables.indexOf(p[i] > -1)) {
            if (map.op[table][p[i]]) {
              if (!isObject(map.op[table][p[i]])) {
                if (JOINS.indexOf('left join ' + map.op[table][p[i]]) == -1)
                  JOINS.push('left join ' + map.op[table][p[i]]);
              }
            }
          }
          table = p[i];
        }
        if (p.length > 2) return p[p.length - 2] + '.' + p[p.length - 1];
        else return txt;
      }
      function getJOIN(tbref, tb) {
        if (tbref != tb) {
          //is there a link between tbref and tb
          if (me.map.op[tbref][tb]) {
            if (JOINS.indexOf('left join ' + me.map.op[tbref][tb]) == -1)
              JOINS.push('left join ' + me.map.op[tbref][tb]);
          } else {
            if (me.map.op[tb][tbref]) {
              var expression = me.map.op[tb][tbref];
              expression = expression.replace(tbref, tb);
              if (JOINS.indexOf('left join ' + expression) == -1)
                JOINS.push('left join ' + expression);
            }
          }
        }
      }
      function formula_parser(tb, fo) {
        var fstx = fo.split('(')[0];
        var fval = fo.split('(')[1].split(')')[0];
        if (list_fnz.indexOf(fstx.toUpperCase()) == -1)
          return error('ERR-002: Unknown formula');
        if (fstx.toUpperCase() == 'COUNT') {
          if (map.tables.indexOf(fval) > -1) {
            if (FROM.length == 0) return `(SELECT count(*) FROM ${fval})`;
            else {
              var index = '*';
              if (me.map.tkeys[fval]) index = me.map.tkeys[fval];
              getJOIN(FROM[0], fval);
              return `count(${fval}.${index})`;
            }
          } else {
            var fields = getFields(fval);
          }
        }
      }
      function getQueryString(qs) {
        function op_eq(k, v) {
          v = decodeURI(v);
          if (v.indexOf('*') > -1) {
            var sp = ' LIKE ';
            v = v.replace(/\*/g, '%');
          } else {
            if (v.indexOf('%') > -1) {
              var sp = ' LIKE ';
              v = v.replace(/\*/g, '%');
            } else sp = ' = ';
          }
          var pp = k.split('.');
          if (pp.length > 2) k = pp[pp.length - 2] + '.' + pp[pp.length - 1];
          // is it a special command ?
          if (k.indexOf(':') > -1) {
            if (k.toLowerCase() == ':limit') SPECIAL.push('LIMIT ' + v);
          } else {
            if (pp.length == 1) k = TABLE + '.' + pp[0];
            // it is a function ?
            if (v.indexOf('(') > -1) {
              // it is a function
            } else {
              if (v.indexOf('{') > -1) {
                // it is a subquery
                sp = ' in ';
                var oo = querycommander(v);
                console.log('->' + oo);
              } else {
                v = '"' + v.replace(/"/g, '\\"') + '"';
              }
            }
            WHERE.push('(' + k + sp + v + ')');
          }
        }
        function op_mop(k, v, mop) {
          v = decodeURI(v);

          // is it a special command ?
          if (k.indexOf(':') > -1) {
            if (k.toLowerCase() == ':limit') SPECIAL.push('LIMIT ' + v);
          } else {
            if (k.indexOf('(') > -1) {
              // it is a function ?
              var part = k.substr(0, k.indexOf('('));
              var ndx = locate_fields(
                k.substr(
                  k.indexOf('(') + 1,
                  k.indexOf(')') - k.indexOf('(') - 1
                )
              );
              k = part + '(' + ndx + ')';
              var fpp = formula_parser(TABLE, k);
            } else {
              var pp = k.split('.');
              if (pp.length > 2)
                k = pp[pp.length - 2] + '.' + pp[pp.length - 1];
              if (pp.length == 1) k = TABLE + '.' + pp[0];
            }

            // it is a function ?
            if (v.indexOf('(') > -1) {
              // it is a function
            } else {
              if (v.indexOf('{') > -1) {
                // it is a subquery
                sp = ' in ';
                var oo = querycommander(v);
                console.log('->' + oo);
              } else {
                v = '"' + v.replace(/"/g, '\\"') + '"';
              }
            }
            WHERE.push('(' + k + mop + v + ')');
          }
        }

        function evalfield(op, k, v) {
          if (k.indexOf('(') == 0) {
            k = k.substr(1, k.length);
          }
          if (op == '=') op_eq(k, v);
          else op_mop(k, v, op);
        }
        for (var i = 0; i < qs.length; i++) {
          var item = decodeURI(qs[i]);
          var oc = -1;
          var poc = -1;
          for (var j = 0; j < list_operator.length; j++) {
            var p = item.indexOf(list_operator[j]);
            if (p != -1) {
              oc = j;
              poc = p;
            }
          }
          if (oc == -1) return error('ERR-004: no operator');
          var loc = list_operator[oc];
          var key = item.substr(0, poc);
          var val = item.substr(poc + loc.length, item.length);
          if (loc != 'or' && loc != 'and') {
            if (i > 0) WHERE.push('AND');
            if (i == 0 && WHERE.length > 0) WHERE.push('AND');
            evalfield(loc, key, val);
          } else {
            WHERE.push(loc.toUpperCase());
          }
        }
      }
      function evalfilters(K) {
        for (var i = 0; i < K.length; i++) {
          if (K[i] != '') {
            var Kpo = K[i].indexOf('(');
            KIK = K[i].substr(0, Kpo);
            KIV = K[i].substr(Kpo, K[i].length);
            KIV = KIV.substr(0, KIV.lastIndexOf(')'));
            if (KIK == 'filter') getQueryString(KIV.split('&'));
          }
        }
      }
      function getGroups(str) {
        var items = {};
        var _id = -1;
        if (str.indexOf('[') > -1) {
          _id = str.substr(str.indexOf('[') + 1, str.length);
          _id = _id.substr(0, _id.indexOf(']'));
          str =
            str.substr(0, str.indexOf('[')) +
            str.substr(str.indexOf(']') + 1, str.length);
        }
        var ref = str.search(/[^A-Za-z0-9_]/);
        if (str.indexOf('{') == -1) {
          if (ref > -1)
            str = str.substr(0, ref) + '{*}' + str.substr(ref, str.length);
        }

        if (ref == -1) {
          str = str + '{*}';
          ref = str.search(/[^A-Za-z0-9_]/);
        }
        var title = str.substr(0, ref);
        if (title) {
          if (_id != -1) {
            //for (var el in map.keys) {
            WHERE.push(map.tkeys[title] + '=' + _id);
            //}
          }
        }
        if (!title) title = 'root';
        TABLE = title;

        if (str.substr(ref, 1) == '{') {
          // on cherche la dernière occurence de }
          var last = str.lastIndexOf('}');
          var K = str.substr(last + 1, str.length);
          evalfilters(K.split(/\.(?![^()]*\))/));
          var expr = str.substr(ref + 1, last - ref - 1).split(/,(?![^{}]*\})/);
          for (var i = 0; i < expr.length; i++) {
            if (expr[i].indexOf('{') > -1) expr[i] = getGroups(expr[i]);
          }
          items[title] = expr;
        } else {
          items.root = str;
        }
        return items;
      }

      function getFields(item, orderby) {
        var value = item.split('=')[1];
        if (!value) value = ' as "' + item + '"';
        else value = ' as "' + value + '"';
        item = item.split('=')[0];
        var f = item.split('.');
        var table = TABLE;
        for (var z = 0; z < f.length - 1; z++) {
          var reftb = f[z];
          if (!map.op[table]) {
            if (!map.tlinks[TABLE + '.' + table])
              return error('ERR-005: ' + table + ' not found');
            else {
              console.log(table + '.' + reftb);
              console.log(map.tlinks[table + '.' + reftb]);
              var expression = map.tlinks[reftb + '.' + table];
              console.log(expression);
            }
          } else {
            console.log(table);
            console.log(reftb);
            if (!map.op[table][reftb]) {
              if (map.op[reftb][table]) {
                var expression = me.map.op[reftb][table];
                expression = expression.replace(table, reftb);
                if (JOINS.indexOf('left join ' + expression) == -1)
                  JOINS.push('left join ' + expression);
              } else
                return error(
                  'ERR-003: links ' + table + '->' + reftb + ' not found'
                );
            } else {
              if (JOINS.indexOf('left join ' + map.op[table][reftb]) == -1)
                JOINS.push('left join ' + map.op[table][reftb]);
              table = reftb;
            }
          }
        }
        var last_item = f[f.length - 1];
        if (map.tables.indexOf(last_item) > -1) last_item = last_item + '.*';
        else last_item = f[f.length - 2] + '.' + last_item;
        FIELDS.push(last_item + value);

        if (orderby == '+') ORDERBY.push(last_item);
        if (orderby == '-') ORDERBY.push(last_item + ' DESC');
      }
      function tokenizer(name, o, ndx, TAB) {
        var orderby = 'x';

        if (!Array.isArray(o)) {
          var item = o;
          var value = item.split('=')[1];
          TAB = o;
          FIELDS.push(o + '.*');
          return;
        }
        if (!o[ndx]) {
          if (_querystring.length > 0) return getQueryString(_querystring);
        }
        var item = o[ndx];
        if (isObject(item)) {
          for (var el in item) {
            if (JOINS.indexOf('left join ' + map.op[name][el]) == -1)
              JOINS.push('left join ' + map.op[name][el]);
            for (var i = 0; i < item[el].length; i++)
              tokenizer(el, item[el], i);
          }
        } else {
          var value = item.split('=')[1];
          if (item.indexOf('*') == -1) {
            if (!value) {
              if (item.indexOf('+') == item.length - 1)
                value = ' as "' + item.substr(0, item.indexOf('+')) + '"';
              else {
                if (item.indexOf('-') == item.length - 1)
                  value = ' as "' + item.substr(0, item.indexOf('-')) + '"';
                else {
                  value = ' as "' + item + '"';
                }
              }
            } else value = ' as "' + value + '"';
          } else value = '';
          TABLE = name;
          // check name
          if (name == 'root') {
            /** no FROM clause */
            if (item.indexOf('(') > -1) {
              FIELDS.push(formula_parser('root', item) + value);
            }
          } else {
            FROM.push(TABLE);
            var table_ref = name;
            if (item.indexOf('+') == item.length - 1) {
              item = item.substr(0, item.indexOf('+'));
              orderby = '+';
            }
            if (item.indexOf('-') == item.length - 1) {
              item = item.substr(0, item.indexOf('-'));
              orderby = '-';
            }
            if (item.indexOf('(') > -1) {
              var fpp = formula_parser('root', item);
              FIELDS.push(fpp + value);
              FORMULA_FIELDS.push(fpp);
            } else {
              if (item.indexOf('.') == -1) {
                FIELDS.push(table_ref + '.' + item + value);
                SIMPLE_FIELDS.push(table_ref + '.' + item);
                if (orderby == '+') ORDERBY.push(table_ref + '.' + item);
                if (orderby == '-')
                  ORDERBY.push(table_ref + '.' + item + ' DESC');
              } else {
                getFields(item, orderby);
              }
            }
          }
        }
      }
      // clean up string
      o = o.trim().replace(/[ ,]+/g, ',');
      // check brackets
      if (!check(o)) return error('ERR-001: invalid brackets sequence');
      // breaks string
      var puri = o.split('/')[0];
      var s = url.parse(o, false);
      _cmdq = s.pathname;
      var quer = s.query;
      if (quer) _querystring = quer.split(/&(?![^()]*\))/);
      else _querystring = [];
      _cmdq = decodeURI(_cmdq);

      var groups = getGroups(_cmdq);

      for (var el in groups) {
        var group = groups[el];
        for (var z = 0; z < group.length; z++) {
          tokenizer(el, groups[el], z);
        }
      }
      if (_querystring.length > 0) getQueryString(_querystring);
      if (FORMULA_FIELDS.length > 0) GROUP_BY = GROUP_BY.concat(SIMPLE_FIELDS);
      SQL.push(FIELDS.join(','));
      if (TABLE != 'root') SQL.push('FROM ' + FROM[0]);
      SQL.push(JOINS.join(' '));
      if (WHERE.length > 0) SQL.push('WHERE ' + WHERE.join(' '));
      if (GROUP_BY.length > 0) SQL.push('GROUP BY ' + GROUP_BY.join(' '));
      if (ORDERBY.length > 0) SQL.push('ORDER BY ' + ORDERBY.join(','));

      if (SPECIAL.length > 0) SQL.push(SPECIAL.join(' '));

      return '(' + SQL.join(' ') + ')';
    }

    if (!me.map) me.err.push('MAP FAILED');
    var SQL = querycommander(str);
    if (me.err.length > 0) return me.err[0];
    console.log(SQL);
    return SQL;
  };
};
