/**
 * Helper function to calculate timezone offset.
 *
 * See also:
 *          ECMA-262 v5, 15.9.5.26
 *
 * @return timezone offset
 */
inline ecma_number_t __attr_always_inline___
ecma_date_timezone_offset (ecma_number_t time) /**< time value */
{
  JERRY_ASSERT (!ecma_number_is_nan (time));

  return (-ecma_date_local_time_zone (time)) / ECMA_DATE_MS_PER_MINUTE;
} /* ecma_date_timezone_offset */
/**
 * Dispatcher of the built-in's routines
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_builtin_date_prototype_dispatch_routine (uint16_t builtin_routine_id, /**< built-in wide routine
                                                                            *   identifier */
                                              ecma_value_t this_arg, /**< 'this' argument value */
                                              const ecma_value_t arguments_list[], /**< list of arguments
                                                                                    *   passed to routine */
                                              ecma_length_t arguments_number) /**< length of arguments' list */
{
  if (JERRY_UNLIKELY (builtin_routine_id == ECMA_DATE_PROTOTYPE_TO_JSON))
  {
    return ecma_builtin_date_prototype_to_json (this_arg);
  }

  if (!ecma_is_value_object (this_arg)
      || !ecma_object_class_is (ecma_get_object_from_value (this_arg), LIT_MAGIC_STRING_DATE_UL))
  {
    return ecma_raise_type_error (ECMA_ERR_MSG ("Date object expected"));
  }

  ecma_object_t *object_p = ecma_get_object_from_value (this_arg);

  ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p;
  ecma_number_t *prim_value_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_number_t,
                                                                 ext_object_p->u.class_prop.u.value);

  if (builtin_routine_id == ECMA_DATE_PROTOTYPE_GET_TIME)
  {
    return ecma_make_number_value (*prim_value_p);
  }

  if (builtin_routine_id == ECMA_DATE_PROTOTYPE_SET_TIME)
  {
    ecma_value_t time = (arguments_number >= 1 ? arguments_list[0]
                                               : ECMA_VALUE_UNDEFINED);

    ecma_value_t ret_value = ECMA_VALUE_EMPTY;

    /* 1. */
    ECMA_OP_TO_NUMBER_TRY_CATCH (time_num, time, ret_value);
    *prim_value_p = ecma_date_time_clip (time_num);

    ret_value = ecma_make_number_value (time_num);
    ECMA_OP_TO_NUMBER_FINALIZE (time_num);

    return ret_value;
  }

  if (builtin_routine_id <= ECMA_DATE_PROTOTYPE_SET_UTC_MILLISECONDS)
  {
    ecma_number_t this_num = *prim_value_p;

    if (!BUILTIN_DATE_FUNCTION_IS_UTC (builtin_routine_id))
    {
      this_num += ecma_date_local_time_zone (this_num);
    }

    if (builtin_routine_id <= ECMA_DATE_PROTOTYPE_GET_UTC_TIMEZONE_OFFSET)
    {
      return ecma_builtin_date_prototype_dispatch_get (builtin_routine_id, this_num);
    }

    return ecma_builtin_date_prototype_dispatch_set (builtin_routine_id,
                                                     ext_object_p,
                                                     this_num,
                                                     arguments_list,
                                                     arguments_number);
  }

  if (builtin_routine_id == ECMA_DATE_PROTOTYPE_TO_ISO_STRING)
  {
    if (ecma_number_is_nan (*prim_value_p) || ecma_number_is_infinity (*prim_value_p))
    {
      return ecma_raise_range_error (ECMA_ERR_MSG ("Date must be a finite number."));
    }

    return ecma_date_value_to_iso_string (*prim_value_p);
  }

  if (ecma_number_is_nan (*prim_value_p))
  {
    return ecma_make_magic_string_value (LIT_MAGIC_STRING_INVALID_DATE_UL);
  }

  switch (builtin_routine_id)
  {
    case ECMA_DATE_PROTOTYPE_TO_STRING:
    {
      return ecma_date_value_to_string (*prim_value_p);
    }
    case ECMA_DATE_PROTOTYPE_TO_DATE_STRING:
    {
      return ecma_date_value_to_date_string (*prim_value_p);
    }
    case ECMA_DATE_PROTOTYPE_TO_TIME_STRING:
    {
      return ecma_date_value_to_time_string (*prim_value_p);
    }
    default:
    {
      JERRY_ASSERT (builtin_routine_id == ECMA_DATE_PROTOTYPE_TO_UTC_STRING);
      return ecma_date_value_to_utc_string (*prim_value_p);
    }
  }
} /* ecma_builtin_date_prototype_dispatch_routine */
/**
 * Common function to create a time zone specific string from a numeric value.
 *
 * Used by:
 *        - The Date routine.
 *        - The Date.prototype.toString routine.
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_date_value_to_string (ecma_number_t datetime_number) /**< datetime */
{
  datetime_number += ecma_date_local_time_zone (datetime_number);
  return ecma_date_to_string_format (datetime_number, "$W $M $D $Y $h:$m:$s GMT$z:$Z");
} /* ecma_date_value_to_string */
/**
 * Common function to convert date to string.
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_date_to_string_format (ecma_number_t datetime_number, /**< datetime */
                            const char *format_p) /**< format buffer */
{
  const char *day_names_p[8] =
  {
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
  };

  const char *month_names_p[13] =
  {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };

  const uint32_t date_buffer_length = 34;
  lit_utf8_byte_t date_buffer[date_buffer_length];

  lit_utf8_byte_t *dest_p = date_buffer;

  while (*format_p != LIT_CHAR_NULL)
  {
    if (*format_p != LIT_CHAR_DOLLAR_SIGN)
    {
      *dest_p++ = (lit_utf8_byte_t) *format_p++;
      continue;
    }

    format_p++;

    const char *str_p = NULL;
    int32_t number = 0;
    int32_t number_length = 0;

    switch (*format_p)
    {
      case LIT_CHAR_UPPERCASE_Y: /* Year. */
      {
        number = (int32_t) ecma_date_year_from_time (datetime_number);
        number_length = 4;
        break;
      }
      case LIT_CHAR_UPPERCASE_M: /* Month. */
      {
        int32_t month = (int32_t) ecma_date_month_from_time (datetime_number);

        JERRY_ASSERT (month >= 0 && month <= 11);

        str_p = month_names_p[month];
        break;
      }
      case LIT_CHAR_UPPERCASE_O: /* Month as number. */
      {
        /* The 'ecma_date_month_from_time' (ECMA 262 v5, 15.9.1.4) returns a
         * number from 0 to 11, but we have to print the month from 1 to 12
         * for ISO 8601 standard (ECMA 262 v5, 15.9.1.15). */
        number = ((int32_t) ecma_date_month_from_time (datetime_number)) + 1;
        number_length = 2;
        break;
      }
      case LIT_CHAR_UPPERCASE_D: /* Day. */
      {
        number = (int32_t) ecma_date_date_from_time (datetime_number);
        number_length = 2;
        break;
      }
      case LIT_CHAR_UPPERCASE_W: /* Day of week. */
      {
        int32_t day = (int32_t) ecma_date_week_day (datetime_number);

        JERRY_ASSERT (day >= 0 && day <= 6);

        str_p = day_names_p[day];
        break;
      }
      case LIT_CHAR_LOWERCASE_H: /* Hour. */
      {
        number = (int32_t) ecma_date_hour_from_time (datetime_number);
        number_length = 2;
        break;
      }
      case LIT_CHAR_LOWERCASE_M: /* Minutes. */
      {
        number = (int32_t) ecma_date_min_from_time (datetime_number);
        number_length = 2;
        break;
      }
      case LIT_CHAR_LOWERCASE_S: /* Seconds. */
      {
        number = (int32_t) ecma_date_sec_from_time (datetime_number);
        number_length = 2;
        break;
      }
      case LIT_CHAR_LOWERCASE_I: /* Milliseconds. */
      {
        number = (int32_t) ecma_date_ms_from_time (datetime_number);
        number_length = 3;
        break;
      }
      case LIT_CHAR_LOWERCASE_Z: /* Time zone minutes part. */
      {
        int32_t time_zone = (int32_t) ecma_date_local_time_zone (datetime_number);

        if (time_zone >= 0)
        {
          *dest_p++ = LIT_CHAR_PLUS;
        }
        else
        {
          *dest_p++ = LIT_CHAR_MINUS;
          time_zone = -time_zone;
        }

        number = time_zone / (int32_t) ECMA_DATE_MS_PER_HOUR;
        number_length = 2;
        break;
      }
      case LIT_CHAR_UPPERCASE_Z: /* Time zone seconds part. */
      {
        int32_t time_zone = (int32_t) ecma_date_local_time_zone (datetime_number);

        if (time_zone < 0)
        {
          time_zone = -time_zone;
        }

        number = time_zone % (int32_t) ECMA_DATE_MS_PER_HOUR;
        number_length = 2;
        break;
      }
      default:
      {
        JERRY_UNREACHABLE ();
        break;
      }
    }

    format_p++;

    if (str_p != NULL)
    {
      /* Print string values. */
      do
      {
        *dest_p++ = (lit_utf8_byte_t) *str_p++;
      }
      while (*str_p != LIT_CHAR_NULL);

      continue;
    }

    /* Print right aligned number values. */
    JERRY_ASSERT (number_length > 0);

    dest_p += number_length;
    lit_utf8_byte_t *buffer_p = dest_p;

    do
    {
      buffer_p--;
      *buffer_p = (lit_utf8_byte_t) ((number % 10) + (int32_t) LIT_CHAR_0);
      number /= 10;
    }
    while (--number_length);
  }

  JERRY_ASSERT (dest_p <= date_buffer + date_buffer_length);

  return ecma_make_string_value (ecma_new_ecma_string_from_utf8 (date_buffer,
                                                                 (lit_utf8_size_t) (dest_p - date_buffer)));
} /* ecma_date_to_string_format */