/**
 * @license
 * Visual Blocks Language
 *
 * Copyright 2016 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Generating Lua for list blocks.
 * @author rodrigoq@google.com (Rodrigo Queiro)
 */
'use strict';

goog.provide('Blockly.Lua.lists');

goog.require('Blockly.Lua');


Blockly.Lua['lists_create_empty'] = function(block) {
  // Create an empty list.
  // List literals must be parenthesized before indexing into.
  return ['({})', Blockly.Lua.ORDER_ATOMIC];
};

Blockly.Lua['lists_create_with'] = function(block) {
  // Create a list with any number of elements of any type.
  var code = new Array(block.itemCount_);
  for (var n = 0; n < block.itemCount_; n++) {
    code[n] = Blockly.Lua.valueToCode(block, 'ADD' + n,
        Blockly.Lua.ORDER_NONE) || 'None';
  }
  code = '({' + code.join(', ') + '})';
  return [code, Blockly.Lua.ORDER_ATOMIC];
};

Blockly.Lua['lists_repeat'] = function(block) {
  // Create a list with one element repeated.
  var functionName = Blockly.Lua.provideFunction_(
      'create_list_repeated',
      ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(item, count)',
       '  local t = {}',
       '  for i = 1, count do',
       '    table.insert(t, item)',
       '  end',
       '  return t',
       'end']);
  var argument0 = Blockly.Lua.valueToCode(block, 'ITEM',
      Blockly.Lua.ORDER_NONE) || 'None';
  var argument1 = Blockly.Lua.valueToCode(block, 'NUM',
      Blockly.Lua.ORDER_NONE) || '0';
  var code = functionName + '(' + argument0 + ', ' + argument1 + ')';
  return [code, Blockly.Lua.ORDER_HIGH];
};

Blockly.Lua['lists_length'] = function(block) {
  // String or array length.
  var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
      Blockly.Lua.ORDER_HIGH) || '({})';
  return ['#' + argument0, Blockly.Lua.ORDER_HIGH];
};

Blockly.Lua['lists_isEmpty'] = function(block) {
  // Is the string null or array empty?
  var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
      Blockly.Lua.ORDER_HIGH) || '({})';
  var code = '#' + argument0 + ' == 0';
  return [code, Blockly.Lua.ORDER_RELATIONAL];
};

Blockly.Lua['lists_indexOf'] = function(block) {
  // Find an item in the list.
  var argument0 = Blockly.Lua.valueToCode(block, 'FIND',
      Blockly.Lua.ORDER_NONE) || '\'\'';
  var argument1 = Blockly.Lua.valueToCode(block, 'VALUE',
      Blockly.Lua.ORDER_NONE) || '({})';
  var functionName;
  if (block.getFieldValue('END') == 'FIRST') {
    functionName = Blockly.Lua.provideFunction_(
        'first_index',
        ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, elem)',
         '  for k, v in ipairs(t) do',
         '    if v == elem then',
         '      return k',
         '    end',
         '  end',
         '  return 0',
         'end']);
  } else {
    functionName = Blockly.Lua.provideFunction_(
        'last_index',
        ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, elem)',
         '  for i = #t, 1, -1 do',
         '    if t[i] == elem then',
         '      return i',
         '    end',
         '  end',
         '  return 0',
         'end']);
  }
  var code = functionName + '(' + argument1 + ', ' + argument0 + ')';
  return [code, Blockly.Lua.ORDER_HIGH];
};

/**
 * Returns an expression calculating the index into a list.
 * @private
 * @param {string} listname Name of the list, used to calculate length.
 * @param {string} where The method of indexing, selected by dropdown in Blockly
 * @param {string=} opt_at The optional offset when indexing from start/end.
 * @return {string} Index expression.
 */
Blockly.Lua.lists.getIndex_ = function(listname, where, opt_at) {
  if (where == 'FIRST') {
    return '1';
  } else if (where == 'FROM_END') {
    return '#' + listname + ' + 1 - ' + opt_at;
  } else if (where == 'LAST') {
    return '#' + listname;
  } else if (where == 'RANDOM') {
    return 'math.random(#' + listname + ')';
  } else {
    return opt_at;
  }
};

/**
 * Counter for generating unique symbols.
 * @private
 * @type {number}
 */
Blockly.Lua.lists.gensym_counter_ = 0;

/**
 * Generate a unique symbol.
 * @private
 * @return {string} unique symbol, eg 'G123'
 */
Blockly.Lua.lists.gensym_ = function() {
  return 'G' + Blockly.Lua.lists.gensym_counter_++;
};

Blockly.Lua['lists_getIndex'] = function(block) {
  // Get element at index.
  // Note: Until January 2013 this block did not have MODE or WHERE inputs.
  var mode = block.getFieldValue('MODE') || 'GET';
  var where = block.getFieldValue('WHERE') || 'FROM_START';
  var at = Blockly.Lua.valueToCode(block, 'AT',
      Blockly.Lua.ORDER_ADDITIVE) || '1';
  var list = Blockly.Lua.valueToCode(block, 'VALUE',
      Blockly.Lua.ORDER_HIGH) || '({})';
  var getIndex_ = Blockly.Lua.lists.getIndex_;
  var gensym_ = Blockly.Lua.lists.gensym_;

  // If `list` would be evaluated more than once (which is the case for LAST,
  // FROM_END, and RANDOM) and is non-trivial, make sure to access it only once.
  if ((where == 'LAST' || where == 'FROM_END' || where == 'RANDOM') &&
      !list.match(/^\w+$/)) {
    // `list` is an expression, so we may not evaluate it more than once.
    if (mode == 'REMOVE') {
      // We can use multiple statements.
      var listVar = Blockly.Lua.variableDB_.getDistinctName(
          'tmp_list', Blockly.Variables.NAME_TYPE);
      var code = listVar + ' = ' + list + '\n' +
          'table.remove(' + listVar + ', ' + getIndex_(listVar, where, at) +
              ')\n';
      return code;
    } else {
      // We need to create a procedure to avoid reevaluating values.
      if (mode == 'GET') {
        // Note that getIndex_() ignores `at` when `where` == 'LAST' or
        // 'RANDOM', so we only need one procedure for each of those 'where'
        // values.  The value for 'FROM_END' depends on `at`, so we will
        // generate a unique procedure (name) each time.
        var functionName = Blockly.Lua.provideFunction_(
            'list_get_' + where.toLowerCase() +
                (where == 'FROM_END' ? '_' + gensym_() : ''),
            ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
             '  return t[' + getIndex_('t', where, at) + ']',
             'end']);
      } else {  // mode == 'GET_REMOVE'
        // We need to create a procedure.
        var functionName = Blockly.Lua.provideFunction_(
            'list_remove_' + where.toLowerCase() +
                (where == 'FROM_END' ? '_' + gensym_() : ''),
            ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
             '  return table.remove(t, ' + getIndex_('t', where, at) + ')',
             'end']);
      }
      var code = functionName + '(' + list + ')';
      return [code, Blockly.Lua.ORDER_HIGH];
    }
  } else {
    // Either `list` is a simple variable, or we only need to refer to `list`
    // once.
    if (mode == 'GET') {
      var code = list + '[' + getIndex_(list, where, at) + ']';
      return [code, Blockly.Lua.ORDER_HIGH];
    } else {
      var code = 'table.remove(' + list + ', ' + getIndex_(list, where, at) +
          ')';
      if (mode == 'GET_REMOVE') {
        return [code, Blockly.Lua.ORDER_HIGH];
      } else {  // `mode` == 'REMOVE'
        return code + '\n';
      }
    }
  }
};

Blockly.Lua['lists_setIndex'] = function(block) {
  // Set element at index.
  // Note: Until February 2013 this block did not have MODE or WHERE inputs.
  var list = Blockly.Lua.valueToCode(block, 'LIST',
      Blockly.Lua.ORDER_HIGH) || '({})';
  var mode = block.getFieldValue('MODE') || 'SET';
  var where = block.getFieldValue('WHERE') || 'FROM_START';
  var at = Blockly.Lua.valueToCode(block, 'AT',
      Blockly.Lua.ORDER_ADDITIVE) || '1';
  var value = Blockly.Lua.valueToCode(block, 'TO',
      Blockly.Lua.ORDER_NONE) || 'None';
  var getIndex_ = Blockly.Lua.lists.getIndex_;

  // If `list` would be evaluated more than once (which is the case for LAST,
  // FROM_END, and RANDOM) and is non-trivial, make sure to access it only once.
  if ((where == 'LAST' || where == 'FROM_END' || where == 'RANDOM') &&
      !list.match(/^\w+$/)) {
    // `list` is an expression, so we may not evaluate it more than once.
    if (where == 'RANDOM' || where == 'LAST') {
      // In these cases, `at` is implicit.  getIndex_() ignores its value.
      if (mode == 'SET') {
        var functionName = Blockly.Lua.provideFunction_(
            'list_set_' + where.toLowerCase(),
            ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, val)',
             '  t[' + getIndex_('t', where, at) + '] = val',
             'end']);
      } else {  // `mode` == 'INSERT'
        var functionName = Blockly.Lua.provideFunction_(
            'list_insert_' + where.toLowerCase(),
            ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, val)',
             '  table.insert(t, ' +
                 // LAST is a special case, because we want to insert
                 // *after* not *before*, the existing last element.
                 getIndex_('t', where, at) + (where == 'LAST' ? ' + 1' : '') +
                 ', val)',
             'end']);
      }
      var code = functionName + '(' + list + ', ' + value + ')\n';
      return code;
    } else {  // `where` = 'FROM_END'
      if (mode == 'SET') {
        var functionName = Blockly.Lua.provideFunction_(
            'list_set_from_end',
            ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
                 '(t, index, val)',
             '  t[#t + 1 - index] = val',
             'end']);
      } else {  // `mode` == 'INSERT'
        var functionName = Blockly.Lua.provideFunction_(
            'list_insert_from_end',
            ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
                 '(t, index, val)',
             '  table.insert(t, #t + 1 - index, val)',
             'end']);
      }
      var code = functionName + '(' + list + ', ' + at + ', ' + value + ')\n';
      return code;
    }
  } else {
    // It's okay to have multiple references to the list.
    if (mode == 'SET') {
      var code = list + '[' + getIndex_(list, where, at) + '] = ' + value;
    } else {  // `mode` == 'INSERT'
      // LAST is a special case, because we want to insert
      // *after* not *before*, the existing last element.
      var code = 'table.insert(' + list + ', ' +
          (getIndex_(list, where, at) + (where == 'LAST' ? ' + 1' : '')) +
          ', ' + value + ')';
    }
    return code + '\n';
  }
};

Blockly.Lua['lists_getSublist'] = function(block) {
  // Get sublist.
  var list = Blockly.Lua.valueToCode(block, 'LIST',
      Blockly.Lua.ORDER_HIGH) || '({})';
  var where1 = block.getFieldValue('WHERE1');
  var where2 = block.getFieldValue('WHERE2');
  var at1 = Blockly.Lua.valueToCode(block, 'AT1',
      Blockly.Lua.ORDER_ADDITIVE) || '1';
  var at2 = Blockly.Lua.valueToCode(block, 'AT2',
      Blockly.Lua.ORDER_ADDITIVE) || '1';
  var getIndex_ = Blockly.Lua.lists.getIndex_;

  var functionName = Blockly.Lua.provideFunction_(
      'list_sublist_' + Blockly.Lua.lists.gensym_(),
      ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(source)',
       '  local t = {}',
       '  local start = ' + getIndex_('source', where1, at1),
       '  local finish = ' + getIndex_('source', where2, at2),
       '  for i = start, finish do',
       '    table.insert(t, source[i])',
       '  end',
       '  return t',
       'end']);
  var code = functionName + '(' + list + ')';
  return [code, Blockly.Lua.ORDER_HIGH];
};

Blockly.Lua['lists_sort'] = function(block) {
  // Block for sorting a list.
  var listCode = Blockly.Lua.valueToCode(
      block, 'LIST', Blockly.Lua.ORDER_HIGH) || '({})';
  var direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1;
  var type = block.getFieldValue('TYPE');

  var functionName = Blockly.Lua.provideFunction_(
      'list_sort',
      ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + 
          '(list, typev, direction)',
       '  local t = {}',
       '  for n,v in pairs(list) do table.insert(t, v) end', // Shallow-copy.
       '  local compareFuncs = {',
       '    NUMERIC = function(a, b)',
       '      return (tonumber(tostring(a)) or 0)',
       '          < (tonumber(tostring(b)) or 0) end,',
       '    TEXT = function(a, b)',
       '      return tostring(a) < tostring(b) end,',
       '    IGNORE_CASE = function(a, b)',
       '      return string.lower(tostring(a)) < string.lower(tostring(b)) end',
       '  }',
       '  local compareTemp = compareFuncs[typev]',
       '  local compare = compareTemp',
       '  if direction == -1',
       '  then compare = function(a, b) return compareTemp(b, a) end',
       '  end',
       '  table.sort(t, compare)',
       '  return t',
       'end']);

  var code = functionName + 
      '(' + listCode + ',"' + type + '", ' + direction + ')';
  return [code, Blockly.Lua.ORDER_HIGH];  
};

Blockly.Lua['lists_split'] = function(block) {
  // Block for splitting text into a list, or joining a list into text.
  var value_input = Blockly.Lua.valueToCode(block, 'INPUT',
      Blockly.Lua.ORDER_NONE);
  var value_delim = Blockly.Lua.valueToCode(block, 'DELIM',
      Blockly.Lua.ORDER_NONE) || '\'\'';
  var mode = block.getFieldValue('MODE');
  var functionName;
  if (mode == 'SPLIT') {
    if (!value_input) {
      value_input = '\'\'';
    }
    functionName = Blockly.Lua.provideFunction_(
        'list_string_split',
        ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
             '(input, delim)',
         '  local t = {}',
         '  local pos = 1',
         '  while true do',
         '    next_delim = string.find(input, delim, pos)',
         '    if next_delim == nil then',
         '      table.insert(t, string.sub(input, pos))',
         '      break',
         '    else',
         '      table.insert(t, string.sub(input, pos, next_delim-1))',
         '      pos = next_delim + #delim',
         '    end',
         '  end',
         '  return t',
         'end']);
  } else if (mode == 'JOIN') {
    if (!value_input) {
      value_input = '({})';
    }
    functionName = 'table.concat';
  } else {
    throw 'Unknown mode: ' + mode;
  }
  var code = functionName + '(' + value_input + ', ' + value_delim + ')';
  return [code, Blockly.Lua.ORDER_HIGH];
};
