/* Copyright JS Foundation and other contributors, http://js.foundation
 *
 * 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.
 */

#include "ecma-line-info.h"

#include "js-parser-internal.h"

#if JERRY_PARSER

#if JERRY_LINE_INFO

/* \addtogroup parser Parser
 * @{
 *
 * \addtogroup jsparser JavaScript
 * @{
 *
 * \addtogroup jsparser_line_info_create Create line info data
 * @{
 */

/*
 * The line-info data structure uses two number encodings:
 *
 * Vlq (variable length quantity):
 *    Each byte has 7 bit data and the highest bit is set for continuation.
 *    The format is big endian.
 *
 * Small:
 *    One byte can encode signed values between 127 and -126.
 *    Two byte can encode signed values between 319 and -318.
 *    Large values are encoded with vlq with a prefix byte.
 *
 * The line-info data structure is a sequence of chunks:
 *
 *  +------+--------------+------------+----------------+
 *  | Line | StreamLength | StreamData | [ByteCodeSize] |
 *  +------+--------------+------------+----------------+
 *
 *  Line [Vlq encoding]:
 *      Specifies the start line of this chunk, relative to its previous value.
 *      The starting column is always ECMA_LINE_INFO_COLUMN_DEFAULT
 *
 *  StreamLength [uint8_t]:
 *      Length of the StreamData in bytes minus ECMA_LINE_INFO_STREAM_SIZE_MIN.
 *      The 0 value represents the last chunk, which size is not specified
 *      (Can be less than ECMA_LINE_INFO_STREAM_SIZE_MIN).
 *
 *  StreamData [sequence of bytes]:
 *      Sequence of the following items:
 *
 *      +-----------+--------+--------+
 *      | EndOffset | [Line] | Column |
 *      +-----------+--------+--------+
 *
 *      EndOffset [Small encoding]:
 *          Specifies the EndOffset in the byte code, relative to the previous EndOffset.
 *          The range of byte codes corresponding to the line/column position of this item
 *          is between the EndOffset of the previous item (inclusive) and the EndOffset
 *          of this item (exclusive). The last end offset of a stream is always 0, which
 *          represents an unterminated range.
 *
 *      Line [Small encoding] [Optional]:
 *          If bit 1 of end offset is set, this specifies the line position of this item,
 *          relative to the previous line position, and the column position is set to
 *          ECMA_LINE_INFO_COLUMN_DEFAULT.
 *
 *      Column [Small encoding]:
 *          Specifies the current column position relative to the previous column position.
 *
 *  ByteCodeSize [Vlq encoding] [Optional]:
 *      If StreamLength is not 0, this specifies the byte code size of the whole range.
 *      This value can be used to skip the byte codes which line info is stored
 *      in this chunk. This information is not available for the last chunk.
 */

/**
 * Maximum number of bytes requires to encode a number.
 */
#define PARSER_LINE_INFO_BUFFER_MAX_SIZE 6

/**
 * Stream generation ends after this size is reached,
 * since there might be not enough place for the next item.
 */
#define PARSER_LINE_INFO_STREAM_SIZE_LIMIT \
  (ECMA_LINE_INFO_STREAM_SIZE_MIN + UINT8_MAX - ((2 * PARSER_LINE_INFO_BUFFER_MAX_SIZE) + 1))

/**
 * Page size of line info pages excluding the first one.
 */
#define PARSER_LINE_INFO_PAGE_SIZE (sizeof (parser_mem_page_t *) + PARSER_STACK_PAGE_SIZE)

/**
 * Page size of the first line info page.
 */
#define PARSER_LINE_INFO_FIRST_PAGE_SIZE (sizeof (parser_line_info_data_t) + PARSER_LINE_INFO_PAGE_SIZE)

/**
 * Get memory data of the first page.
 */
#define PARSER_LINE_INFO_GET_FIRST_PAGE(line_info_p) (((parser_mem_page_t *) ((line_info_p) + 1)))

/**
 * Free line info temporary data collected during parsing.
 */
void
parser_line_info_free (parser_line_info_data_t *line_info_p)
{
  if (line_info_p == NULL)
  {
    return;
  }

  parser_mem_page_t *current_page_p = PARSER_LINE_INFO_GET_FIRST_PAGE (line_info_p)->next_p;
  parser_free (line_info_p, PARSER_LINE_INFO_FIRST_PAGE_SIZE);

  while (current_page_p != NULL)
  {
    parser_mem_page_t *next_p = current_page_p->next_p;

    parser_free (current_page_p, PARSER_LINE_INFO_PAGE_SIZE);
    current_page_p = next_p;
  }
} /* parser_line_info_free */

/**
 * Encodes an uint32_t number into a buffer. Numbers expected to be larger values.
 *
 * @return the number of bytes written to the buffer
 */
static uint32_t
parser_line_info_encode_vlq (uint8_t *buffer_p, /**< target buffer */
                             uint32_t value) /**< encoded value */
{
  if (value <= ECMA_LINE_INFO_VLQ_MASK)
  {
    *buffer_p = (uint8_t) value;
    return 1;
  }

  uint32_t length = 0;
  uint32_t current_value = value;

  do
  {
    current_value >>= ECMA_LINE_INFO_VLQ_SHIFT;
    length++;
  } while (current_value > 0);

  buffer_p += length;

  do
  {
    *(--buffer_p) = (uint8_t) (value | ECMA_LINE_INFO_VLQ_CONTINUE);
    value >>= ECMA_LINE_INFO_VLQ_SHIFT;
  } while (value > 0);

  buffer_p[length - 1] &= ECMA_LINE_INFO_VLQ_MASK;
  return length;
} /* parser_line_info_encode_vlq */

/**
 * Encodes an uint32_t number into a buffer. Numbers expected to be smaller values.
 *
 * @return the number of bytes written to the buffer
 */
static uint32_t
parser_line_info_encode_small (uint8_t *buffer_p, /**< target buffer */
                               uint32_t value) /**< encoded value */
{
  if (JERRY_LIKELY (value < ECMA_LINE_INFO_ENCODE_TWO_BYTE_MIN))
  {
    buffer_p[0] = (uint8_t) value;
    return 1;
  }

  if (JERRY_LIKELY (value < ECMA_LINE_INFO_ENCODE_VLQ_MIN))
  {
    buffer_p[0] = ECMA_LINE_INFO_ENCODE_TWO_BYTE;
    buffer_p[1] = (uint8_t) (value - ECMA_LINE_INFO_ENCODE_TWO_BYTE_MIN);
    return 2;
  }

  *buffer_p++ = ECMA_LINE_INFO_ENCODE_VLQ;
  return parser_line_info_encode_vlq (buffer_p, value - ECMA_LINE_INFO_ENCODE_VLQ_MIN) + 1;
} /* parser_line_info_encode_small */

/**
 * Encodes the difference between two values.
 *
 * @return encoded difference
 */
static inline uint32_t JERRY_ATTR_ALWAYS_INLINE
parser_line_info_difference_get (uint32_t current_value, /**< current value */
                                 uint32_t prev_value) /**< previous value */
{
  uint32_t result = current_value - prev_value - 1;

  if (result <= (UINT32_MAX >> 1))
  {
    return (result << 1) | ECMA_LINE_INFO_INCREASE;
  }

  return ((UINT32_MAX - result) << 1) | ECMA_LINE_INFO_DECREASE;
} /* parser_line_info_difference_get */

/**
 * Appends a value at the end of the line info stream.
 */
static void
parser_line_info_append_number (parser_context_t *context_p, /**< context */
                                uint32_t value) /**< value to be encoded */
{
  parser_line_info_data_t *line_info_p = context_p->line_info_p;
  uint8_t buffer[PARSER_LINE_INFO_BUFFER_MAX_SIZE];

  JERRY_ASSERT (line_info_p != NULL);

  uint32_t length = parser_line_info_encode_vlq (buffer, value);
  uint8_t offset = line_info_p->last_page_p->bytes[0];

  if (offset + length <= PARSER_STACK_PAGE_SIZE)
  {
    memcpy (line_info_p->last_page_p->bytes + offset, buffer, length);

    line_info_p->last_page_p->bytes[0] = (uint8_t) (length + offset);
    return;
  }

  parser_mem_page_t *new_page_p;
  new_page_p = (parser_mem_page_t *) parser_malloc (context_p, PARSER_LINE_INFO_PAGE_SIZE);

  new_page_p->next_p = NULL;

  line_info_p->last_page_p->next_p = new_page_p;
  line_info_p->last_page_p = new_page_p;

  new_page_p->bytes[0] = (uint8_t) (length + 1);
  memcpy (new_page_p->bytes + 1, buffer, length);
} /* parser_line_info_append_number */

/**
 * Updates the current line information data.
 */
void
parser_line_info_append (parser_context_t *context_p, /**< context */
                         parser_line_counter_t line, /**< line */
                         parser_line_counter_t column) /**< column */
{
  parser_line_info_data_t *line_info_p = context_p->line_info_p;
  uint32_t value;

  if (line_info_p != NULL)
  {
    if (line_info_p->byte_code_position == context_p->byte_code_size
        || (line_info_p->line == line && line_info_p->column == column))
    {
      return;
    }

    /* Sets ECMA_LINE_INFO_HAS_LINE bit. */
    value = (uint32_t) (line != line_info_p->line);
  }
  else
  {
    line_info_p = (parser_line_info_data_t *) parser_malloc (context_p, PARSER_LINE_INFO_FIRST_PAGE_SIZE);
    context_p->line_info_p = line_info_p;

    parser_mem_page_t *page_p = PARSER_LINE_INFO_GET_FIRST_PAGE (line_info_p);
    page_p->next_p = NULL;
    page_p->bytes[0] = 1;

    line_info_p->last_page_p = page_p;
    line_info_p->byte_code_position = 0;
    line_info_p->line = 1;
    line_info_p->column = 1;

    /* Sets ECMA_LINE_INFO_HAS_LINE bit. */
    value = (uint32_t) (line != 1);
  }

  value |= ((context_p->byte_code_size - line_info_p->byte_code_position) << 1);

  parser_line_info_append_number (context_p, value);
  line_info_p->byte_code_position = context_p->byte_code_size;

  if (value & ECMA_LINE_INFO_HAS_LINE)
  {
    value = parser_line_info_difference_get (line, line_info_p->line);
    parser_line_info_append_number (context_p, value);
    line_info_p->line = line;
  }

  value = parser_line_info_difference_get (column, line_info_p->column);
  parser_line_info_append_number (context_p, value);
  line_info_p->column = column;
} /* parser_line_info_append */

/**
 * Line info iterator structure
 */
typedef struct
{
  parser_mem_page_t *current_page_p; /**< current page */
  uint32_t offset; /**< current offset */
} parser_line_info_iterator_t;

/**
 * Decodes the next value from the iterator stream
 */
static uint32_t
parser_line_info_iterator_get (parser_line_info_iterator_t *iterator_p) /**< iterator */
{
  uint8_t *source_p = iterator_p->current_page_p->bytes + iterator_p->offset;
  uint32_t result = ecma_line_info_decode_vlq (&source_p);

  iterator_p->offset = (uint32_t) (source_p - iterator_p->current_page_p->bytes);

  JERRY_ASSERT (iterator_p->offset <= iterator_p->current_page_p->bytes[0]);

  if (iterator_p->offset < iterator_p->current_page_p->bytes[0])
  {
    return result;
  }

  iterator_p->current_page_p = iterator_p->current_page_p->next_p;
  iterator_p->offset = 1;
  return result;
} /* parser_line_info_iterator_get */

/**
 * Generate line info data
 *
 * @return generated line info data
 */
uint8_t *
parser_line_info_generate (parser_context_t *context_p) /**< context */
{
  parser_line_info_iterator_t iterator;
  uint8_t *line_info_p = NULL;
  uint8_t *dst_p = NULL;
  uint32_t total_length = 0;
  uint32_t total_length_size = 0;

  while (true)
  {
    /* The following code runs twice: first the size of the data,
     * is computed and the data is generated during the second run.
     * Note: line_info_p is NULL during the first run. */
    parser_mem_page_t *iterator_byte_code_page_p = context_p->byte_code.first_p;
    uint32_t iterator_byte_code_page_offset = 0;
    uint32_t iterator_byte_code_base = 0;
    uint32_t iterator_last_byte_code_offset = UINT32_MAX;
    uint32_t iterator_prev_line = 0;
    uint32_t iterator_prev_column = 0;
    uint32_t iterator_line = 1;
    uint32_t iterator_column = 1;
    uint8_t block_buffer[PARSER_LINE_INFO_BUFFER_MAX_SIZE];
    uint8_t line_column_buffer[PARSER_LINE_INFO_BUFFER_MAX_SIZE * 2];
    uint8_t *block_size_p = NULL;
    uint32_t block_byte_code_offset = 0;
    uint32_t block_prev_line = 1;
    uint32_t stream_byte_code_offset = 0;
    uint32_t stream_current_line = 1;
    uint32_t stream_current_column = ECMA_LINE_INFO_COLUMN_DEFAULT;
    uint32_t stream_prev_line = 1;
    uint32_t stream_prev_column = ECMA_LINE_INFO_COLUMN_DEFAULT;
    uint32_t stream_size = 0;
    uint32_t stream_value_count = 0;
    uint32_t value;

    iterator.current_page_p = PARSER_LINE_INFO_GET_FIRST_PAGE (context_p->line_info_p);
    iterator.offset = 1;

    do
    {
      /* Decode line information generated during parsing. */
      value = parser_line_info_iterator_get (&iterator);
      iterator_byte_code_page_offset += (value >> 1);

      if (value & 0x1)
      {
        value = parser_line_info_iterator_get (&iterator);
        JERRY_ASSERT (value != ((0 << 1) | ECMA_LINE_INFO_DECREASE));
        iterator_line = ecma_line_info_difference_update (iterator_line, value);
      }

      value = parser_line_info_iterator_get (&iterator);
      iterator_column = ecma_line_info_difference_update (iterator_column, value);

      while (iterator_byte_code_page_offset >= PARSER_CBC_STREAM_PAGE_SIZE)
      {
        uint8_t relative_offset = iterator_byte_code_page_p->bytes[PARSER_CBC_STREAM_PAGE_SIZE - 1];
        iterator_byte_code_base += relative_offset & CBC_LOWER_SEVEN_BIT_MASK;
        iterator_byte_code_page_offset -= PARSER_CBC_STREAM_PAGE_SIZE;
        iterator_byte_code_page_p = iterator_byte_code_page_p->next_p;
      }

      uint32_t iterator_byte_code_offset = iterator_byte_code_base;

      if (iterator_byte_code_page_offset > 0)
      {
        uint8_t relative_offset = iterator_byte_code_page_p->bytes[iterator_byte_code_page_offset - 1];
        iterator_byte_code_offset += relative_offset & CBC_LOWER_SEVEN_BIT_MASK;
      }

      /* Skip those line/column pairs which byte code was discarded during post processing
       * or does not change line/column (this is possible when multiple skips occures). */
      if (iterator_byte_code_offset == iterator_last_byte_code_offset
          || (iterator_line == iterator_prev_line && iterator_column == iterator_prev_column))
      {
        continue;
      }

      iterator_prev_line = iterator_line;
      iterator_prev_column = iterator_column;
      iterator_last_byte_code_offset = iterator_byte_code_offset;

      if (block_size_p != NULL)
      {
        /* Sets ECMA_LINE_INFO_HAS_LINE bit. */
        value = (((iterator_byte_code_offset - stream_byte_code_offset) << 1)
                 | (uint32_t) (stream_prev_line != stream_current_line));

        uint32_t line_column_size = 0;
        uint32_t offset_size = parser_line_info_encode_small (block_buffer, value);
        stream_byte_code_offset = iterator_byte_code_offset;

        if (value & ECMA_LINE_INFO_HAS_LINE)
        {
          value = parser_line_info_difference_get (stream_current_line, stream_prev_line);
          line_column_size = parser_line_info_encode_small (line_column_buffer, value);
          stream_prev_line = stream_current_line;
          stream_prev_column = ECMA_LINE_INFO_COLUMN_DEFAULT;
        }

        value = parser_line_info_difference_get (stream_current_column, stream_prev_column);
        line_column_size += parser_line_info_encode_small (line_column_buffer + line_column_size, value);

        stream_prev_column = stream_current_column;
        stream_current_line = iterator_line;
        stream_current_column = iterator_column;

        stream_value_count++;

        if (stream_value_count < ECMA_LINE_INFO_STREAM_VALUE_COUNT_MAX
            && (stream_size + offset_size + line_column_size <= PARSER_LINE_INFO_STREAM_SIZE_LIMIT))
        {
          stream_size += offset_size + line_column_size;

          if (line_info_p != NULL)
          {
            memcpy (dst_p, block_buffer, offset_size);
            dst_p += offset_size;
            memcpy (dst_p, line_column_buffer, line_column_size);
            dst_p += line_column_size;
          }
          continue;
        }

        /* Finalize the current chunk. The size of EndOffset is always 1. */
        stream_size += 1 + line_column_size;

        JERRY_ASSERT (stream_size > ECMA_LINE_INFO_STREAM_SIZE_MIN
                      && (stream_size - ECMA_LINE_INFO_STREAM_SIZE_MIN) <= UINT8_MAX);

        if (line_info_p != NULL)
        {
          *block_size_p = (uint8_t) (stream_size - ECMA_LINE_INFO_STREAM_SIZE_MIN);
          /* Set EndOffset to 0 and copy the has_line bit. */
          *dst_p++ = (uint8_t) (block_buffer[0] & ECMA_LINE_INFO_HAS_LINE);
          memcpy (dst_p, line_column_buffer, line_column_size);
          dst_p += line_column_size;
        }
        else
        {
          total_length += stream_size;
          dst_p = block_buffer;
        }

        uint32_t byte_code_diff = iterator_last_byte_code_offset - block_byte_code_offset;
        dst_p += parser_line_info_encode_vlq (dst_p, byte_code_diff);
        block_byte_code_offset = iterator_last_byte_code_offset;

        if (line_info_p == NULL)
        {
          total_length += (uint32_t) (dst_p - block_buffer);
        }
      }

      /* Start a new chunk. */
      if (line_info_p == NULL)
      {
        dst_p = block_buffer;
      }

      value = parser_line_info_difference_get (iterator_line, block_prev_line);

      dst_p += parser_line_info_encode_vlq (dst_p, value);
      block_size_p = dst_p;
      dst_p++;

      if (line_info_p == NULL)
      {
        total_length += (uint32_t) (dst_p - block_buffer);
      }

      block_prev_line = iterator_line;
      stream_current_line = iterator_line;
      stream_current_column = iterator_column;
      stream_prev_line = iterator_line;
      stream_prev_column = ECMA_LINE_INFO_COLUMN_DEFAULT;
      stream_size = 0;
      stream_value_count = 0;
    } while (iterator.current_page_p != NULL);

    value = (stream_prev_line != stream_current_line);

    /* Finalize the last stream */
    if (line_info_p == NULL)
    {
      dst_p = line_column_buffer;
      total_length += stream_size + 1;
    }
    else
    {
      *block_size_p = 0;
      /* Small encoded value of has_line bit. */
      *dst_p++ = (uint8_t) value;
    }

    if (value)
    {
      value = parser_line_info_difference_get (stream_current_line, stream_prev_line);
      dst_p += parser_line_info_encode_small (dst_p, value);
      stream_prev_column = ECMA_LINE_INFO_COLUMN_DEFAULT;
    }

    value = parser_line_info_difference_get (stream_current_column, stream_prev_column);
    dst_p += parser_line_info_encode_small (dst_p, value);

    if (line_info_p == NULL)
    {
      total_length += (uint32_t) (dst_p - line_column_buffer);
    }

    if (line_info_p != NULL)
    {
      break;
    }

    total_length_size = parser_line_info_encode_vlq (block_buffer, total_length);

    /* TODO: Support allocation fail. */
    line_info_p = (uint8_t *) jmem_heap_alloc_block (total_length + total_length_size);
    dst_p = line_info_p + parser_line_info_encode_vlq (line_info_p, total_length);
  }

  JERRY_ASSERT (line_info_p + total_length_size + total_length == dst_p);

#if JERRY_PARSER_DUMP_BYTE_CODE
  if (context_p->is_show_opcodes)
  {
    ecma_line_info_dump (line_info_p);
  }
#endif /* JERRY_PARSER_DUMP_BYTE_CODE */

  return line_info_p;
} /* parser_line_info_generate */

#endif /* JERRY_LINE_INFO */

#endif /* JERRY_PARSER */

/**
 * @}
 * @}
 * @}
 */
