import project.Any.Any
import project.Data.Array.Array
import project.Data.Numbers.Integer
import project.Data.Numbers.Number
import project.Data.Text.Text
import project.Data.Time.Date.Date
import project.Data.Time.Date_Time.Date_Time
import project.Data.Time.Duration.Duration
import project.Data.Time.Time_Of_Day.Time_Of_Day
import project.Data.Time.Time_Zone.Time_Zone
import project.Data.Vector.Vector
import project.Error.Error as Base_Error
import project.Errors.Common.Not_Found
import project.Function.Function
import project.Nothing.Nothing
from project.Data.Boolean import Boolean, False, True
from project.Runtime.Managed_Resource import Managed_Resource

type Type
    ## PRIVATE
       ADVANCED

       Type meta-representation.

       Arguments:
       - value: The value of the type in the meta representation.
    Value value

    ## ADVANCED
       ICON metadata

       Returns a vector of `Meta.Constructor` for this type
    constructors : Vector
    constructors self =
        Vector.from_polyglot_array (get_type_constructors self.value Meta.Constructor.Value)

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns a vector of method names that can be invoked
       on instances of this type.
       ? Static Methods

         To obtain list of _static methods_ on a given type
         use `Meta.type_of`.

       > Example
         All instance methods to invoke on `Integer` as
         `(v:Integer) v.method_name...`:

             Meta.meta Integer . methods

       > Example
         All static methods to invoke on `Integer` as
         `Integer.method_name...`:

            Meta.meta (Meta.type_of Integer) . methods


    methods : Vector
    methods self =
        Vector.from_polyglot_array (get_type_methods self.value)

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns the fully qualified name of the type.
    qualified_name : Text
    qualified_name self =
        c = self.value
        get_qualified_type_name c

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns the short name of the type.
    name : Text
    name self =
        c = self.value
        get_short_type_name c

    ## ADVANCED
       GROUP Metadata
       ICON find

       Finds type specified by fully qualified name. Searches
       all the types known to the system by transitive closure
       of import statements in executing modules.

       Arguments:
       - qualified_name: fully qualified name.
    find : Text -> Type ! Not_Found
    find qualified_name =
        raw = find_type_by_qualified_name qualified_name
        case Meta.meta raw of
            t:Meta.Type -> t
            _ -> Base_Error.throw Not_Found

type Atom
    ## PRIVATE
       ADVANCED

       An Atom meta-representation.

       Arguments:
       - value: The value of the atom in the meta representation.
    Value value

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns a vector of field values of the given atom.
    fields : Vector
    fields self = Vector.from_polyglot_array (get_atom_fields self.value)

    ## ADVANCED
       ICON metadata

       Returns a constructor value of the given atom.
    constructor : Constructor
    constructor self =
        java_constructor = get_atom_constructor self.value ...
        Meta.Constructor.Value java_constructor

type Constructor
    ## PRIVATE
       ADVANCED

       A constructor meta-representation.

       Arguments:
       - value: The value of the constructor in the meta representation.
    Value value

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns a vector of field names defined by a constructor.
    fields : Vector
    fields self =
        c = self.value ...
        Vector.from_polyglot_array (get_constructor_fields c)

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns the name of a constructor.
    name : Text
    name self =
        c = self.value ...
        get_constructor_name c

    ## ADVANCED
       ICON braces

       Creates a new atom of the given constructor.

       Arguments:
       - fields: A vector of arguments to pass to the constructor when creating the
         new atom.
    new : Vector -> Any
    new self fields =
        ctor = self.value ...
        new_atom ctor fields

    ## ADVANCED
       GROUP Metadata
       ICON metadata
       Returns the type that this constructor is a part of.
    declaring_type : Type
    declaring_type self =
        c = self.value ...
        Type.Value (get_constructor_declaring_type c)

type Primitive
    ## PRIVATE
       ADVANCED

       A primitive value meta-representation.

       Arguments:
       - value: The value of the primitive object in the meta representation.
    Value value

type Unresolved_Symbol
    ## PRIVATE
       ADVANCED

       An unresolved symbol meta-representation.

       Arguments:
       - value: The value of the unresolved symbol in the meta representation.
    Value value

    ## PRIVATE
       ADVANCED

       Returns a new unresolved symbol with its name changed to the provided
       argument.

       Arguments:
       - new_name: The new name for the unresolved symbol.
    rename : Text -> Any
    rename self new_name =
        create_unresolved_symbol new_name self.value

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns the name of an unresolved symbol.
    name : Text
    name self = get_unresolved_symbol_name self.value

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Starts building an instrumentation for a given node
    instrument : Instrumentor
    instrument self = Instrumentor.Value (instrumentor_builtin "newBuilder" [ self.value ])

type Error
    ## PRIVATE
       ADVANCED

       An error meta-representation, containing the payload of a dataflow error.

       Arguments:
       - value: The payload of the error.
    Value value

type Polyglot
    ## PRIVATE
       ADVANCED

       A polyglot value meta-representation.

       Arguments:
       - value: The polyglot value contained in the meta representation.
    Value value

    ## ADVANCED
       GROUP Metadata
       ICON metadata

       Returns the language with which a polyglot value is associated.
    get_language : Language
    get_language self =
        lang_str = get_polyglot_language self.value
        if lang_str == "java" then Language.Java else Language.Unknown

## ADVANCED
   ICON metadata

   Checks whether `self` represents the same underlying reference as `value`.

   Arguments:
   - value_1: The first value.
   - value_2: The second value.
Any.is_same_object_as : Any -> Boolean
Any.is_same_object_as self value = is_same_object self value

## ADVANCED
   ICON metadata

   Checks if `self` is an instance of `typ`.

   Arguments:
   - typ: The type to check `self` against.
Any.is_a : Any -> Boolean
Any.is_a self typ = is_a self typ

## ADVANCED
   ICON metadata

   Checks if `self` is an instance of `typ`.

   Arguments:
   - typ: The type to check `self` against.
Base_Error.is_a : Any -> Boolean
Base_Error.is_a self typ = typ==Any || typ==Base_Error

## PRIVATE

   Gets the atom constructor instance for the provided atom.

   Arguments:
   - atom: The atom to obtain the constructor for.
get_atom_constructor : Atom -> Any
get_atom_constructor atom = @Builtin_Method "Meta.get_atom_constructor"

## PRIVATE

   Get the fields for the provided atom.

   Arguments:
   - atom: The atom to obtain the fields for.
get_atom_fields : Atom -> Array
get_atom_fields atom = @Builtin_Method "Meta.get_atom_fields"

## PRIVATE

   Get the constructors for the provided type.

   Arguments:
   - typ: The type to obtain the constructors for.
   - factory: function to use to wrap constructors in
get_type_constructors : Atom -> (Any -> Any) -> Array
get_type_constructors typ factory = @Builtin_Method "Meta.get_type_constructors"

## PRIVATE

   Get the methods for the provided type.

   Arguments:
   - typ: The type to obtain the methods for.
get_type_methods : Atom -> Array
get_type_methods typ = @Builtin_Method "Meta.get_type_methods"

## PRIVATE

   Get a textual representation of the language from which an object comes.

   Arguments:
   - value: The value to obtain the source language for.
get_polyglot_language : Any -> Text
get_polyglot_language value = @Builtin_Method "Meta.get_polyglot_language"

## PRIVATE

   Creates an unresolved symbol for the name in the scope.

   Arguments:
   - name: The name of the unresolved symbol.
   - symbol: The unresolved symbol from which to get the scope.
create_unresolved_symbol : Text -> Any -> Unresolved_Symbol
create_unresolved_symbol name symbol = @Builtin_Method "Meta.create_unresolved_symbol"

## PRIVATE

   Obtains the name of the provided unresolved symbol.

   Arguments:
   - symbol: The unresolved symbol from which to get the name.
get_unresolved_symbol_name : Unresolved_Symbol -> Text
get_unresolved_symbol_name symbol = @Builtin_Method "Meta.get_unresolved_symbol_name"

## PRIVATE

   Get the fields of an atom constructor.

   Arguments:
   - atom_constructor: The constructor from which to get the fields.
get_constructor_fields : Any -> Array
get_constructor_fields atom_constructor = @Builtin_Method "Meta.get_constructor_fields"

## PRIVATE

   Get the name of an atom constructor.

   Arguments:
   - atom_constructor: The atom constructor from which to obtain the name.
get_constructor_name : Any -> Text
get_constructor_name atom_constructor = @Builtin_Method "Meta.get_constructor_name"

## PRIVATE

   Constructs a new atom using the provided constructor and fields.

   Arguments:
   - constructor: The constructor for the atom to create.
   - fields: The arguments to pass to constructor.
new_atom : Any -> Array -> Atom
new_atom constructor fields = @Builtin_Method "Meta.new_atom"

## PRIVATE

   Constructs a new atom with a "hole". Returns an object with `value` and
   `fill` properties. Value contains the created atom and `fill` holds a
   function to "fill the hole" later.

   There can be only one hole in the atom, i.e., only one field of the atom
   returned by the `value` method should be a hole. If this condition is
   violated, a Panic error with `Uninitialized_State` payload is thrown.

   Arguments:
   - factory: a function that takes the "hole" element and returns newly created atom

   > Example
     Create a Pair that has a hole in its first element.
         atom_with_hole = Meta.atom_with_hole (e-> Pair.Value e 2)
         atom_with_hole.value.first.to_text == "Meta.atom_with_hole"
         atom_with_hole.fill 1
         atom_with_hole.value.first.to_text == "1"
atom_with_hole : (Any -> Atom) -> Any
atom_with_hole factory = @Builtin_Method "Meta.atom_with_hole_builtin"

## PRIVATE
   ADVANCED

   Returns a meta-representation of a given runtime entity.

   Arguments:
   - value: The runtime entity to get the meta representation of.
meta : Any -> Atom | Constructor | Primitive | Polyglot | Unresolved_Symbol | Error
meta value = if is_atom value then Atom.Value value else
    if is_atom_constructor value then Constructor.Value value else
        if is_polyglot value then Polyglot.Value value else
            if is_unresolved_symbol value then Unresolved_Symbol.Value value else
                if is_error value then Error.Value value.catch else
                    if is_type value then Type.Value value.catch else
                        Primitive.Value value

## PRIVATE
   ADVANCED

   Checks whether two objects are represented by the same underlying reference.

   Arguments:
   - value_1: The first value.
   - value_2: The second value.
is_same_object : Any -> Any -> Boolean
is_same_object value_1 value_2 = @Builtin_Method "Meta.is_same_object"

## PRIVATE
   ADVANCED

   Checks if `value` is an instance of `typ`.

   Arguments:
   - value: The value to check for being an instance of `typ`.
   - typ: The type to check `self` against.
is_a : Any -> Any -> Boolean
is_a value typ = @Builtin_Method "Meta.is_a"

## PRIVATE
java_instance_check value typ =
    val_java = get_polyglot_language value == "java"
    typ_java = get_polyglot_language typ == "java"
    val_java && typ_java && typ.class.isInstance value

## PRIVATE
   ADVANCED

   Returns the type of the given value.

   Arguments:
   - value: The value to get the type of.
type_of : Any -> Any
type_of value = @Builtin_Method "Meta.type_of"

## PRIVATE
   ADVANCED

   Given a type object, method name and a parameter name, return the associated annotation if it exists.

   Arguments:
   - target: The value or type to get the attribute from.
   - method: The symbol representing method or constructor to get the attribute for.
   - parameter_name: The name of the parameter to get the attribute for.
get_annotation : Any -> Any -> Text -> Any | Nothing
get_annotation target method parameter_name = @Builtin_Method "Meta.get_annotation"

## PRIVATE
   Represents a polyglot language.
type Language
    ## PRIVATE
       ADVANCED

       The Java language.
    Java

    ## PRIVATE
       ADVANCED

       An unknown language.
    Unknown

## PRIVATE

   Checks if the provided value is an atom constructor.

   Arguments:
   - value: The value to check.
is_atom_constructor : Any -> Boolean
is_atom_constructor value = @Builtin_Method "Meta.is_atom_constructor"

## PRIVATE

   Checks if the provided value is an atom.

   Arguments:
   - value: The value to check.
is_atom : Any -> Boolean
is_atom value = @Builtin_Method "Meta.is_atom"

## PRIVATE

   Checks if the provided value is a runtime error.

   Arguments:
   - value: The value to check.
is_error : Any -> Boolean
is_error value = @Builtin_Method "Meta.is_error"

## PRIVATE

   Checks if the provided value is a type.

   Arguments:
   - value: The value to check.
is_type : Any -> Boolean
is_type value = @Builtin_Method "Meta.is_type"

## PRIVATE

   Checks if the provided value is a polyglot value.

   Arguments:
   - value: The value to check.
is_polyglot : Any -> Boolean
is_polyglot value = @Builtin_Method "Meta.is_polyglot"

## PRIVATE

   Checks if the provided value is an unresolved symbol.

   Arguments:
   - value: The value to check.
is_unresolved_symbol : Any -> Boolean
is_unresolved_symbol value = @Builtin_Method "Meta.is_unresolved_symbol"

## PRIVATE

   Returns a Text representing the source location of a stack frame above
   the call.

   Arguments:
   - skip_frames: how many frames on the stack to skip.

   If the function is called with 0 it will return the location of that call.
   Note that not only function calls, but also things like pattern matching add
   frames to the stack and there is no guarantee that the amount of frames that
   need to be skipped will not change between versions, so this method should be
   used carefully.
get_source_location : Integer -> Text
get_source_location skip_frames =
    get_source_location_builtin skip_frames

## PRIVATE

   Returns a Text representing the source location of a stack frame above
   the call.

   Arguments:
   - frames_to_skip: how many frames on the stack to skip. Called with 0
     will return exact location of the call.
get_source_location_builtin : Integer -> Text
get_source_location_builtin frames_to_skip = @Builtin_Method "Meta.get_source_location_builtin"

## PRIVATE

   Displays the type of the provided value as text.

   Arguments:
   - value: The value for which to display the type.
get_simple_type_name : Any -> Text
get_simple_type_name value = @Builtin_Method "Meta.get_simple_type_name"

## PRIVATE

   Returns the fully qualified type name of the given value.

   Arguments:
   - value: the value to get the type of.
get_qualified_type_name : Any -> Text
get_qualified_type_name value = @Builtin_Method "Meta.get_qualified_type_name"

## PRIVATE
   Returns a short name of a type (the last part of its qualified name).

   Arguments:
   - typ: the type to get the short name of.
get_short_type_name : Any -> Text
get_short_type_name typ = @Builtin_Method "Meta.get_short_type_name"

## PRIVATE
   Returns the type that this constructor is a part of.

   Arguments:
   - constructor: the constructor to get the declaring type of.
get_constructor_declaring_type : Any -> Any
get_constructor_declaring_type constructor = @Builtin_Method "Meta.get_constructor_declaring_type"

## PRIVATE
instrumentor_builtin op args = @Builtin_Method "Meta.instrumentor_builtin"

## PRIVATE
   ADVANCED

   Builder to create instrumentation for a function
type Instrumentor
    ## PRIVATE
    Value impl

    ## PRIVATE
    uuid id:Text -> Any = instrumentor_builtin "uuid" id

    ## PRIVATE
       ADVANCED

       Registers callback to be executed at the begining of node/expression
       execution. The callback `fn` gets UUID of the node/expression that is
       being executed and can return `Nothing` to continue regular execution
       or anything else to skip the execution and just return given value.

       Arguments:
       - fn: The callback function accepting UUID.
    on_enter self (fn : Text -> Any | Nothing) =
        new = instrumentor_builtin "onEnter" [ self.impl, fn ]
        Instrumentor.Value new

    ## PRIVATE
       ADVANCED

       Registers callback to be executed when a node/expression evaluation
       is over. The callback `fn` gets UUID and the computed value (or value
       of `expression` if specified). Usually
       the value is _cached_ and returned from `on_enter` callback next time
       the same expression is evaluated.

       > Example
         Specify `expression` to _"inline evaluate"_ it.
            see_a_b uuid:Text ~result =
              if uuid == "expected-uuid" then
                IO.println "evalutated to "+result.to_text

            Meta.meta .fn . instrument . on_return fn=see_a_b expression="a+b" . activate

       Arguments:
       - fn: The callback function accepting UUID and computed value
       - expression: Expression to evaluate on_return - by default Nothing -
         e.g. to provide the return value of the function
    on_return self (fn : Text -> Any -> Nothing) expression:(Text | Nothing)=Nothing =
        new = instrumentor_builtin "onReturn" [ self.impl, fn, expression ]
        Instrumentor.Value new

    ## PRIVATE
       ADVANCED

       Registers callback to be executed when a node/expression representing function is about to be called.
       The callback `fn` shall accept three arguments. The UUID to identify the expression, the function to be
       invoked and the arguments to pass to the function. The callback can return `Nothing`
       (in such case the function gets executed with provided arguments) or some other value,
       which is then returned instead of calling the function.

       Arguments:
       - fn: The callback function accepting UUID and function value
    on_call self (fn : Text -> Function -> Vector Any -> Any | Nothing) =
        new = instrumentor_builtin "onCall" [ self.impl, fn ]
        Instrumentor.Value new

    ## PRIVATE
       ADVANCED

       Activates configured instrumentor. Returns managed resource to
       deactivate the instrumentor later.

       Arguments:
       - value: The value of the atom in the meta representation.
    activate self =
        finalize_instrumentor impl = instrumentor_builtin "deactivate" [ impl ]
        create action = (instrumentor_builtin action [ self.impl, finalize_instrumentor ]) : Managed_Resource
        create "activate"

## PRIVATE
   Returns type specified by fully qualified name.

   Arguments:
   - fqn: fully qualified name.
find_type_by_qualified_name : Text -> Any
find_type_by_qualified_name fqn = @Builtin_Method "Meta.find_type_by_qualified_name"