/* Copyright 2015 Samsung Electronics Co., Ltd.
 *
 * 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.
 */

#ifndef LIT_LITERAL_STORAGE_H
#define LIT_LITERAL_STORAGE_H

#include "ecma-globals.h"
#include "rcs-recordset.h"

class lit_literal_storage_t;
extern lit_literal_storage_t lit_storage;

/**
 * Charset record
 *
 * layout:
 * ------- header -----------------------
 * type (4 bits)
 * alignment (2 bits)
 * unused (2 bits)
 * hash (8 bits)
 * length (16 bits)
 * pointer to prev (16 bits)
 * ------- characters -------------------
 * ...
 * chars
 * ....
 * ------- alignment bytes --------------
 * unused bytes (their count is specified
 * by 'alignment' field in header)
 * --------------------------------------
 */
class lit_charset_record_t : public rcs_record_t
{
  friend class rcs_recordset_t;
  friend class lit_literal_storage_t;

public:
  /**
   * Calculate the size that record will occupy inside the storage
   *
   * @return size of the record in bytes
   */
  static size_t
  size (size_t size) /**< size of the charset buffer */
  {
    return JERRY_ALIGNUP (size + header_size (), RCS_DYN_STORAGE_LENGTH_UNIT);
  } /* size */

  /**
   * Get the size of the header part of the record
   *
   * @return size of the header in bytes
   */
  static size_t
  header_size ()
  {
    return _header_size;
  } /* header_size */

  /**
   * Get the count of the alignment bytes at the end of record.
   * These bytes are needed to align the record to RCS_DYN_STORAGE_ALIGNMENT.
   *
   * @return alignment bytes count (the value of the 'alignment' field in the header)
   */
  size_t
  get_alignment_bytes_count () const
  {
    return get_field (_alignment_field_pos, _alignment_field_width);
  } /* get_alignment_bytes_count */

  /**
   * Set the count of the alignment bytes at the end of record (the value of the 'alignment' field in the header)
   */
  void
  set_alignment_bytes_count (size_t count) /**< count of the alignment bytes */
  {
    JERRY_ASSERT (count <= RCS_DYN_STORAGE_ALIGNMENT);
    set_field (_alignment_field_pos, _alignment_field_width, count);
  } /* set_alignment_bytes_count */

  /**
   * Get hash value of the record's charset.
   *
   * @return hash value of the string (the value of the 'hash' field in the header)
   */
  ecma_string_hash_t
  get_hash () const
  {
    return (ecma_string_hash_t) get_field (_hash_field_pos, _hash_field_width);
  } /* get_hash */

  /**
   * Get the length of the string, which is contained inside the record
   *
   * @return length of the string (count of the ecma_char_t characters inside the charset)
   */
  ecma_length_t
  get_length () const
  {
    return (ecma_length_t) ((get_size () - header_size () - get_alignment_bytes_count ()) / sizeof (ecma_char_t));
  } /* get_length */

  /**
   * Get the size of the record
   *
   * @return size of the record in bytes
   */
  size_t
  get_size () const
  {
    return get_field (_length_field_pos, _length_field_width) * RCS_DYN_STORAGE_LENGTH_UNIT;
  } /* get_size */

  rcs_record_t *get_prev () const;

  ecma_length_t get_charset (ecma_char_t *buff, size_t size);

  int compare_zt (const ecma_char_t *, size_t);
  bool equal (lit_charset_record_t *);
  bool equal_zt (const ecma_char_t *);
  bool equal_non_zt (const ecma_char_t *, ecma_length_t);

private:
  /**
   * Set record's size (the value of the 'length' field in the header)
   */
  void
  set_size (size_t size) /**< size in bytes */
  {
    JERRY_ASSERT (JERRY_ALIGNUP (size, RCS_DYN_STORAGE_ALIGNMENT) == size);

    set_field (_length_field_pos, _length_field_width, size >> RCS_DYN_STORAGE_ALIGNMENT_LOG);
  } /* set_size */

  /**
   * Set record's hash (the value of the 'hash' field in the header)
   */
  void
  set_hash (ecma_string_hash_t hash) /**< hash value */
  {
    set_field (_hash_field_pos, _hash_field_width, hash);
  } /* set_hash */

  void set_prev (rcs_record_t *);

  void set_charset (const ecma_char_t *, size_t);

  /**
   * Offset and length of 'alignment' field, in bits
   */
  static const uint32_t _alignment_field_pos = _fields_offset_begin;
  static const uint32_t _alignment_field_width = 2u;

  /**
   * Offset and length of 'hash' field, in bits
   */
  static const uint32_t _hash_field_pos = _alignment_field_pos + _alignment_field_width + 2u;
  static const uint32_t _hash_field_width = 8u;

  /**
   * Offset and length of 'alignment' field, in bits
   */
  static const uint32_t _length_field_pos = _hash_field_pos + _hash_field_width;
  static const uint32_t _length_field_width = 16u;

  /**
   * Offset and length of 'prev' field, in bits
   */
  static const uint32_t _prev_field_pos = _length_field_pos + _length_field_width;
  static const uint32_t _prev_field_width = rcs_cpointer_t::bit_field_width;

  static const size_t _header_size = RCS_DYN_STORAGE_LENGTH_UNIT + RCS_DYN_STORAGE_LENGTH_UNIT / 2;
}; /* lit_charset_record_t */

/**
 * Magic string record
 * Doesn't hold any characters. Corresponding string is identified by its id.
 *
 * Layout:
 * ------- header -----------------------
 * type (4 bits)
 * magic string id  (12 bits)
 * pointer to prev (16 bits)
 * --------------------------------------
 */
class lit_magic_record_t : public rcs_record_t
{
  friend class rcs_recordset_t;
  friend class lit_literal_storage_t;

public:
  /**
   * Calculate the size that record will occupy inside the storage
   *
   * @return size of the record in bytes
   */
  static size_t size ()
  {
    return _size;
  } /* size */

  /**
   * Get the size of the header part of the record
   *
   * @return size of the header in bytes
   */
  static size_t header_size ()
  {
    return _size;
  } /* header_size */

  /**
   * Get the size of the record
   *
   * @return size of the record in bytes
   */
  size_t get_size () const
  {
    return _size;
  } /* get_size */

  /**
   * Get magic string id which is held by the record
   *
   * @return magic string id
   */
  template <typename magic_string_id_t>
  magic_string_id_t get_magic_str_id () const
  {
    uint32_t id = get_field (magic_field_pos, magic_field_width);
    // JERRY_ASSERT (id < ECMA_MAGIC_STRING__COUNT);
    return (magic_string_id_t) id;
  } /* get_magic_str_id */

private:
  /**
   * Set record's size (the value of the 'length' field in the header)
   */
  void set_size (size_t size) /**< size in bytes */
  {
    JERRY_ASSERT (size == get_size ());
  } /* set_size */

  template <typename magic_string_id_t>
  void set_magic_str_id (magic_string_id_t id)
  {
    set_field (magic_field_pos, magic_field_width, id);
  } /* set_magic_str_id */

  /**
   * Get previous record in the literal storage
   *
   * @return  pointer to the previous record relative to this record, or NULL if this is the first record in the
   *          storage
   */
  rcs_record_t *get_prev () const
  {
    return get_pointer (prev_field_pos, prev_field_width);
  } /* get_prev */

  /**
   * Set the pointer to the previous record inside the literal storage (sets 'prev' field in the header)
   */
  void set_prev (rcs_record_t *prev_rec_p)
  {
    set_pointer (prev_field_pos, prev_field_width, prev_rec_p);
  } /* set_prev */


  /**
   * Offset and length of 'magic string id' field, in bits
   */
  static const uint32_t magic_field_pos = _fields_offset_begin;
  static const uint32_t magic_field_width = 12u;

  /**
   * Offset and length of 'prev' field, in bits
   */
  static const uint32_t prev_field_pos = magic_field_pos + magic_field_width;
  static const uint32_t prev_field_width = rcs_cpointer_t::bit_field_width;

  static const size_t _size = RCS_DYN_STORAGE_LENGTH_UNIT;
}; /* lit_magic_record_t */

/**
 * Number record
 * Doesn't hold any characters, holds a number. Numbers from source code are represented as number literals.
 *
 * Layout:
 * ------- header -----------------------
 * type (4 bits)
 * magic string id  (12 bits)
 * pointer to prev (16 bits)
 * --------------------------------------
 */
class lit_number_record_t : public rcs_record_t
{
  friend class rcs_recordset_t;
  friend class lit_literal_storage_t;

public:

  static size_t
  size ()
  {
    return _size;
  } /* size */

  /**
   * Get the size of the header part of the record
   *
   * @return size of the header in bytes
   */
  static size_t
  header_size ()
  {
    return _header_size;
  } /* header_size */

  /**
   * Get the size of the record
   *
   * @return size of the record in bytes
   */
  size_t
  get_size () const
  {
    return _size;
  } /* get_size */

  /**
   * Get previous record in the literal storage
   *
   * @return  pointer to the previous record relative to this record, or NULL if this is the first record in the
   *          storage
   */
  rcs_record_t *
  get_prev () const
  {
    return get_pointer (prev_field_pos, prev_field_width);
  } /* get_prev */

  /**
   * Set the pointer to the previous record inside the literal storage (sets 'prev' field in the header)
   */
  void
  set_prev (rcs_record_t *prev_rec_p)
  {
    set_pointer (prev_field_pos, prev_field_width, prev_rec_p);
  } /* set_prev */

  /**
   * Get the number which is held by the record
   *
   * @return number
   */
  ecma_number_t
  get_number () const
  {
    rcs_record_iterator_t it ((rcs_recordset_t *)&lit_storage,
                              (rcs_record_t *)this);
    it.skip (header_size ());

    return it.read<ecma_number_t> ();
  } /* get_number */

private:
  /**
   * Set record's size (the value of the 'length' field in the header)
   */
  void
  set_size (size_t size) /**< size in bytes */
  {
    JERRY_ASSERT (size == get_size ());
  } /* set_size */

  /**
   * Offset and length of 'prev' field, in bits
   */
  static const uint32_t prev_field_pos = _fields_offset_begin + 12u;
  static const uint32_t prev_field_width = rcs_cpointer_t::bit_field_width;

  static const size_t _header_size = RCS_DYN_STORAGE_LENGTH_UNIT;
  static const size_t _size = _header_size + sizeof (ecma_number_t);
}; /* lit_number_record_t */

/**
 * Literal storage
 *
 * Represents flexible storage for the literals. The following records could be created inside the storage:
 * - charset literal (lit_charset_record_t)
 * - magic string literal (lit_magic_record_t)
 * - number literal (lit_number_record_t)
 */
class lit_literal_storage_t : public rcs_recordset_t
{
public:
  enum
  {
    LIT_STR = _first_type_id,
    LIT_MAGIC_STR,
    LIT_MAGIC_STR_EX,
    LIT_NUMBER
  };

  lit_charset_record_t *create_charset_record (const ecma_char_t *, size_t);
  lit_magic_record_t *create_magic_record (ecma_magic_string_id_t);
  lit_magic_record_t *create_magic_record_ex (ecma_magic_string_ex_id_t);
  lit_number_record_t *create_number_record (ecma_number_t);

  void dump ();

private:
  virtual rcs_record_t *get_prev (rcs_record_t *);
  virtual void set_prev (rcs_record_t *, rcs_record_t *);
  virtual size_t get_record_size (rcs_record_t *);
}; /* lit_literal_storage_t */

#define LIT_STR_T (lit_literal_storage_t::LIT_STR)
#define LIT_MAGIC_STR_T (lit_literal_storage_t::LIT_MAGIC_STR)
#define LIT_MAGIC_STR_EX_T (lit_literal_storage_t::LIT_MAGIC_STR_EX)
#define LIT_NUMBER_T (lit_literal_storage_t::LIT_NUMBER)

#endif /* LIT_LITERAL_STORAGE_H */
