/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef builtin_temporal_Calendar_h
#define builtin_temporal_Calendar_h

#include <initializer_list>
#include <stdint.h>

#include "builtin/temporal/Wrapped.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "vm/NativeObject.h"

namespace js {
struct ClassSpec;
class JSStringBuilder;
class PlainObject;
}  // namespace js

namespace js::temporal {

class CalendarObject : public NativeObject {
 public:
  static const JSClass class_;
  static const JSClass& protoClass_;

  static constexpr uint32_t IDENTIFIER_SLOT = 0;
  static constexpr uint32_t SLOT_COUNT = 1;

  JSString* identifier() const {
    return getFixedSlot(IDENTIFIER_SLOT).toString();
  }

 private:
  static const ClassSpec classSpec_;
};

struct Duration;
struct PlainDate;
struct PlainDateTime;
class DurationObject;
class PlainDateObject;
class PlainMonthDayObject;
class PlainYearMonthObject;
enum class CalendarOption;
enum class TemporalUnit;

/**
 * ISODaysInYear ( year )
 */
int32_t ISODaysInYear(int32_t year);

/**
 * ISODaysInMonth ( year, month )
 */
int32_t ISODaysInMonth(int32_t year, int32_t month);

/**
 * ISODaysInMonth ( year, month )
 */
int32_t ISODaysInMonth(double year, int32_t month);

/**
 * ToISODayOfYear ( year, month, day )
 */
int32_t ToISODayOfYear(const PlainDate& date);

/**
 * 21.4.1.12 MakeDay ( year, month, date )
 */
int32_t MakeDay(const PlainDate& date);

/**
 * 21.4.1.13 MakeDate ( day, time )
 */
int64_t MakeDate(const PlainDateTime& dateTime);

/**
 * 21.4.1.13 MakeDate ( day, time )
 */
int64_t MakeDate(int32_t year, int32_t month, int32_t day);

/**
 * GetISO8601Calendar ( )
 */
CalendarObject* GetISO8601Calendar(JSContext* cx);

/**
 * ToTemporalCalendar ( temporalCalendarLike )
 */
JSObject* ToTemporalCalendar(JSContext* cx,
                             JS::Handle<JS::Value> temporalCalendarLike);

/**
 * ToTemporalCalendarWithISODefault ( temporalCalendarLike )
 */
JSObject* ToTemporalCalendarWithISODefault(
    JSContext* cx, JS::Handle<JS::Value> temporalCalendarLike);

/**
 * GetTemporalCalendarWithISODefault ( item )
 */
JSObject* GetTemporalCalendarWithISODefault(JSContext* cx,
                                            JS::Handle<JSObject*> item);

enum class CalendarField {
  Year,
  Month,
  MonthCode,
  Day,
  Hour,
  Minute,
  Second,
  Millisecond,
  Microsecond,
  Nanosecond,
};

/**
 * CalendarFields ( calendar, fieldNames )
 */
bool CalendarFields(
    JSContext* cx, JS::Handle<JSObject*> calendar,
    std::initializer_list<CalendarField> fieldNames,
    JS::MutableHandle<JS::StackGCVector<JS::PropertyKey>> result);

/**
 * CalendarMergeFields ( calendar, fields, additionalFields )
 */
JSObject* CalendarMergeFields(JSContext* cx, JS::Handle<JSObject*> calendar,
                              JS::Handle<PlainObject*> fields,
                              JS::Handle<PlainObject*> additionalFields);

/**
 * CalendarDateAdd ( calendar, date, duration [ , options [ , dateAdd ] ] )
 */
Wrapped<PlainDateObject*> CalendarDateAdd(
    JSContext* cx, JS::Handle<JSObject*> calendar,
    JS::Handle<Wrapped<PlainDateObject*>> date,
    JS::Handle<Wrapped<DurationObject*>> duration,
    JS::Handle<JS::Value> dateAdd);

/**
 * CalendarDateAdd ( calendar, date, duration [ , options [ , dateAdd ] ] )
 */
Wrapped<PlainDateObject*> CalendarDateAdd(
    JSContext* cx, JS::Handle<JSObject*> calendar,
    JS::Handle<Wrapped<PlainDateObject*>> date,
    JS::Handle<Wrapped<DurationObject*>> duration,
    JS::Handle<JSObject*> options);

/**
 * CalendarDateAdd ( calendar, date, duration [ , options [ , dateAdd ] ] )
 */
bool CalendarDateAdd(JSContext* cx, JS::Handle<JSObject*> calendar,
                     JS::Handle<PlainDateObject*> date,
                     JS::Handle<Wrapped<DurationObject*>> duration,
                     JS::Handle<JSObject*> options, PlainDate* result);

/**
 * CalendarDateAdd ( calendar, date, duration [ , options [ , dateAdd ] ] )
 */
bool CalendarDateAdd(JSContext* cx, JS::Handle<JSObject*> calendar,
                     JS::Handle<PlainDateObject*> date,
                     JS::Handle<Wrapped<DurationObject*>> duration,
                     PlainDate* result);

/**
 * CalendarDateAdd ( calendar, date, duration [ , options [ , dateAdd ] ] )
 */
bool CalendarDateAdd(JSContext* cx, JS::Handle<JSObject*> calendar,
                     JS::Handle<Wrapped<PlainDateObject*>> date,
                     JS::Handle<Wrapped<DurationObject*>> duration,
                     JS::Handle<JS::Value> dateAdd, PlainDate* result);

/**
 * CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] )
 */
bool CalendarDateUntil(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<Wrapped<PlainDateObject*>> one,
                       JS::Handle<Wrapped<PlainDateObject*>> two,
                       JS::Handle<JSObject*> options,
                       JS::Handle<JS::Value> dateUntil, Duration* result);

/**
 * CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] )
 */
bool CalendarDateUntil(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<Wrapped<PlainDateObject*>> one,
                       JS::Handle<Wrapped<PlainDateObject*>> two,
                       TemporalUnit largestUnit,
                       JS::Handle<JS::Value> dateUntil, Duration* result);

/**
 * CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] )
 */
bool CalendarDateUntil(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<Wrapped<PlainDateObject*>> one,
                       JS::Handle<Wrapped<PlainDateObject*>> two,
                       JS::Handle<JSObject*> options, Duration* result);

/**
 * CalendarDateUntil ( calendar, one, two, options [ , dateUntil ] )
 */
bool CalendarDateUntil(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<Wrapped<PlainDateObject*>> one,
                       JS::Handle<Wrapped<PlainDateObject*>> two,
                       TemporalUnit largestUnit, Duration* result);

/**
 * CalendarYear ( calendar, dateLike )
 */
bool CalendarYear(JSContext* cx, JS::Handle<JSObject*> calendar,
                  JS::Handle<JS::Value> dateLike,
                  JS::MutableHandle<JS::Value> result);

/**
 * CalendarMonth ( calendar, dateLike )
 */
bool CalendarMonth(JSContext* cx, JS::Handle<JSObject*> calendar,
                   JS::Handle<JS::Value> dateLike,
                   JS::MutableHandle<JS::Value> result);

/**
 * CalendarMonthCode ( calendar, dateLike )
 */
bool CalendarMonthCode(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<JS::Value> dateLike,
                       JS::MutableHandle<JS::Value> result);

/**
 * CalendarDay ( calendar, dateLike )
 */
bool CalendarDay(JSContext* cx, JS::Handle<JSObject*> calendar,
                 JS::Handle<JS::Value> dateLike,
                 JS::MutableHandle<JS::Value> result);

/**
 * CalendarDayOfWeek ( calendar, dateLike )
 */
bool CalendarDayOfWeek(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<JS::Value> dateLike,
                       JS::MutableHandle<JS::Value> result);

/**
 * CalendarDayOfYear ( calendar, dateLike )
 */
bool CalendarDayOfYear(JSContext* cx, JS::Handle<JSObject*> calendar,
                       JS::Handle<JS::Value> dateLike,
                       JS::MutableHandle<JS::Value> result);

/**
 * CalendarWeekOfYear ( calendar, dateLike )
 */
bool CalendarWeekOfYear(JSContext* cx, JS::Handle<JSObject*> calendar,
                        JS::Handle<JS::Value> dateLike,
                        JS::MutableHandle<JS::Value> result);

/**
 * CalendarYearOfWeek ( calendar, dateLike )
 */
bool CalendarYearOfWeek(JSContext* cx, JS::Handle<JSObject*> calendar,
                        JS::Handle<JS::Value> dateLike,
                        JS::MutableHandle<JS::Value> result);

/**
 * CalendarDaysInWeek ( calendar, dateLike )
 */
bool CalendarDaysInWeek(JSContext* cx, JS::Handle<JSObject*> calendar,
                        JS::Handle<JS::Value> dateLike,
                        JS::MutableHandle<JS::Value> result);

/**
 * CalendarDaysInMonth ( calendar, dateLike )
 */
bool CalendarDaysInMonth(JSContext* cx, JS::Handle<JSObject*> calendar,
                         JS::Handle<JS::Value> dateLike,
                         JS::MutableHandle<JS::Value> result);

/**
 * CalendarDaysInYear ( calendar, dateLike )
 */
bool CalendarDaysInYear(JSContext* cx, JS::Handle<JSObject*> calendar,
                        JS::Handle<JS::Value> dateLike,
                        JS::MutableHandle<JS::Value> result);

/**
 * CalendarMonthsInYear ( calendar, dateLike )
 */
bool CalendarMonthsInYear(JSContext* cx, JS::Handle<JSObject*> calendar,
                          JS::Handle<JS::Value> dateLike,
                          JS::MutableHandle<JS::Value> result);

/**
 * CalendarInLeapYear ( calendar, dateLike )
 */
bool CalendarInLeapYear(JSContext* cx, JS::Handle<JSObject*> calendar,
                        JS::Handle<JS::Value> dateLike,
                        JS::MutableHandle<JS::Value> result);

/**
 * CalendarDateFromFields ( calendar, fields [ , options ] )
 */
Wrapped<PlainDateObject*> CalendarDateFromFields(JSContext* cx,
                                                 JS::Handle<JSObject*> calendar,
                                                 JS::Handle<JSObject*> fields);

/**
 * CalendarDateFromFields ( calendar, fields [ , options ] )
 */
Wrapped<PlainDateObject*> CalendarDateFromFields(JSContext* cx,
                                                 JS::Handle<JSObject*> calendar,
                                                 JS::Handle<JSObject*> fields,
                                                 JS::Handle<JSObject*> options);

/**
 * CalendarYearMonthFromFields ( calendar, fields [ , options ] )
 */
Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
    JSContext* cx, JS::Handle<JSObject*> calendar,
    JS::Handle<JSObject*> fields);

/**
 * CalendarYearMonthFromFields ( calendar, fields [ , options ] )
 */
Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
    JSContext* cx, JS::Handle<JSObject*> calendar, JS::Handle<JSObject*> fields,
    JS::Handle<JSObject*> options);

/**
 * CalendarMonthDayFromFields ( calendar, fields [ , options ] )
 */
Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
    JSContext* cx, JS::Handle<JSObject*> calendar,
    JS::Handle<JSObject*> fields);

/**
 * CalendarMonthDayFromFields ( calendar, fields [ , options ] )
 */
Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
    JSContext* cx, JS::Handle<JSObject*> calendar, JS::Handle<JSObject*> fields,
    JS::Handle<JSObject*> options);

/**
 * CalendarEquals ( one, two )
 */
bool CalendarEquals(JSContext* cx, JS::Handle<JSObject*> one,
                    JS::Handle<JSObject*> two, bool* equals);

/**
 * CalendarEquals ( one, two )
 */
bool CalendarEqualsOrThrow(JSContext* cx, JS::Handle<JSObject*> one,
                           JS::Handle<JSObject*> two);

/**
 * ConsolidateCalendars ( one, two )
 */
JSObject* ConsolidateCalendars(JSContext* cx, JS::Handle<JSObject*> one,
                               JS::Handle<JSObject*> two);

/**
 * MaybeFormatCalendarAnnotation ( calendarObject, showCalendar )
 */
bool MaybeFormatCalendarAnnotation(JSContext* cx, JSStringBuilder& result,
                                   JS::Handle<JSObject*> calendarObject,
                                   CalendarOption showCalendar);

/**
 * FormatCalendarAnnotation ( id, showCalendar )
 */
bool FormatCalendarAnnotation(JSContext* cx, JSStringBuilder& result,
                              JS::Handle<JSString*> id,
                              CalendarOption showCalendar);

/**
 * Perform `ToString(calendar)` with an optimization when the built-in
 * Temporal.Calendar.prototype.toString method is called.
 */
JSString* CalendarToString(JSContext* cx, JS::Handle<JSObject*> calendar);

/**
 * Return true when accessing the calendar fields |fieldNames| can be optimized.
 * Otherwise returns false.
 */
bool IsBuiltinAccess(JSContext* cx, JS::Handle<CalendarObject*> calendar,
                     std::initializer_list<CalendarField> fieldNames);

} /* namespace js::temporal */

#endif /* builtin_temporal_Calendar_h */
