/* -*- mode: c++; indent-tabs-mode: nil -*- */
/** @file QC_Type.cpp type class definition */
/*
    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include "qore_reflection.h"
#include "QC_Type.h"
#include "QC_TypedHash.h"
#include "QC_AbstractReflectionFunction.h"

QoreObject* get_type_object(const QoreTypeInfo* t) {
    return new QoreObject(QC_TYPE, getProgram(), new QoreType(t));
}

static QoreHashNode* Type_serializer(const QoreObject& self, const QoreType& t, QoreSerializationContext& context, ExceptionSink* xsink) {
    ReferenceHolder<QoreHashNode> rv(new QoreHashNode(autoTypeInfo), xsink);
    rv->setKeyValue("type", new QoreStringNode(qore_type_get_name(t.typeInfo)), xsink);
    return rv.release();
}

static void Type_deserializer(QoreObject& self, const QoreHashNode* sdata, QoreDeserializationContext& context, ExceptionSink* xsink) {
    if (sdata) {
        QoreValue val = sdata->getKeyValue("type");
        if (val.getType() != NT_STRING) {
            xsink->raiseException("DESERIALIZATION-ERROR", "Type serialization key 'type' has type '%s'; expecting 'string'", val.getTypeName());
            return;
        }
        const QoreTypeInfo* type = qore_get_type_from_string(val.get<const QoreStringNode>()->c_str(), *xsink);
        if (*xsink) {
            return;
        }
        assert(type);
        self.setPrivate(CID_TYPE, new QoreType(type));
        return;
    }

    xsink->raiseException("DESERIALIZATION-ERROR", "missing serialization data for the Type class");
}

/** @defgroup type_constants Type Constants

    @since %Qore 0.9.0
*/
///@{
//! @ref auto_type "auto type"
/**
*/
const AutoType = qore(get_type_object(autoTypeInfo));

//! @ref hash_complex_type "hash<auto> type"
/**
*/
const AutoHashType = qore(get_type_object(autoHashTypeInfo));

//! @ref hash_complex_or_nothing_type "*hash<auto> type"
/**
*/
const AutoHashOrNothingType = qore(get_type_object(autoHashOrNothingTypeInfo));

//! @ref list_complex_type "list<auto> type"
/**
*/
const AutoListType = qore(get_type_object(autoListTypeInfo));

//! @ref list_complex_or_nothing_type "*list<auto> type"
/**
*/
const AutoListOrNothingType = qore(get_type_object(autoListOrNothingTypeInfo));

//! @ref int_type "int type"
/**
*/
const IntType = qore(get_type_object(bigIntTypeInfo));

//! @ref int_or_nothing_type "*int type"
/**
*/
const IntOrNothingType = qore(get_type_object(bigIntOrNothingTypeInfo));

//! @ref binary_type "binary type"
/**
*/
const BinaryType = qore(get_type_object(binaryTypeInfo));

//! @ref binary_or_nothing_type "*binary type"
/**
*/
const BinaryOrNothingType = qore(get_type_object(binaryOrNothingTypeInfo));

//! @ref bool_type "bool type"
/**
*/
const BoolType = qore(get_type_object(boolTypeInfo));

//! @ref bool_or_nothing_type "*bool type"
/**
*/
const BoolOrNothingType = qore(get_type_object(boolOrNothingTypeInfo));

//! @ref code_type "code type"
/**
*/
const CodeType = qore(get_type_object(codeTypeInfo));

//! @ref code_or_nothing_type "*code type"
/**
*/
const CodeOrNothingType = qore(get_type_object(codeOrNothingTypeInfo));

//! @ref data_type "data type"
/**
*/
const DataType = qore(get_type_object(dataTypeInfo));

//! @ref data_or_nothing_type "*data type"
/**
*/
const DataOrNothingType = qore(get_type_object(dataOrNothingTypeInfo));

//! @ref date_type "date type"
/**
*/
const DateType = qore(get_type_object(dateTypeInfo));

//! @ref date_or_nothing_type "*date type"
/**
*/
const DateOrNothingType = qore(get_type_object(dateOrNothingTypeInfo));

//! @ref float_type "float type"
/**
*/
const FloatType = qore(get_type_object(floatTypeInfo));

//! @ref float_or_nothing_type "*float type"
/**
*/
const FloatOrNothingType = qore(get_type_object(floatOrNothingTypeInfo));

//! @ref hash_type "hash type"
/**
*/
const HashType = qore(get_type_object(hashTypeInfo));

//! @ref hash_or_nothing_type "*hash type"
/**
*/
const HashOrNothingType = qore(get_type_object(hashOrNothingTypeInfo));

//! @ref list_type "list type"
/**
*/
const ListType = qore(get_type_object(listTypeInfo));

//! @ref list_or_nothing_type "*list type"
/**
*/
const ListOrNothingType = qore(get_type_object(listOrNothingTypeInfo));

//! @ref nothing_type "nothing type"
/**
*/
const NothingType = qore(get_type_object(nothingTypeInfo));

//! @ref null_type "null type"
/**
*/
const NullType = qore(get_type_object(nullTypeInfo));

//! @ref null_or_nothing_type "*null type"
/**
*/
const NullOrNothingType = qore(get_type_object(nullOrNothingTypeInfo));

//! @ref number_type "number type"
/**
*/
const NumberType = qore(get_type_object(numberTypeInfo));

//! @ref number_or_nothing_type "*number type"
/**
*/
const NumberOrNothingType = qore(get_type_object(numberOrNothingTypeInfo));

//! @ref object_type "object type"
/**
*/
const ObjectType = qore(get_type_object(objectTypeInfo));

//! @ref object_or_nothing_type "*object type"
/**
*/
const ObjectOrNothingType = qore(get_type_object(objectOrNothingTypeInfo));

//! @ref reference_type "reference type"
/**
*/
const ReferenceType = qore(get_type_object(referenceTypeInfo));

//! @ref reference_or_nothing_type "*reference type"
/**
*/
const ReferenceOrNothingType = qore(get_type_object(referenceOrNothingTypeInfo));

//! @ref softlist_complex_type "softlist<auto> type"
/**
*/
const SoftAutoListType = qore(get_type_object(softAutoListTypeInfo));

//! @ref softlist_complex_or_nothing_type "*softlist<auto> type"
/**
*/
const SoftAutoListOrNothingType = qore(get_type_object(softAutoListOrNothingTypeInfo));

//! @ref softint_type "softint type"
/**
*/
const SoftIntType = qore(get_type_object(softBigIntTypeInfo));

//! @ref softint_or_nothing_type "*softint type"
/**
*/
const SoftIntOrNothingType = qore(get_type_object(softBigIntOrNothingTypeInfo));

//! @ref hexbinary_type "hexbinary type"
/**
*/
const HexBinaryType = qore(get_type_object(hexBinaryTypeInfo));

//! @ref hexbinary_or_nothing_type "*hexbinary type"
/**
*/
const HexBinaryOrNothingType = qore(get_type_object(hexBinaryOrNothingTypeInfo));

//! @ref base64binary_type "base64binary type"
/**
*/
const Base64BinaryType = qore(get_type_object(base64BinaryTypeInfo));

//! @ref base64binary_or_nothing_type "*base64binary type"
/**
*/
const Base64BinaryOrNothingType = qore(get_type_object(base64BinaryOrNothingTypeInfo));

//! @ref softbinary_type "softbinary type"
/**
*/
const SoftBinaryType = qore(get_type_object(softBinaryTypeInfo));

//! @ref softbinary_or_nothing_type "*softbinary type"
/**
*/
const SoftBinaryOrNothingType = qore(get_type_object(softBinaryOrNothingTypeInfo));

//! @ref softbool_type "softbool type"
/**
*/
const SoftBoolType = qore(get_type_object(softBoolTypeInfo));

//! @ref softbool_or_nothing_type "*softbool type"
/**
*/
const SoftBoolOrNothingType = qore(get_type_object(softBoolOrNothingTypeInfo));

//! @ref softdate_type "softdate type"
/**
*/
const SoftDateType = qore(get_type_object(softDateTypeInfo));

//! @ref softdate_or_nothing_type "*softdate type"
/**
*/
const SoftDateOrNothingType = qore(get_type_object(softDateOrNothingTypeInfo));

//! @ref softfloat_type "softfloat type"
/**
*/
const SoftFloatType = qore(get_type_object(softFloatTypeInfo));

//! @ref softfloat_or_nothing_type "*softfloat type"
/**
*/
const SoftFloatOrNothingType = qore(get_type_object(softFloatOrNothingTypeInfo));

//! @ref softlist_type "softlist type"
/**
*/
const SoftListType = qore(get_type_object(softListTypeInfo));

//! @ref softlist_or_nothing_type "*softlist type"
/**
*/
const SoftListOrNothingType = qore(get_type_object(softListOrNothingTypeInfo));

//! @ref softnumber_type "softnumber type"
/**
*/
const SoftNumberType = qore(get_type_object(softNumberTypeInfo));

//! @ref softnumber_or_nothing_type "*softnumber type"
/**
*/
const SoftNumberOrNothingType = qore(get_type_object(softNumberOrNothingTypeInfo));

//! @ref softstring_type "softstring type"
/**
*/
const SoftStringType = qore(get_type_object(softStringTypeInfo));

//! @ref softstring_or_nothing_type "*softstring type"
/**
*/
const SoftStringOrNothingType = qore(get_type_object(softStringOrNothingTypeInfo));

//! @ref string_type "string type"
/**
*/
const StringType = qore(get_type_object(stringTypeInfo));

//! @ref string_or_nothing_type "*string type"
/**
*/
const StringOrNothingType = qore(get_type_object(stringOrNothingTypeInfo));

//! @ref timeout_type "timeout type"
/**
*/
const TimeoutType = qore(get_type_object(timeoutTypeInfo));

//! @ref timeout_or_nothing_type "timeout or nothing type"
/**
*/
const TimeoutOrNothingType = qore(get_type_object(timeoutOrNothingTypeInfo));
///@}

/** @defgroup type_match_constants Type Match Constants

    @since %Qore 0.9.4
*/
///@{
//! types do not match
/**
*/
const QTI_NOT_EQUAL = QTI_NOT_EQUAL;

//! types match because at least one side matches all types
/**
*/
const QTI_WILDCARD = QTI_WILDCARD;

//! types match but are not closely related
/**
*/
const QTI_AMBIGUOUS = QTI_AMBIGUOUS;

//! types match and are closely related
/**
*/
const QTI_NEAR = QTI_NEAR;

//! types are identical
/**
*/
const QTI_IDENT = QTI_IDENT;
///@}

//! The base class for %Qore data types
/**
    @since %Qore 0.9.0
 */
qclass Type [ns=Qore::Reflection; arg=QoreType* t; dom=REFLECTION; vparent=Serializable; serializer=reinterpret_cast<q_serializer_t>(Type_serializer); deserializer=Type_deserializer];

//! Creates the type from the given string
/** @param string the type string

    @throw UNKNOWN-TYPE cannot find the given type
 */
Type::constructor(string typestr) {
    TempEncodingHelper utf8_typestr(typestr, QCS_UTF8, xsink);
    const QoreTypeInfo* type = qore_get_type_from_string(utf8_typestr->c_str(), *xsink);
    if (*xsink) {
        return;
    }
    assert(type);
    self->setPrivate(CID_TYPE, new QoreType(type));
}

//! returns the type's name
/** @par Example:
    @code{.py}
string name = t.getName();
    @endcode

    @return the type's name
 */
string Type::getName() [flags=RET_VALUE_ONLY] {
    return new QoreStringNode(qore_type_get_name(t->typeInfo));
}

//! Returns @ref True if the Type object passed as an argument is equal to the current object; @ref False if not
/** @par Example:
    @code{.py}
bool b = t1.isEqual(t2);
    @endcode

    @param type the type to check with the current type for equality

    @return @ref True if the Type object passed as an argument is equal to the current object; @ref False if not
*/
bool Type::isEqual(Type[QoreType] type) [flags=CONSTANT] {
    ReferenceHolder<QoreType> holder(type, xsink);
    return qore_type_equal(t->typeInfo, type->typeInfo);
}

//! Returns @ref True if the output of the Type object passed as an argument is compatible with the return type if the current Type
/** @par Example:
    @code{.py}
bool b = t1.isOutputCompatible(t2);
    @endcode

    @param type the type to check output value compatibility with the current type

    @return @ref True if the output of the Type object passed as an argument is compatible with the return type if the
    current Type

    @see isCompatible()
*/
bool Type::isOutputCompatible(Type[QoreType] type) [flags=CONSTANT] {
    ReferenceHolder<QoreType> holder(type, xsink);
    return qore_type_is_output_compatible(t->typeInfo, type->typeInfo);
}

//! Returns @ref True if the argument type is compatible with the current type (inputs and outputs)
/** @par Example:
    @code{.py}
bool b = t1.isCompatible(t2);
    @endcode

    @param type the type to check compatibility with the current type

    @return @ref True if the argument type is compatible with the current type (inputs and outputs)

    @note a more strict check than @ref isOutputCompatible()

    @since %Qore 0.9.4
*/
bool Type::isCompatible(Type[QoreType] type) [flags=CONSTANT] {
    ReferenceHolder<QoreType> holder(type, xsink);
    return qore_type_is_input_output_compatible(t->typeInfo, type->typeInfo);
}

//! Returns @ref True if the output of the Type object passed as an argument is compatible with the input type if the current Type
/** @par Example:
    @code{.py}
int v = t1.isAssignableFrom(t2);
    @endcode

    @param type the type to check output value compatibility with the current type
    @param may_not_match an output variable, if @ref Qore::True "True" then the assignment is not guaranteed to
    succeed and may fail at runtime

    @return @ref True if the output of the Type object passed as an argument is compatible with the input type if the
    current Type

    @since %Qore 0.9.4
*/
bool Type::isAssignableFrom(Type[QoreType] type, reference<bool> may_not_match) [flags=CONSTANT] {
    ReferenceHolder<QoreType> holder(type, xsink);
    QoreTypeSafeReferenceHelper r(may_not_match, xsink);
    if (!r) {
        return QoreValue();
    }

    bool may_not_match_flag = false;
    bool rv = qore_type_is_assignable_from(t->typeInfo, type->typeInfo, may_not_match_flag);
    if (r.assign(may_not_match_flag)) {
        return QoreValue();
    }
    return rv;
}

//! Returns the value after any conversions by the type
/** @par Example:
    @code{.py}
auto v1 = t1.acceptsValue(v0);
    @endcode

    @param value the value to assign to the type

    @return the value to be assigned; can be converted by the type

    @throw RUNTIME-TYPE-ERROR if the value cannot be assigned to the type
*/
auto Type::acceptsValue(auto value) {
    return qore_type_assign_value(t->typeInfo, value, xsink);
}

//! Returns @ref True if the output of the Type object passed as an argument is compatible with the input type if the current Type
/** @par Example:
    @code{.py}
bool b = t1.isAssignableFrom(t2);
    @endcode

    @param type the type to check output value compatibility with the current type

    @return @ref True if the output of the Type object passed as an argument is compatible with the input type if the
    current Type

    @since %Qore 0.9.4
*/
bool Type::isAssignableFrom(Type[QoreType] type) [flags=CONSTANT] {
    ReferenceHolder<QoreType> holder(type, xsink);
    return qore_type_is_assignable_from(t->typeInfo, type->typeInfo);
}

//! Returns @ref True if the Type object can be assigned from the value given as an argument
/** @par Example:
    @code{.py}
int b = t.isAssignableFrom(val);
    @endcode

    @param value the value to check

    @return a match code for the assignment; see @ref type_match_constants for possible values

    @since %Qore 0.9.4
*/
int Type::isAssignableFrom(auto value) [flags=CONSTANT] {
    return qore_type_is_assignable_from(t->typeInfo, value);
}

//! Returns the base type code for the type or @ref Qore::NT_ALL for those that don't have types
/** @par Example:
    @code{.py}
int t = t.getBaseTypeCode();
    @endcode

    @return the base type code for the type or @ref Qore::NT_ALL for those that don't have types

    @since %Qore 0.9.4
*/
int Type::getBaseTypeCode() [flags=CONSTANT] {
    return qore_type_get_base_type(t->typeInfo);
}

//! Returns a hash of types accepted by this type
/** @return a hash of types accepted by this type

    @since %Qore 0.9.4
*/
hash<string, bool> Type::getAcceptTypeHash() [flags=CONSTANT] {
    return qore_type_get_accept_types(t->typeInfo);
}

//! Returns a hash of types returned by this type
/** @return a hash of types returned by this type

    @since %Qore 0.9.4
*/
hash<string, bool> Type::getReturnTypeHash() [flags=CONSTANT] {
    return qore_type_get_return_types(t->typeInfo);
}

//! Returns @ref True if values of this type can be converted to a scalar value
/** @par Example:
    @code{.py}
bool b = t.canConvertToScalar();
    @endcode

    @return @ref True if the output of the Type object passed as an argument is compatible with the return type if the current Type
*/
bool Type::canConvertToScalar() [flags=CONSTANT] {
    return qore_type_can_convert_to_scalar(t->typeInfo);
}

//! Returns @ref True if this type has a default value
/** @par Example:
    @code{.py}
bool b = t.canConvertToScalar();
    @endcode

    @return @ref True if this type has a default value
*/
bool Type::hasDefaultValue() [flags=CONSTANT] {
    return qore_type_has_default_value(t->typeInfo);
}

//! Returns the default value for the type or @ref nothing if the type has no default value
/** @par Example:
    @code{.py}
auto v = t.getDefaultValue();
    @endcode

    @return the default value for the type or @ref nothing if the type has no default value
*/
auto Type::getDefaultValue() [flags=CONSTANT] {
    return qore_type_get_default_value(t->typeInfo);
}

//! Returns @ref Qore::True "True" if the type accepts and returns @ref nothing in addition to other values
/** @par Example:
    @code{.py}
bool b = type.isOrNothingType();
    @endcode

    @return @ref Qore::True "True" if the type accepts and returns @ref nothing in addition to other values

    @since %Qore 0.9.4
*/
bool Type::isOrNothingType() [flags=CONSTANT] {
    return qore_type_is_output_compatible(nothingTypeInfo, t->typeInfo);
}

//! Returns the base type for the current type; if the type is already a base type (i.e. not an "or nothing" type), then the same type is returned
/** @par Example:
    @code{.py}
auto v = t.getBaseType();
    @endcode

    @return the base type for the current type; if the type is already a base type (i.e. not an "or nothing" type), then the same type is returned

    @see getOrNothingType()

    @since %Qore 0.9.4
*/
Type Type::getBaseType() [flags=CONSTANT] {
    if (!qore_type_is_output_compatible(nothingTypeInfo, t->typeInfo) || t->typeInfo == nothingTypeInfo) {
        return self->refSelf();
    }
    // get type string
    QoreString typestr(typeInfoGetName(t->typeInfo));
    if (typestr[0] != '*') {
        return self->refSelf();
    }
    typestr.splice(0, 1, xsink);
    if (*xsink) {
        return QoreValue();
    }

    const QoreTypeInfo* type = qore_get_type_from_string(typestr.c_str(), *xsink);
    if (*xsink) {
        return QoreValue();
    }
    return new QoreObject(QC_TYPE, getProgram(), new QoreType(type));
}

//! Returns the "or nothing" type for the current type; if the type is already an "or nothing" type (i.e. it already accepts @ref nothing), then the same type is returned
/** @par Example:
    @code{.py}
auto v = t.getOrNothingType();
    @endcode

    @return the "or nothing" type for the current type; if the type is already an "or nothing" type (i.e. it already accepts @ref nothing), then the same type is returned

    @see getBaseType()
*/
Type Type::getOrNothingType() [flags=CONSTANT] {
    return new QoreObject(QC_TYPE, getProgram(), new QoreType(get_or_nothing_type_check(t->typeInfo)));
}

//! Returns the element type for complex list and hash types, if any, otherwise returns @ref nothing
/** @par Example:
    @code{.py}
*Type element_type = type.getElementType();
    @endcode

    @return the element type for complex list and hash types, if any, otherwise returns @ref nothing

    @since %Qore 0.9.4
*/
*Type Type::getElementType() [flags=CONSTANT] {
    const QoreTypeInfo* element_type = typeInfoGetElementType(t->typeInfo);
    if (!element_type) {
        return QoreValue();
    }
    return new QoreObject(QC_TYPE, getProgram(), new QoreType(element_type));
}

//! Returns the TypedHash for the type, if the type specifies a typed hash
/** @par Example:
    @code{.py}
*TypedHash hd = type.getTypedHash();
    @endcode

    @return the TypedHash for the type, if the type specifies a typed hash

    @since %Qore 0.9.4
*/
*TypedHash Type::getTypedHash() [flags=CONSTANT] {
    const TypedHashDecl* hashdecl = typeInfoGetTypedHash(t->typeInfo);
    if (!hashdecl) {
        return QoreValue();
    }
    return new QoreObject(QC_TYPEDHASH, getProgram(), new QoreReflectionTypedHash(getProgram(), hashdecl));
}

//! Returns @ref Qore::True "True" if the type is a TypedHash type, @ref Qore::False "False" if not
/** @par Example:
    @code{.py}
bool b = type.isTypedHash();
    @endcode

    @return @ref Qore::True "True" if the type is a TypedHash type, @ref Qore::False "False" if not

    @since %Qore 0.9.4
*/
bool Type::isTypedHash() [flags=CONSTANT] {
    return typeInfoGetTypedHash(t->typeInfo) ? true : false;
}

//! Returns @ref Qore::True "True" if the type is not a wildcard type; i.e. has type restrictions
/** @par Example:
    @code{.py}
bool b = type.hasType();
    @endcode

    @return @ref Qore::True "True" if the type is not a wildcard type; i.e. has type restrictions

    @since %Qore 0.9.4
*/
bool Type::hasType() [flags=CONSTANT] {
    return typeInfoHasType(t->typeInfo);
}

//! Returns the type object for the value
/** @par Example:
    @code{.py}
Type t = Type::getType(v);
    @endcode

    @param v the value to return the type object for

    @return the type object for the value
*/
static Type Type::getType(auto v) [flags=CONSTANT] {
    return new QoreObject(QC_TYPE, getProgram(), new QoreType(v.getFullTypeInfo()));
}

//! Returns the "or nothing" type object for the value
/** @par Example:
    @code{.py}
Type t = Type::getOrNothingType(v);
    @endcode

    @return the "or nothing" type object for the value
*/
static Type Type::getOrNothingType(auto v) [flags=CONSTANT] {
    // the class ptr must be valid
    return new QoreObject(QC_TYPE, getProgram(), new QoreType(get_or_nothing_type_check(v.getFullTypeInfo())));
}
