/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    ql_type.qpp

    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/Qore.h>
#include "qore/intern/ql_type.h"
#include "qore/intern/qore_date_private.h"
#include "qore/intern/QoreObjectIntern.h"
#include "qore/intern/QoreHashNodeIntern.h"
#include "qore/intern/qore_list_private.h"

#include <memory>

static QoreValue auto_cast(const QoreValue v);

static QoreHashNode* auto_cast_hash(const QoreHashNode* h) {
    ReferenceHolder<QoreHashNode> rv(new QoreHashNode(autoTypeInfo), nullptr);

    qore_hash_private* hash_priv = qore_hash_private::get(**rv);

    const QoreTypeInfo* vtype = nullptr;
    bool vcommon = false;

    ConstHashIterator i(h);
    while (i.next()) {
        const char* key = i.getKey();
        const QoreValue val = i.get();
        hash_priv->setKeyValueIntern(key, auto_cast(val));

        const QoreTypeInfo* ctype = val.getTypeInfo();

        if (i.first()) {
            if (ctype && ctype != anyTypeInfo) {
                vtype = ctype;
                vcommon = true;
            }
        } else if (vcommon && !QoreTypeInfo::matchCommonType(vtype, ctype)) {
            vcommon = false;
        }
    }

    // issue #2791: when performing type folding, do not set to type "any" but rather use "auto"
    if (vtype && vtype != anyTypeInfo) {
        hash_priv->complexTypeInfo = qore_get_complex_hash_type(vtype);
    }

    return rv.release();
}

static QoreListNode* auto_cast_list(const QoreListNode* l) {
    ReferenceHolder<QoreListNode> rv(new QoreListNode(autoTypeInfo), nullptr);

    qore_list_private* list_priv = qore_list_private::get(**rv);

    const QoreTypeInfo* vtype = nullptr;
    bool vcommon = false;

    ConstListIterator i(l);
    while (i.next()) {
        const QoreValue val = i.getValue();
        list_priv->pushIntern(auto_cast(val));

        const QoreTypeInfo* ctype = val.getTypeInfo();

        if (!i.index()) {
            if (ctype && ctype != anyTypeInfo) {
                vtype = ctype;
                vcommon = true;
            }
        } else if (vcommon && !QoreTypeInfo::matchCommonType(vtype, ctype)) {
            vcommon = false;
        }
    }

    // issue #2791: when performing type folding, do not set to type "any" but rather use "auto"
    if (vtype && vtype != anyTypeInfo) {
        list_priv->complexTypeInfo = qore_get_complex_list_type(vtype);
    }

    return rv.release();
}

static QoreValue auto_cast(const QoreValue v) {
    switch (v.getType()) {
        case NT_HASH: {
            return auto_cast_hash(v.get<QoreHashNode>());
        }
        case NT_LIST: {
            return auto_cast_list(v.get<QoreListNode>());
        }
        default:
            return v.refSelf();
    }
}

/** @defgroup string_type_constants String Type Constants
    String type constants as returned by type() and typename()
*/
///@{
namespace Qore::Type;

//! Gives the type for @ref boolean "boolean" values
const Boolean = "bool";

//! Gives the type for @ref integer "integer" values
const Int = "integer";

//! Gives the type for @ref float "float" values
const Float = "float";

//! Gives the type for @ref number "number" values
const Number = "number";

//! Gives the type for @ref string "string" values
const String = "string";

//! Gives the type for the @ref date "date" values
const Date = "date";

//! Gives the type when @ref nothing "no value" is available
const NothingType = "nothing";

//! Gives the type for @ref null "SQL null" values
const NullType = "NULL";

//! Gives the type for @ref binary "binary" values
const Binary = "binary";

//! Gives the type for @ref list "list" values
const List = "list";

//! Gives the type for @ref hash "hash" values
const Hash = "hash";

//! Gives the type for @ref object "object" values
const Object = "object";

//! Gives the type for @ref call_reference "call references"
const CallReference = "call reference";

//! Gives the type for @ref closure "closures"
const Closure = "closure";
///@}

/** @defgroup type_conversion_functions Type Conversion Functions
 */
///@{

//! Converts the argument to a boolean value
/**
    @param arg the argument to convert to a boolean

    @return the boolean value corresponding to the argument

    @par Example:
    @code{.py}
bool b = boolean(1); # returns True
    @endcode

    @note this function behaves differently when @ref strict-bool-eval "%strict-bool-eval" is set
*/
bool boolean(any arg) [flags=CONSTANT] {
   return arg.getAsBool();
}

//! Converts the string to an integer value with respect to the <b>base</b>.
/** If a character is out of range of the corresponding <b>base</b> only the substring preceeding the character is taken into account.\n
    Base 0 means to interpret as a code literal, so that the actual base is 8, 10, or 16.\n
    (Does the same as \link <string>::toInt(int base) \endlink).

    @param str the string to convert to an integer
    @param base the base of the integer in the string; this value must be 0 or 2 - 36 inclusive or an \c INVALID-BASE exception will be thrown

    @return the integer value corresponding to the arguments

    @par Example:
    @code{.py}
int("fe", 16);   // returns 254
int("34", 5);    // returns 19
int("2p", 25);   // returns 2  (p means 25, which is out of range)
int("2p", 26);   // returns 77
int("2z", 36);   // returns 107
int("21");       // returns 21 (base = 10)
int("21", 0);    // returns 21 (base = 10)
int("021", 0);   // returns 17 (base =  8)
int("0x21", 0);  // returns 33 (base = 16)
    @endcode

    @throw INVALID-BASE the base is invalid; must be 0 or 2 - 36 inclusive

    @see \link <string>::toInt(int base) \endlink

    @since %Qore 0.8.5, this variant with the <b>base</b> argument is supported
  */
int int(string str, int base = 10) [flags=RET_VALUE_ONLY] {
    if (base < 0 || base == 1 || base > 36)
        return xsink->raiseException("INVALID-BASE", "base " QLLD " is invalid; base must be 0 or 2 - 36 inclusive", base);
    return strtoll(str->getBuffer(), 0, (int)base);
}

//! Converts the argument to an integer value
/**
    @param i the argument to convert to an integer

    @return the integer value corresponding to the argument

    @par Example:
    @code{.py}
int i = int("1");
    @endcode
*/
int int(softint i) [flags=CONSTANT] {
    return i;
}

//! Always returns 0
/** The int() function is used for type conversions, therefore this variant is not tagged with @ref NOOP

    @see int(softint)
*/
int int() [flags=CONSTANT] {
   return 0;
}

//! Converts the argument to a floating-point (float) value
/**
    @param f the argument to convert to a float

    @return the float value corresponding to the argument

    @par Example:
    @code{.py}
float i = float("3.1415");
    @endcode
*/
float float(softfloat f) [flags=CONSTANT] {
   return f;
}

//! Always returns 0.0
/** The float() function is used for type conversions, therefore this variant is not tagged with @ref NOOP

    @see float(softfloat)
*/
float float() [flags=CONSTANT] {
   return 0.0;
}

//! Converts the argument to a @ref number "number" value
/**
    @param n the argument to convert to a @ref number "number"

    @return the @ref number "number" value corresponding to the argument

    @par Example:
    @code{.py}
number n = number("2.23040945718005e35");
    @endcode
*/
number number(softnumber n) [flags=CONSTANT] {
   return n->refSelf();
}

//! Always returns 0.0
/** The number() function is used for type conversions, therefore this variant is not tagged with @ref NOOP

    @see number(softnumber)
*/
number number() [flags=CONSTANT] {
   return ZeroNumber->refSelf();
}

//! Converts the argument to a string
/**
    @param str the argument to convert to a string
    @param enc the encoding of the string returned

    @return the string value corresponding to the argument(s)

    @par Example:
    @code{.py}
string str = string(100);
    @endcode

    @since the \c enc argument was added in %Qore 0.8.12
*/
string string(softstring str, *string enc) [flags=CONSTANT] {
   if (enc) {
      const QoreEncoding* e = QEM.findCreate(enc->getBuffer());
      if (e != str->getEncoding())
         return str->convertEncoding(e, xsink);
   }
   return str->refSelf();
}

//! Always returns an empty string
/** The string() function is used for type conversions, therefore this variant is not tagged with @ref NOOP

    @see string(softstring)
*/
string string() [flags=CONSTANT] {
   return null_string();
}

//! Always returns an empty binary object (of zero length)
/** The binary() function is used for type conversions, therefore this variant is not tagged with @ref NOOP

    @see
    - binary(softstring)
    - binary(null)
*/
binary binary() [flags=CONSTANT] {
   return new BinaryNode;
}

//! Always returns an empty binary object (of zero length)
/** The binary() function is used for type conversions, therefore this variant is not tagged with @ref NOOP

    @see binary(softstring)
*/
binary binary(null[doc] x) [flags=CONSTANT] {
   return new BinaryNode;
}

//! Returns a binary data type of the string passed; data types other than string will first be converted to a string and then returned as binary data
/** This function is useful if, for example, a string type actually contains binary data; using this function will ensure that all data in the string (even if it contains embedded nulls) is maintained in the binary object (Qore strings must normally be terminated by a single null, so some %Qore string operations do not work on binary data with embedded nulls).

    @par Example:
    @code{.py}
binary b = binary(str);
    @endcode

    @see <string>::toBinary()
*/
binary binary(softstring str) [flags=CONSTANT] {
   BinaryNode *b = new BinaryNode;
   b->append(str->getBuffer(), str->strlen());
   return b;
}

//! Always returns the same binary object passed
/** The binary() function is used for type conversions, therefore this variant is not tagged with @ref NOOP
*/
binary binary(binary bin) [flags=CONSTANT] {
   return bin->refSelf();
}

//! Returns a list of the arguments passed at the top level
/** @param ... the arguments that will be returned as a list

    @return a list of the arguments passed at the top level; if a sole argument of @ref nothing is passed, then an empty list is returned

    @par Example:
    @code{.py}
list<auto> l = list(1, 2, 3, 4);
    @endcode
*/
list<auto> list(...) [flags=CONSTANT] {
    QoreListNode *l;
    if (num_args(args) > 1) {
        l = args->listRefSelf();
    } else {
        l = new QoreListNode(autoTypeInfo);
        QoreValue p0 = get_param_value(args, 0);
        if (!p0.isNothing()) {
            l->push(p0.refSelf(), xsink);
        }
    }
    return l;
}

//! Returns a hash of an object's members
/** @note that if this function is called from outside the class' scope, the hash will only contain the object's public members

    @param obj the object to process

    @return a hash of an object's members

    @par Example:
    @code{.py}
hash<auto> h = hash(obj);
    @endcode
*/
hash<auto> hash(object obj) [flags=RET_VALUE_ONLY] {
   return qore_object_private::get(*obj)->getRuntimeMemberHash(xsink);
}

//! Returns a hash by taking even numbered list elements (starting with 0) and converting them to strings for the hash keys, and the odd numbered elements following the keys as the key value
/**
    @param l the list to process in a manner similar to perl's hash initialization

    @return a hash by taking even numbered list elements (starting with 0) and converting them to strings for the hash keys, and the odd numbered elements following the keys as the key value

    @par Example:
    @code{.py}
hash<auto> h = hash(("a", 1, "b", "two"));
    @endcode
*/
hash<auto> hash(list<auto> l) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<QoreHashNode> h(new QoreHashNode(autoTypeInfo), xsink);

    bool valid = true;
    ConstListIterator li(l);
    while (valid && ((valid = li.next()))) {
        QoreStringValueHelper str(li.getValue());
        h->setKeyValue(str->c_str(), ((valid = li.next())) ? li.getReferencedValue() : QoreValue(), xsink);
        if (*xsink)
            return QoreValue();
    }
    return h.release();
}

//! Returns a hash by taking the first list as a list of keys, and the second list as a list of values
/** If the two lists are of unequal sizes, then the keys list takes precedence (if the values list is longer, excess values are ignored, if the keys list is longer, then excess elements are assigned @ref nothing)

    @param keys a list of key values for the hash
    @param values a list of values for the hash, corresponding to the elements in \a keys

    @return a hash by taking the first list as a list of keys, and the second list as a list of values

    @par Example:
    @code{.py}
hash<auto> h = hash(keysv, values);
    @endcode
*/
hash<auto> hash(list<softstring> keys, list<auto> values) [flags=RET_VALUE_ONLY] {
    ReferenceHolder<QoreHashNode> h(new QoreHashNode(autoTypeInfo), xsink);

    ConstListIterator ki(keys);
    ConstListIterator vi(values);

    bool valid = true;
    while (ki.next()) {
        if (valid)
            valid = vi.next();

        QoreStringValueHelper str(ki.getValue());
        h->setKeyValue(str->c_str(), valid ? vi.getReferencedValue() : QoreValue(), xsink);
        if (*xsink)
            return QoreValue();
    }

    return h.release();
}

//! Returns the same hash with all complex types stripped
/** @par Example:
    @code{.py}
hash h2 = hash(h1);
    @endcode

    @param h the hash to return

    @return the same hash passed as an argument with all complex types stripped

    @since
    - %Qore 0.8.13 this function was modified to remove type information at runtime from typed hashes
*/
hash hash(hash h) [flags=CONSTANT] {
    return h->hashRefSelf();
}

//! Always returns the same hash passed
/** The hash() function is used for type conversions, therefore this variant is not tagged with @ref NOOP
*/
hash<auto> hash() [flags=CONSTANT] {
    return new QoreHashNode(autoTypeInfo);
}

//! Returns a string giving the data type of the argument passed; see @ref string_type_constants for the values returned by this function
/**
    @param arg the argument to check

    @return a string giving the data type of the argument passed; see @ref string_type_constants for the values returned by this function

    @par Example:
    @code{.py}
string type = type(v);
    @endcode

    @note
    - This function is identical to typename(any) and to calling pseudo-method <value>::type()
    - It is faster and more efficient to use <value>::typeCode() for comparing data types

    @see pseudo-method <value>::typeCode()
*/
string type(auto arg) [flags=CONSTANT] {
    return new QoreStringNode(arg.getTypeName());
}

//! Returns a string giving the data type of the argument passed; see @ref string_type_constants for the values returned by this function
/**
    @param arg the argument to check

    @return a string giving the data type of the argument passed; see @ref string_type_constants for the values returned by this function

    @par Example:
    @code{.py}
string type = typename(v);
    @endcode

    @note
    - This function is identical to type(any) and to calling pseudo-method <value>::type()
    - It is faster and more efficient to use <value>::typeCode() for comparing data types
*/
string typename(auto arg) [flags=CONSTANT] {
    return new QoreStringNode(arg.getTypeName());
}

//! Returns a string created from the binary data passed, taking an optional second argument giving the string encoding; if no second argument is passed then the @ref default_encoding "default character encoding" is assumed
/**
    @param b the binary object to convert directly to a string
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string created from the binary data passed

    @par Example:
    @code{.py}
string str = binary_to_string(b, "iso-8859-1");
    @endcode
*/
string binary_to_string(binary b, *string encoding) [flags=CONSTANT] {
    const QoreEncoding* qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
    if (!b->size()) {
        return new QoreStringNode(qe);
    }
    return new QoreStringNode((const char*)b->getPtr(), b->size(), qe);
}

//! Returns complex types converted to the most specific types possible using internal type folding
/** @par Example:
    @code{.py}
list<auto> tmp;
tmp += 1;
tmp += 2;
list<int> int_list = auto_cast(tmp);
    @endcode

    @param val the value to convert

    @return a copy of the input value with complex types converted to the most specific types possible using internal type folding

    @since %Qore 0.9.3
 */
auto auto_cast(auto val) [flags=CONSTANT] {
    return auto_cast(val);
}
///@}
