/*****************************************************************************\
|                                               ( )_  _                       |
|    _ _    _ __   _ _    __    ___ ___     _ _ | ,_)(_)  ___   ___     _     |
|   ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| |  | |/',__)/' v `\ /'_`\   |
|   | (_) )| |  ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__,\| (˅) |( (_) )  |
|   | ,__/'(_)  `\__,_)`\__  |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/'  |
|   | |                ( )_) |                                                |
|   (_)                 \___/'                                                |
|                                                                             |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved.             |
| Licensed under the AGPL-3.0.                                                |
|                                                                             |
| According to our dual licensing model,this program can be used either      |
| under the terms of the GNU Affero General Public License,version 3,       |
| or under a proprietary license.                                             |
|                                                                             |
| The texts of the GNU Affero General Public License with an additional       |
| permission and of our proprietary license can be found at and               |
| in the LICENSE file you have received along with this program.              |
|                                                                             |
| This program is distributed in the hope that it will be useful,            |
| but WITHOUT ANY WARRANTY,without even the implied warranty of              |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                |
| GNU Affero General Public License for more details.                         |
|                                                                             |
| "General Bots" is a registered trademark of Pragmatismo.io.                 |
| The licensing of the program under the AGPLv3 does not imply a              |
| trademark license. Therefore any rights,title and interest in              |
| our trademarks remain entirely with us.                                     |
|                                                                             |
\*****************************************************************************/

'use strict';

import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
import { GBVMService } from './GBVMService.js';
import Path from 'path';

/**
 * Image processing services of conversation to be called by BASIC.
 */
export class KeywordsExpressions {
  private static getParams = (text: string, names) => {
    let ret = {};
    const splitParamsButIgnoreCommasInDoublequotes = (str: string) => {
      return str.split(',').reduce(
        (accum, curr) => {
          if (accum.isConcatting) {
            accum.soFar[accum.soFar.length - 1] += ',' + curr;
          } else {
            if (curr === '') {
              curr = null;
            }
            accum.soFar.push(curr);
          }
          if (curr.split('"').length % 2 == 0) {
            accum.isConcatting = !accum.isConcatting;
          }
          return accum;
        },
        { soFar: [], isConcatting: false }
      ).soFar;
    };

    const items = splitParamsButIgnoreCommasInDoublequotes(text);

    let i = 0;
    let json = '';
    names.forEach(name => {
      let value = items[i];
      i++;
      json = `${json} "${name}": ${value === undefined ? null : value} ${names.length == i ? '' : ','}`;
    });
    json = `${json}`;

    return json;
  };

  public static isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
  }

  /**
   * Returns the list of BASIC keyword and their JS match.
   * Based on https://github.com/uweg/vbscript-to-typescript.
   */
  public static getKeywords() {
    // Keywords from General Bots BASIC.

    let keywords = [];
    let i = 0;

    const convertConditions = input => {
      var result = input.replace(/ +and +/gim, ' && ');
      result = result.replace(/ +or +/gim, ' || ');
      result = result.replace(/ +<> +/gim, ' !== ');
      result = result.replace(/ += +/gim, ' === ');
      return result;
    };

    keywords[i++] = [
      /^\s*INPUT(.*)/gim,
      ($0, $1, $2) => {

        let separator;
        if ($1.indexOf(',')  > -1){
          separator  = ',';
        }
        else if ($1.indexOf(';')  > -1){
          separator  = ';';
        }
        let parts; 
        if ( separator && (parts = $1.split(separator)) && parts.length > 1){
          return `
          TALK ${parts[0]}
          HEAR ${parts[1]}`;
        }
        else
        {
          return `
          HEAR ${$1}`;
        }
      }
    ];

    keywords[i++] = [
      /^\s*WRITE(.*)/gim,
      ($0, $1, $2) => {
        return `PRINT${$1}`;
      }
    ];

    keywords[i++] = [/^\s*REM.*/gim, ''];

    keywords[i++] = [/^\s*CLOSE.*/gim, ''];

    // Always autoclose keyword.

    keywords[i++] = [/^\s*CLOSE.*/gim, ''];

    keywords[i++] = [/^\s*\'.*/gim, ''];

    keywords[i++] = [
      /^\s*PRINT ([\s\S]*)/gim,
      ($0, $1, $2) => {
        let sessionName;
        let kind = null;
        let pos;
        $1 = $1.trim();

        if ($1.substr(0, 1) === '#') {
          sessionName = $1.substr(1, $1.indexOf(',') - 1);
          $1 = $1.replace(/\; \"\,\"/gi, '');
          $1 = $1.substr($1.indexOf(',') + 1);

          let separator;
          if ($1.indexOf(',')  > -1){
            separator  = ',';
          }
          else if ($1.indexOf(';')  > -1){
            separator  = ';';
          }
          let items;
          if (separator && (items = $1.split(separator)) && items.length > 1) {
            return `await sys.save({pid: pid, file: files[${sessionName}], args:[${items.join(',')}]})`;
          } else {
            return `await sys.set({pid: pid, file: files[${sessionName}], address: col++, name: "${items[0]}",   value:  ${items[0]}})`;
          }
        } else {
          return `await dk.talk({pid: pid, text: ${$1}})`;
        }
      }
    ];

    keywords[i++] = [
      /^\s*open([\s\S]*)/gim,
      ($0, $1, $2) => {
        let sessionName;
        let kind = null;
        let pos;

        $1 = $1.replace('FOR APPEND', '');
        $1 = $1.replace('FOR OUTPUT', '');

        if ((pos = $1.match(/\s*AS\s*\#/gi))) {
          kind = 'AS';
        } else if ((pos = $1.match(/\s*WITH\s*\#/gi))) {
          kind = 'WITH';
        }

        if (pos) {
          let part = $1.substr($1.lastIndexOf(pos[0]));
          sessionName = `${part.substr(part.indexOf('#') + 1)}`;
          $1 = $1.substr(0, $1.lastIndexOf(pos[0]));
        }
        $1 = $1.trim();
        if (!$1.startsWith('"') && !$1.startsWith("'")) {
          $1 = `"${$1}"`;
        }
        const params = this.getParams($1, ['url', 'username', 'password']);

        // Checks if it is opening a file or a webpage.

        if (kind === 'AS' && KeywordsExpressions.isNumber(sessionName)) {
          const jParams = JSON.parse(`{${params}}`);
          const filename = `${jParams.url.substr(0, jParams.url.lastIndexOf("."))}.xlsx`;
          let code = 
          `
           col = 1
           await sys.save({pid: pid,file: "${filename}", args: [id] })
           await dk.setFilter ({pid: pid, value:  "id=" + id })
           files[${sessionName}] = "${filename}"  
          `;
          return code;
        } else {
          sessionName = sessionName?`"${sessionName}"`:null;
          kind = `"${kind}"`;
          return `page = await wa.openPage({pid: pid, handle: page, sessionKind: ${kind}, sessionName: ${sessionName}, ${params}})`;
        }
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*SELECT\s*(.*)/gim,
      ($0, $1, $2) => {
        let tableName = /\s*FROM\s*(\w+\$*)/.exec($2)[1];
        let sql = `SELECT ${$2}`.replace(tableName, '?');
        return `${$1} = await sys.executeSQL({pid: pid, data:${$1}, sql:"${sql}", tableName:"${tableName}"})\n`;
      }
    ];

    keywords[i++] = [/^\s*end if/gim, '}'];

    keywords[i++] = [
      /^\s*if +(.*?) +then/gim,
      (input, group1) => {
        var condition = convertConditions(group1);
        return 'if (' + condition + ') {';
      }
    ];

    keywords[i++] = [/^\s*else(?!{)/gim, '}\nelse {'];

    keywords[i++] = [/^\s*select case +(.*)/gim, 'switch ($1) {'];

    keywords[i++] = [/^\s*end select/gim, '}'];

    keywords[i++] = [/^\s*end function/gim, '}'];

    keywords[i++] = [/^\s*function +(.*)\((.*)\)/gim, '$1 = ($2) => {\n'];

    keywords[i++] = [/^\s*for +(.*to.*)/gim, 'for ($1) {'];

    keywords[i++] = [/^\s*next *$/gim, '}'];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*pay\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($2, ['orderId', 'customerName', 'ammount']);
        
        return `
          ${$1} = await sys.pay({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*do while +(.*)/gim,
      function (input, group1) {
        var condition = convertConditions(group1);
        return 'while (' + condition + ') {';
      }
    ];

    keywords[i++] = [/^\s*loop *$/gim, '}'];

    keywords[i++] = [
      /^\s*open([\s\S]*)/gim,
      ($0, $1, $2) => {
        let sessionName;
        let kind = null;
        let pos;

        $1 = $1.replace(' FOR APPEND', '');

        if ((pos = $1.match(/\s*AS\s*\#/))) {
          kind = 'AS';
        } else if ((pos = $1.match(/\s*WITH\s*\#/))) {
          kind = 'WITH';
        }

        if (pos) {
          let part = $1.substr($1.lastIndexOf(pos[0]));
          sessionName = `${part.substr(part.indexOf('#') + 1)}`;
          $1 = $1.substr(0, $1.lastIndexOf(pos[0]));
        }
        $1 = $1.trim();
        if (!$1.startsWith('"') && !$1.startsWith("'")) {
          $1 = `"${$1}"`;
        }
        const params = this.getParams($1, ['url', 'username', 'password']);

        // Checks if it is opening a file or a webpage.

        if (kind === 'AS' && KeywordsExpressions.isNumber(sessionName)) {
          const jParams = JSON.parse(`{${params}}`);
          const filename = `${Path.basename(jParams.url, 'txt')}xlsx`;
          return `files[${sessionName}] = "${filename}"`;
        } else {
          sessionName = `"${sessionName}"`;
          kind = `"${kind}"`;
          return `page = await wa.openPage({pid: pid, handle: page, sessionKind: ${kind}, sessionName: ${sessionName}, ${params}})`;
        }
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*get config\s*(.*)/gim,
      ($0, $1, $2) => {
        return `${$1} = await dk.getConfig ({pid: pid, name: ${$2}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set hear on)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `hrOn = ${$3}`;
      }
    ];
    
    keywords[i++] = [
      /^\s*(.*)\=\s*(REWRITE)(\s*)(.*)/gim,
      ($0, $1, $2, $3, $4) => {
        const params = this.getParams($4, ['text']);
        return `await sys.rewrite ({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(.*)\=\s*(AUTO SAVE)(\s*)(.*)/gim,
      ($0, $1, $2, $3, $4) => {
        const params = this.getParams($4, ['handle']);
        return `await sys.autoSave ({pid: pid, ${params}})`;
      }
    ];


    keywords[i++] = [
      /^\s*hear (\w+\$*) as (\w+( \w+)*.xlsx)/gim,
      ($0, $1, $2) => {
        return `${$1} = await dk.hear({pid: pid, kind:"sheet", arg: "${$2}"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*login/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"login"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*email/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"email"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*integer/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"integer"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*file/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"file"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*boolean/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"boolean"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*name/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"name"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*date/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"date"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*hour/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"hour"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*phone/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"phone"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*money/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"money"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*qrcode/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"qrcode"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*language/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"language"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*zipcode/gim,
      ($0, $1) => {
        return `${$1} = await dk.hear({pid: pid, kind:"zipcode"})`;
      }
    ];

    keywords[i++] = [
      /^\s*hear (\w+\$*) as\s*(.*)/gim,
      ($0, $1, $2) => {
        return `${$1} = await dk.hear({pid: pid, kind:"menu", args: [${$2}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*(hear)\s*(\w+\$*)/gim,
      ($0, $1, $2) => {
        return `${$2} = await dk.hear({pid: pid})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*find contact\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await dk.fndContact({pid: pid, ${$2}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*find\s*(.*)\s*or talk\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.find({pid: pid, handle: page, args:[${$2}]})\n
        if (!${$1}) {
          await dk.talk ({pid: pid, text: ${$3}})\n;
          return -1;
        }
        `;
      }
    ];

    keywords[i++] = [
      /^\s*CALL\s*(.*)/gim,
      ($0, $1) => {
        return `await ${$1}`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*find\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `
        ${$1} = await sys.find({pid: pid, handle: page, args: [${$2}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*create deal(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['dealName', 'contact', 'company', 'amount']);

        return `${$1} = await dk.createDeal({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*active tasks/gim,
      ($0, $1) => {
        return `${$1} = await dk.getActiveTasks({pid: pid})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*append\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.append({pid: pid, args:[${$2}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*sort\s*(\w+\$*)\s*by(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.sortBy({pid: pid, array: ${$2}, memberName: "${$3}"})`;
      }
    ];

    keywords[i++] = [
      /^\s*see\s*text\s*of\s*(\w+\$*)\s*as\s*(\w+\$*)\s*/gim,
      ($0, $1, $2, $3) => {
        return `${$2} = await sys.seeText({pid: pid, url: ${$1})`;
      }
    ];

    keywords[i++] = [
      /^\s*see\s*caption\s*of\s*(\w+\$*)\s*as(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$2} = await sys.seeCaption({pid: pid, url: ${$1})`;
      }
    ];

    keywords[i++] = [
      /^\s*(wait)\s*(\d+)/gim,
      ($0, $1, $2) => {
        return `await sys.wait({pid: pid, seconds:${$2}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(get stock for )(.*)/gim,
      ($0, $1, $2) => {
        return `stock = await sys.getStock({pid: pid, symbol: ${$2})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*get\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        const count = ($2.match(/\,/g) || []).length;
        const values = $2.split(',');

        // Handles GET "selector".

        if (count == 1) {
          return `${$1} =  await wa.getBySelector({pid: pid, handle:page, selector: ${values[0]}})`;
        }

        // Handles GET "frameSelector", "selector"
        else if (count == 2) {
          return `${$1} =  await wa.getByFrame({pid: pid, handle: page, ${values[0]}, frameOrSelector: ${values[1]}, selector: ${values[2]}})`;
        }

        // Handles the GET http version.
        else {
          return `${$1} = await sys.get ({pid: pid, file: ${$2}, addressOrHeaders: headers, httpUsername, httpPs})`;
        }
      }
    ];

    keywords[i++] = [
      /\= NEW OBJECT/gim,
      ($0, $1, $2, $3) => {
        return ` = {pid: pid}`;
      }
    ];

    keywords[i++] = [
      /\= NEW ARRAY/gim,
      ($0, $1, $2, $3) => {
        return ` = []`;
      }
    ];

    keywords[i++] = [
      /^\s*(go to)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['fromOrDialogName', 'dialogName']);
        return `await dk.gotoDialog({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set language)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setLanguage ({pid: pid, ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set filter)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setFilter ({pid: pid, ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*set param \s*(.*)\s*as\s*(.*)/gim,
      ($0, $1, $2) => {
        return `await dk.setUserParam ({pid: pid, ${$1}}, ${$2})`;
      }
    ];

    keywords[i++] = [
      /^\s*set header\s*(.*)\s*as\s*(.*)/gim,
      ($0, $1, $2) => {
        return `headers[${$1}]=${$2})`;
      }
    ];

    keywords[i++] = [
      /^\s*set http username\s*\=\s*(.*)/gim,
      ($0, $1) => {
        return `httpUsername = ${$1}`;
      }
    ];

    keywords[i++] = [
      /^\s*set http password\s*\=\s*(.*)/gim,
      ($0, $1) => {
        return `httpPs = ${$1}`;
      }
    ];

    keywords[i++] = [
      /^\s*(.*)\=\s*(datediff)(\s*)(.*)/gim,
      ($0, $1, $2, $3, $4) => {
        const params = this.getParams($4, ['date1', 'date2', 'mode']);
        return `await dk.getDateDiff ({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(.*)\=\s*(dateadd)(\s*)(.*)/gim,
      ($0, $1, $2, $3, $4) => {
        const params = this.getParams($4, ['date', 'mode', 'units']);
        return `await dk.dateAdd ({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set output)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setOutput ({pid: pid, value: "${$3}"})`;
      }
    ];
    
    keywords[i++] = [
      /^\s*(set max lines)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setMaxLines ({pid: pid, count: ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set max columns)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setMaxColumns ({pid: pid, count: ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set translator)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setTranslatorOn ({pid: pid, on: "${$3.toLowerCase()}"})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set theme)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setTheme ({pid: pid, theme: "${$3.toLowerCase()}"})`;
      }
    ];

    keywords[i++] = [
      /^\s*(set whole word)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.setWholeWord ({pid: pid, on: "${$3.toLowerCase()}"})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*post\s*(.*),\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.postByHttp ({pid: pid, url:${$2}, data:${$3}, headers})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*put\s*(.*),\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.putByHttp ({pid: pid, url:${$2}, data:${$3}, headers})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*download\s*(.*),\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.download ({pid: pid, handle:page, selector: ${$2}, folder:${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*CREATE FOLDER\s*(.*)/gim,
      ($0, $1, $2) => {
        return `${$1} = await sys.createFolder ({pid: pid, name:${$2}})`;
      }
    ];

    keywords[i++] = [
      /^\s*SHARE FOLDER\s*(.*)/gim,
      ($0, $1) => {
        return `await sys.shareFolder ({pid: pid, name: ${$1}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(create a bot farm using)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await sys.createABotFarmUsing ({pid: pid, ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(transfer to)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await dk.transferTo ({pid: pid, to:${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(\btransfer\b)(?=(?:[^"]|"[^"]*")*$)/gim,
      () => {
        return `await dk.transferTo ({pid: pid})`;
      }
    ];

    keywords[i++] = [
      /^\s*(exit)/gim,
      () => {
        return `return;`;
      }
    ];

    keywords[i++] = [
      /^\s*(END)/gim,
      () => {
        return `return;`;
      }
    ];

    keywords[i++] = [
      /^\s*(show menu)/gim,
      () => {
        return `await dk.showMenu ({pid: pid, })`;
      }
    ];

    keywords[i++] = [
      /^\s*(talk to)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['mobile', 'message']);
        return `await sys.talkTo({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(talk)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        $3 = GBVMService.normalizeQuotes($3);

        // Uses auto quote if this is a frase with more then one word.

        if (/\s/.test($3) && $3.substr(0, 1) !== '"') {
          $3 = `"${$3}"`;
        }
        return `await dk.talk ({pid: pid, text: ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(send sms to)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['mobile', 'message']);
        return `await sys.sendSmsTo({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(send email)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['to', 'subject', 'body']);
        return `await dk.sendEmail({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(send mail)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['to', 'subject', 'body']);
        return `await dk.sendEmail({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(send file to)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['mobile', 'filename', 'caption']);
        return `await dk.sendFileTo({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(hover)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['selector']);
        return `await wa.hover ({pid: pid, handle: page, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(click link text)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['text', 'index']);
        return `await wa.linkByText ({pid: pid, handle: page, ${params}})`;
      }
    ];
    
    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*(text of)(\s*)(.*)/gim,
      ($0, $1, $2, $3, $4) => {
        const params = this.getParams($4, ['frameOrSelector', 'selector']);
        return `
        ${$1} = await wa.getTextOf ({pid: pid, handle: page, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(click button)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['text', 'index']);
        return `await wa.clickButton ({pid: pid, handle: page, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(click)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        // page is not string.
        // https://github.com/GeneralBots/BotServer/issues/310
        const params = this.getParams($3, ['frameOrSelector', 'selector']);
        return `await wa.click ({pid: pid, handle:page, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(send file)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['filename', 'caption']);
        return `await dk.sendFile({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(copy)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['src', 'dest']);
        return `await sys.copyFile ({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(convert)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['src', 'dest']);
        return `await sys.convert ({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*chart(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($3, ['type', 'data', 'legends', 'transpose']);
        return `${$1} = await dk.chart ({pid: pid, ${params}})`;
      }
    ];

    keywords[i++] = [
      /^\s*MERGE\s*(.*)\s*WITH\s*(.*)BY\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await sys.merge({pid: pid, file: ${$1}, data: ${$2}, key1: ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(MERGE)(\s*)(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await img.mergeImage({pid: pid, files: [${$3}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*PRESS\s*(.*)/gim,
      ($0, $1, $2) => {
        return `await wa.pressKey({pid: pid, handle: page, char: ${$1}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*SCREENSHOT\s*(.*)/gim,
      ($0, $1, $2) => {
        return `${$1} = await wa.screenshot({pid: pid, handle: page, selector: ${$1}})`;
      }
    ];

    keywords[i++] = [
      /^\s*TWEET\s*(.*)/gim,
      ($0, $1, $2) => {
        return `await sys.tweet({pid: pid, text: ${$1}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*(.*)\s*as\s*image/gim,
      ($0, $1, $2) => {
        return `${$1} = await sys.asImage({pid: pid, data: ${$2}})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*(.*)\s*as\s*pdf/gim,
      ($0, $1, $2) => {
        return `${$1} = await sys.asPdf({pid: pid, data: ${$2})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*FILL\s*(.*)\s*WITH\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await sys.fill({pid: pid, templateName: ${$2}, data: ${$3}})`;
      }
    ];

    keywords[i++] = [
      /^\s*save\s*(\w+\$*)\s*as\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `await sys.saveFile({pid: pid, file: ${$2}, data: ${$1}})`;
      }
    ];

    keywords[i++] = [
      /^\s*(save)(\s*)(.*\.xlsx)(.*)/gim,
      ($0, $1, $2, $3, $4) => {
        $3 = $3.replace(/\'/g, '');
        $3 = $3.replace(/\"/g, '');
        $4 = $4.substr(2);
        return `await sys.save({pid: pid, file: "${$3}", args: [${$4}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*set\s*(.*)/gim,
      ($0, $1, $2) => {
        const params = this.getParams($1, ['file', 'address', 'value']);
        return `await sys.set ({pid: pid, handle: page, ${params}})`;
      }
    ];
    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*BLUR\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `${$1} = await img.blur({pid: pid, args: [${$2}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*SHARPEN\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        return `
        ${$1} = await img.sharpen({pid: pid, args: [${$2}]})`;
      }
    ];

    keywords[i++] = [
      /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*CARD\s*(.*)/gim,
      ($0, $1, $2, $3) => {
        const params = this.getParams($1, ['doc', 'data']);
        return `
        ${$1} = await dk.card({pid: pid, args: [${$2}]})`;
      }
    ];

    return keywords;
  }
}