# frozen_string_literal: true

# Creates a new instance/object of a given data type.
#
# This function makes it possible to create new instances of
# concrete data types. If a block is given it is called with the
# just created instance as an argument.
#
# Calling this function is equivalent to directly
# calling the data type:
#
# @example `new` and calling type directly are equivalent
#
# ```puppet
# $a = Integer.new("42")
# $b = Integer("42")
# ```
#
# These would both convert the string `"42"` to the decimal value `42`.
#
# @example arguments by position or by name
#
# ```puppet
# $a = Integer.new("42", 8)
# $b = Integer({from => "42", radix => 8})
# ```
#
# This would convert the octal (radix 8) number `"42"` in string form
# to the decimal value `34`.
#
# The new function supports two ways of giving the arguments:
#
# * by name (using a hash with property to value mapping)
# * by position (as regular arguments)
#
# Note that it is not possible to create new instances of
# some abstract data types (for example `Variant`). The data type `Optional[T]` is an
# exception as it will create an instance of `T` or `undef` if the
# value to convert is `undef`.
#
# The arguments that can be given is determined by the data type.
#
# > An assertion is always made that the produced value complies with the given type constraints.
#
# @example data type constraints are checked
#
# ```puppet
# Integer[0].new("-100")
# ```
#
# Would fail with an assertion error (since value is less than 0).
#
# The following sections show the arguments and conversion rules
# per data type built into the Puppet Type System.
#
# ### Conversion to `Optional[T]` and `NotUndef[T]`
#
# Conversion to these data types is the same as a conversion to the type argument `T`.
# In the case of `Optional[T]` it is accepted that the argument to convert may be `undef`.
# It is however not acceptable to give other arguments (than `undef`) that cannot be
# converted to `T`.
#
# ### Conversion to Integer
#
# A new `Integer` can be created from `Integer`, `Float`, `Boolean`, and `String` values.
# For conversion from `String` it is possible to specify the radix (base).
#
# ```puppet
# type Radix = Variant[Default, Integer[2,2], Integer[8,8], Integer[10,10], Integer[16,16]]
#
# function Integer.new(
#   String $value,
#   Radix $radix = 10,
#   Boolean $abs = false
# )
#
# function Integer.new(
#   Variant[Numeric, Boolean] $value,
#   Boolean $abs = false
# )
# ```
#
# * When converting from `String` the default radix is 10.
# * If radix is not specified an attempt is made to detect the radix from the start of the string:
#   * `0b` or `0B` is taken as radix 2.
#   * `0x` or `0X` is taken as radix 16.
#   * `0` as radix 8.
#   * All others are decimal.
# * Conversion from `String` accepts an optional sign in the string.
# * For hexadecimal (radix 16) conversion an optional leading `"0x"`, or `"0X"` is accepted.
# * For octal (radix 8) an optional leading `"0"` is accepted.
# * For binary (radix 2) an optional leading `"0b"` or `"0B"` is accepted.
# * When `radix` is set to `default`, the conversion is based on the leading.
#   characters in the string. A leading `"0"` for radix 8, a leading `"0x"`, or `"0X"` for
#   radix 16, and leading `"0b"` or `"0B"` for binary.
# * Conversion from `Boolean` results in `0` for `false` and `1` for `true`.
# * Conversion from `Integer`, `Float`, and `Boolean` ignores the radix.
# * `Float` value fractions are truncated (no rounding).
# * When `abs` is set to `true`, the result will be an absolute integer.
#
# @example Converting to Integer in multiple ways
#
# ```puppet
# $a_number = Integer("0xFF", 16)    # results in 255
# $a_number = Integer("010")         # results in 8
# $a_number = Integer("010", 10)     # results in 10
# $a_number = Integer(true)          # results in 1
# $a_number = Integer(-38, 10, true) # results in 38
# ```
#
# ### Conversion to Float
#
# A new `Float` can be created from `Integer`, `Float`, `Boolean`, and `String` values.
# For conversion from `String` both float and integer formats are supported.
#
# ```puppet
# function Float.new(
#   Variant[Numeric, Boolean, String] $value,
#   Boolean $abs = true
# )
# ```
#
# * For an integer, the floating point fraction of `.0` is added to the value.
# * A `Boolean` `true` is converted to `1.0`, and a `false` to `0.0`.
# * In `String` format, integer prefixes for hex and binary are understood (but not octal since
#   floating point in string format may start with a `'0'`).
# * When `abs` is set to `true`, the result will be an absolute floating point value.
#
# ### Conversion to Numeric
#
# A new `Integer` or `Float` can be created from `Integer`, `Float`, `Boolean` and
# `String` values.
#
# ```puppet
# function Numeric.new(
#   Variant[Numeric, Boolean, String] $value,
#   Boolean $abs = true
# )
# ```
#
# * If the value has a decimal period, or if given in scientific notation
#   (e/E), the result is a `Float`, otherwise the value is an `Integer`. The
#   conversion from `String` always uses a radix based on the prefix of the string.
# * Conversion from `Boolean` results in `0` for `false` and `1` for `true`.
# * When `abs` is set to `true`, the result will be an absolute `Float`or `Integer` value.
#
# @example Converting to Numeric in different ways
#
# ```puppet
# $a_number = Numeric(true)        # results in 1
# $a_number = Numeric("0xFF")      # results in 255
# $a_number = Numeric("010")       # results in 8
# $a_number = Numeric("3.14")      # results in 3.14 (a float)
# $a_number = Numeric(-42.3, true) # results in 42.3
# $a_number = Numeric(-42, true)   # results in 42
# ```
#
# ### Conversion to Timespan
#
# A new `Timespan` can be created from `Integer`, `Float`, `String`, and `Hash` values. Several variants of the constructor are provided.
#
# **Timespan from seconds**
#
# When a Float is used, the decimal part represents fractions of a second.
#
# ```puppet
# function Timespan.new(
#   Variant[Float, Integer] $value
# )
# ```
#
# **Timespan from days, hours, minutes, seconds, and fractions of a second**
#
# The arguments can be passed separately in which case the first four, days, hours, minutes, and seconds are mandatory and the rest are optional.
# All values may overflow and/or be negative. The internal 128-bit nano-second integer is calculated as:
#
# ```
# (((((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds
# ```
#
# ```puppet
# function Timespan.new(
#   Integer $days, Integer $hours, Integer $minutes, Integer $seconds,
#   Integer $milliseconds = 0, Integer $microseconds = 0, Integer $nanoseconds = 0
# )
# ```
#
# or, all arguments can be passed as a `Hash`, in which case all entries are optional:
#
# ```puppet
# function Timespan.new(
#   Struct[{
#     Optional[negative] => Boolean,
#     Optional[days] => Integer,
#     Optional[hours] => Integer,
#     Optional[minutes] => Integer,
#     Optional[seconds] => Integer,
#     Optional[milliseconds] => Integer,
#     Optional[microseconds] => Integer,
#     Optional[nanoseconds] => Integer
#   }] $hash
# )
# ```
#
# **Timespan from String and format directive patterns**
#
# The first argument is parsed using the format optionally passed as a string or array of strings. When an array is used, an attempt
# will be made to parse the string using the first entry and then with each entry in succession until parsing succeeds. If the second
# argument is omitted, an array of default formats will be used.
#
# An exception is raised when no format was able to parse the given string.
#
# ```puppet
# function Timespan.new(
#   String $string, Variant[String[2],Array[String[2], 1]] $format = <default format>)
# )
# ```
#
# the arguments may also be passed as a `Hash`:
#
# ```puppet
# function Timespan.new(
#   Struct[{
#     string => String[1],
#     Optional[format] => Variant[String[2],Array[String[2], 1]]
#   }] $hash
# )
# ```
#
# The directive consists of a percent (`%`) character, zero or more flags, optional minimum field width and
# a conversion specifier as follows:
# ```
# %[Flags][Width]Conversion
# ```
#
# ##### Flags:
#
# | Flag  | Meaning
# | ----  | ---------------
# | -     | Don't pad numerical output
# | _     | Use spaces for padding
# | 0     | Use zeros for padding
#
# ##### Format directives:
#
# | Format | Meaning |
# | ------ | ------- |
# | D | Number of Days |
# | H | Hour of the day, 24-hour clock |
# | M | Minute of the hour (00..59) |
# | S | Second of the minute (00..59) |
# | L | Millisecond of the second (000..999) |
# | N | Fractional seconds digits |
#
# The format directive that represents the highest magnitude in the format will be allowed to
# overflow. I.e. if no "%D" is used but a "%H" is present, then the hours may be more than 23.
#
# The default array contains the following patterns:
#
# ```
# ['%D-%H:%M:%S', '%D-%H:%M', '%H:%M:%S', '%H:%M']
# ```
#
# Examples - Converting to Timespan
#
# ```puppet
# $duration = Timespan(13.5)       # 13 seconds and 500 milliseconds
# $duration = Timespan({days=>4})  # 4 days
# $duration = Timespan(4, 0, 0, 2) # 4 days and 2 seconds
# $duration = Timespan('13:20')    # 13 hours and 20 minutes (using default pattern)
# $duration = Timespan('10:03.5', '%M:%S.%L') # 10 minutes, 3 seconds, and 5 milli-seconds
# $duration = Timespan('10:03.5', '%M:%S.%N') # 10 minutes, 3 seconds, and 5 nano-seconds
# ```
#
# ### Conversion to Timestamp
#
# A new `Timestamp` can be created from `Integer`, `Float`, `String`, and `Hash` values. Several variants of the constructor are provided.
#
# **Timestamp from seconds since epoch (1970-01-01 00:00:00 UTC)**
#
# When a Float is used, the decimal part represents fractions of a second.
#
# ```puppet
# function Timestamp.new(
#   Variant[Float, Integer] $value
# )
# ```
#
# **Timestamp from String and patterns consisting of format directives**
#
# The first argument is parsed using the format optionally passed as a string or array of strings. When an array is used, an attempt
# will be made to parse the string using the first entry and then with each entry in succession until parsing succeeds. If the second
# argument is omitted, an array of default formats will be used.
#
# A third optional timezone argument can be provided. The first argument will then be parsed as if it represents a local time in that
# timezone. The timezone can be any timezone that is recognized when using the `'%z'` or `'%Z'` formats, or the word `'current'`, in which
# case the current timezone of the evaluating process will be used. The timezone argument is case insensitive.
#
# The default timezone, when no argument is provided, or when using the keyword `default`, is 'UTC'.
#
# It is illegal to provide a timezone argument other than `default` in combination with a format that contains '%z' or '%Z' since that
# would introduce an ambiguity as to which timezone to use. The one extracted from the string, or the one provided as an argument.
#
# An exception is raised when no format was able to parse the given string.
#
# ```puppet
# function Timestamp.new(
#   String $string,
#   Variant[String[2],Array[String[2], 1]] $format = <default format>,
#   String $timezone = default)
# )
# ```
#
# the arguments may also be passed as a `Hash`:
#
# ```puppet
# function Timestamp.new(
#   Struct[{
#     string => String[1],
#     Optional[format] => Variant[String[2],Array[String[2], 1]],
#     Optional[timezone] => String[1]
#   }] $hash
# )
# ```
#
# The directive consists of a percent (%) character, zero or more flags, optional minimum field width and
# a conversion specifier as follows:
# ```
# %[Flags][Width]Conversion
# ```
#
# ##### Flags:
#
# | Flag  | Meaning
# | ----  | ---------------
# | -     | Don't pad numerical output
# | _     | Use spaces for padding
# | 0     | Use zeros for padding
# | #     | Change names to upper-case or change case of am/pm
# | ^     | Use uppercase
# | :     | Use colons for `%z`
#
# ##### Format directives (names and padding can be altered using flags):
#
# **Date (Year, Month, Day):**
#
# | Format | Meaning |
# | ------ | ------- |
# | Y | Year with century, zero-padded to at least 4 digits |
# | C | year / 100 (rounded down such as `20` in `2009`) |
# | y | year % 100 (`00..99`) |
# | m | Month of the year, zero-padded (`01..12`) |
# | B | The full month name (`"January"`) |
# | b | The abbreviated month name (`"Jan"`) |
# | h | Equivalent to `%b` |
# | d | Day of the month, zero-padded (`01..31`) |
# | e | Day of the month, blank-padded (`1..31`) |
# | j | Day of the year (`001..366`) |
#
# **Time (Hour, Minute, Second, Subsecond):**
#
# | Format | Meaning |
# | ------ | ------- |
# | H | Hour of the day, 24-hour clock, zero-padded (`00..23`) |
# | k | Hour of the day, 24-hour clock, blank-padded (`0..23`) |
# | I | Hour of the day, 12-hour clock, zero-padded (`01..12`) |
# | l | Hour of the day, 12-hour clock, blank-padded (`1..12`) |
# | P | Meridian indicator, lowercase (`"am"` or `"pm"`) |
# | p | Meridian indicator, uppercase (`"AM"` or `"PM"`) |
# | M | Minute of the hour (`00..59`) |
# | S | Second of the minute (`00..60`) |
# | L | Millisecond of the second (`000..999`). Digits under millisecond are truncated to not produce 1000 |
# | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified width are truncated to avoid carry up |
#
# **Time (Hour, Minute, Second, Subsecond):**
#
# | Format | Meaning |
# | ------ | ------- |
# | z   | Time zone as hour and minute offset from UTC (e.g. `+0900`) |
# | :z  | hour and minute offset from UTC with a colon (e.g. `+09:00`) |
# | ::z | hour, minute and second offset from UTC (e.g. `+09:00:00`) |
# | Z   | Abbreviated time zone name or similar information.  (OS dependent) |
#
# **Weekday:**
#
# | Format | Meaning |
# | ------ | ------- |
# | A | The full weekday name (`"Sunday"`) |
# | a | The abbreviated name (`"Sun"`) |
# | u | Day of the week (Monday is `1`, `1..7`) |
# | w | Day of the week (Sunday is `0`, `0..6`) |
#
# **ISO 8601 week-based year and week number:**
#
# The first week of YYYY starts with a Monday and includes YYYY-01-04.
# The days in the year before the first week are in the last week of
# the previous year.
#
# | Format | Meaning |
# | ------ | ------- |
# | G | The week-based year |
# | g | The last 2 digits of the week-based year (`00..99`) |
# | V | Week number of the week-based year (`01..53`) |
#
# **Week number:**
#
# The first week of YYYY that starts with a Sunday or Monday (according to %U
# or %W). The days in the year before the first week are in week 0.
#
# | Format | Meaning |
# | ------ | ------- |
# | U | Week number of the year. The week starts with Sunday. (`00..53`) |
# | W | Week number of the year. The week starts with Monday. (`00..53`) |
#
# **Seconds since the Epoch:**
#
# | Format | Meaning |
# | s | Number of seconds since 1970-01-01 00:00:00 UTC. |
#
# **Literal string:**
#
# | Format | Meaning |
# | ------ | ------- |
# | n | Newline character (`\n`) |
# | t | Tab character (`\t`) |
# | % | Literal `%` character |
#
# **Combination:**
#
# | Format | Meaning |
# | ------ | ------- |
# | c | date and time (`%a %b %e %T %Y`) |
# | D | Date (`%m/%d/%y`) |
# | F | The ISO 8601 date format (`%Y-%m-%d`) |
# | v | VMS date (`%e-%^b-%4Y`) |
# | x | Same as `%D` |
# | X | Same as `%T` |
# | r | 12-hour time (`%I:%M:%S %p`) |
# | R | 24-hour time (`%H:%M`) |
# | T | 24-hour time (`%H:%M:%S`) |
#
# The default array contains the following patterns:
#
# When a timezone argument (other than `default`) is explicitly provided:
#
# ```
# ['%FT%T.L', '%FT%T', '%F']
# ```
#
# otherwise:
#
# ```
# ['%FT%T.%L %Z', '%FT%T %Z', '%F %Z', '%FT%T.L', '%FT%T', '%F']
# ```
#
# Examples - Converting to Timestamp
#
# ```puppet
# $ts = Timestamp(1473150899)                              # 2016-09-06 08:34:59 UTC
# $ts = Timestamp({string=>'2015', format=>'%Y'})          # 2015-01-01 00:00:00.000 UTC
# $ts = Timestamp('Wed Aug 24 12:13:14 2016', '%c')        # 2016-08-24 12:13:14 UTC
# $ts = Timestamp('Wed Aug 24 12:13:14 2016 PDT', '%c %Z') # 2016-08-24 19:13:14.000 UTC
# $ts = Timestamp('2016-08-24 12:13:14', '%F %T', 'PST')   # 2016-08-24 20:13:14.000 UTC
# $ts = Timestamp('2016-08-24T12:13:14', default, 'PST')   # 2016-08-24 20:13:14.000 UTC
#
# ```
#
# ### Conversion to Type
#
# A new `Type` can be created from its `String` representation.
#
# @example Creating a type from a string
#
# ```puppet
# $t = Type.new('Integer[10]')
# ```
#
# ### Conversion to String
#
# Conversion to `String` is the most comprehensive conversion as there are many
# use cases where a string representation is wanted. The defaults for the many options
# have been chosen with care to be the most basic "value in textual form" representation.
# The more advanced forms of formatting are intended to enable writing special purposes formatting
# functions in the Puppet language.
#
# A new string can be created from all other data types. The process is performed in
# several steps - first the data type of the given value is inferred, then the resulting data type
# is used to find the most significant format specified for that data type. And finally,
# the found format is used to convert the given value.
#
# The mapping from data type to format is referred to as the *format map*. This map
# allows different formatting depending on type.
#
# @example Positive Integers in Hexadecimal prefixed with `'0x'`, negative in Decimal
#
# ```puppet
# $format_map = {
#   Integer[default, 0] => "%d",
#   Integer[1, default] => "%#x"
# }
# String("-1", $format_map)  # produces '-1'
# String("10", $format_map)  # produces '0xa'
# ```
#
# A format is specified on the form:
#
# ```
# %[Flags][Width][.Precision]Format
# ```
#
# `Width` is the number of characters into which the value should be fitted. This allocated space is
# padded if value is shorter. By default it is space padded, and the flag `0` will cause padding with `0`
# for numerical formats.
#
# `Precision` is the number of fractional digits to show for floating point, and the maximum characters
# included in a string format.
#
# Note that all data type supports the formats `s` and `p` with the meaning "default string representation" and
# "default programmatic string representation" (which for example means that a String is quoted in 'p' format).
#
# **Signatures of String conversion**
#
# ```puppet
# type Format = Pattern[/^%([\s\+\-#0\[\{<\(\|]*)([1-9][0-9]*)?(?:\.([0-9]+))?([a-zA-Z])/]
# type ContainerFormat = Struct[{
#   format         => Optional[String],
#   separator      => Optional[String],
#   separator2     => Optional[String],
#   string_formats => Hash[Type, Format]
#   }]
# type TypeMap = Hash[Type, Variant[Format, ContainerFormat]]
# type Formats = Variant[Default, String[1], TypeMap]
#
# function String.new(
#   Any $value,
#   Formats $string_formats
# )
# ```
#
# Where:
#
# * `separator` is the string used to separate entries in an array, or hash (extra space should not be included at
#   the end), defaults to `","`
# * `separator2` is the separator between key and value in a hash entry (space padding should be included as
#   wanted), defaults to `" => "`.
# * `string_formats` is a data type to format map for values contained in arrays and hashes - defaults to `{Any => "%p"}`. Note that
#   these nested formats are not applicable to data types that are containers; they are always formatted as per the top level
#   format specification.
#
# @example Simple Conversion to String (using defaults)
#
# ```puppet
# $str = String(10)      # produces '10'
# $str = String([10])    # produces '["10"]'
# ```
#
# @example Simple Conversion to String specifying the format for the given value directly
#
# ```puppet
# $str = String(10, "%#x")    # produces '0x10'
# $str = String([10], "%(a")  # produces '("10")'
# ```
#
# @example Specifying type for values contained in an array
#
# ```puppet
# $formats = {
#   Array => {
#     format => '%(a',
#     string_formats => { Integer => '%#x' }
#   }
# }
# $str = String([1,2,3], $formats) # produces '(0x1, 0x2, 0x3)'
# ```
#
# The given formats are merged with the default formats, and matching of values to convert against format is based on
# the specificity of the mapped type; for example, different formats can be used for short and long arrays.
#
# **Integer to String**
#
# | Format  | Integer Formats
# | ------  | ---------------
# | d       | Decimal, negative values produces leading `-`.
# | x X     | Hexadecimal in lower or upper case. Uses `..f/..F` for negative values unless `+` is also used. A `#` adds prefix `0x/0X`.
# | o       | Octal. Uses `..0` for negative values unless `+` is also used. A `#` adds prefix `0`.
# | b B     | Binary with prefix `b` or `B`. Uses `..1/..1` for negative values unless `+` is also used.
# | c       | Numeric value representing a Unicode value, result is a one unicode character string, quoted if alternative flag `#` is used
# | s       | Same as `d`, or `d` in quotes if alternative flag `#` is used.
# | p       | Same as `d`.
# | eEfgGaA | Converts integer to float and formats using the floating point rules.
#
# Defaults to `d`.
#
# **Float to String**
#
# | Format  | Float formats
# | ------  | -------------
# | f       | Floating point in non exponential notation.
# | e E     | Exponential notation with `e` or `E`.
# | g G     | Conditional exponential with `e` or `E` if exponent `< -4` or `>=` the precision.
# | a A     | Hexadecimal exponential form, using `x`/`X` as prefix and `p`/`P` before exponent.
# | s       | Converted to string using format `p`, then applying string formatting rule, alternate form `#`` quotes result.
# | p       | Same as `f` format with minimum significant number of fractional digits, prec has no effect.
# | dxXobBc | Converts float to integer and formats using the integer rules.
#
# Defaults to `p`.
#
# **String to String**
#
# | Format | String
# | ------ | ------
# | s      | Unquoted string, verbatim output of control chars.
# | p      | Programmatic representation - strings are quoted, interior quotes and control chars are escaped. Selects single or double quotes based on content, or uses double quotes if alternative flag `#` is used.
# | C      | Each `::` name segment capitalized, quoted if alternative flag `#` is used.
# | c      | Capitalized string, quoted if alternative flag `#` is used.
# | d      | Downcased string, quoted if alternative flag `#` is used.
# | u      | Upcased string, quoted if alternative flag `#` is used.
# | t      | Trims leading and trailing whitespace from the string, quoted if alternative flag `#` is used.
#
# Defaults to `s` at top level and `p` inside array or hash.
#
# **Boolean to String**
#
# | Format    | Boolean Formats
# | ----      | -------------------
# | t T       | String `'true'/'false'` or `'True'/'False'`, first char if alternate form is used (i.e. `'t'/'f'` or `'T'/'F'`).
# | y Y       | String `'yes'/'no'`, `'Yes'/'No'`, `'y'/'n'` or `'Y'/'N'` if alternative flag `#` is used.
# | dxXobB    | Numeric value `0/1` in accordance with the given format which must be valid integer format.
# | eEfgGaA   | Numeric value `0.0/1.0` in accordance with the given float format and flags.
# | s         | String `'true'` / `'false'`.
# | p         | String `'true'` / `'false'`.
#
# **Regexp to String**
#
# | Format    | Regexp Formats
# | ----      | --------------
# | s         | No delimiters, quoted if alternative flag `#` is used.
# | p         | Delimiters `/ /`.
#
# **Undef to String**
#
# | Format    | Undef formats
# | ------    | -------------
# | s         | Empty string, or quoted empty string if alternative flag `#` is used.
# | p         | String `'undef'`, or quoted `'"undef"'` if alternative flag `#` is used.
# | n         | String `'nil'`, or `'null'` if alternative flag `#` is used.
# | dxXobB    | String `'NaN'`.
# | eEfgGaA   | String `'NaN'`.
# | v         | String `'n/a'`.
# | V         | String `'N/A'`.
# | u         | String `'undef'`, or `'undefined'` if alternative `#` flag is used.
#
# **Default value to String**
#
# | Format    | Default formats
# | ------    | ---------------
# | d D       | String `'default'` or `'Default'`, alternative form `#` causes value to be quoted.
# | s         | Same as `d`.
# | p         | Same as `d`.
#
# **Binary value to String**
#
# | Format    | Default formats
# | ------    | ---------------
# | s         | binary as unquoted UTF-8 characters (errors if byte sequence is invalid UTF-8). Alternate form escapes non ascii bytes.
# | p         | `'Binary("<base64strict>")'`
# | b         | `'<base64>'` - base64 string with newlines inserted
# | B         | `'<base64strict>'` - base64 strict string (without newlines inserted)
# | u         | `'<base64urlsafe>'` - base64 urlsafe string
# | t         | `'Binary'` - outputs the name of the type only
# | T         | `'BINARY'` - output the name of the type in all caps only
#
# * The alternate form flag `#` will quote the binary or base64 text output.
# * The format `%#s` allows invalid UTF-8 characters and outputs all non ascii bytes
#   as hex escaped characters on the form `\\xHH` where `H` is a hex digit.
# * The width and precision values are applied to the text part only in `%p` format.
#
# **Array & Tuple to String**
#
# | Format    | Array/Tuple Formats
# | ------    | -------------
# | a         | Formats with `[ ]` delimiters and `,`, alternate form `#` indents nested arrays/hashes.
# | s         | Same as `a`.
# | p         | Same as `a`.
#
# See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for
# more information about options.
#
# The alternate form flag `#` will cause indentation of nested array or hash containers. If width is also set
# it is taken as the maximum allowed length of a sequence of elements (not including delimiters). If this max length
# is exceeded, each element will be indented.
#
# **Hash & Struct to String**
#
# | Format    | Hash/Struct Formats
# | ------    | -------------
# | h         | Formats with `{ }` delimiters, `,` element separator and ` => ` inner element separator unless overridden by flags.
# | s         | Same as h.
# | p         | Same as h.
# | a         | Converts the hash to an array of `[k,v]` tuples and formats it using array rule(s).
#
# See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for
# more information about options.
#
# The alternate form flag `#` will format each hash key/value entry indented on a separate line.
#
# **Type to String**
#
# | Format    | Array/Tuple Formats
# | ------    | -------------
# | s         | The same as `p`, quoted if alternative flag `#` is used.
# | p         | Outputs the type in string form as specified by the Puppet Language.
#
# **Flags**
#
# | Flag     | Effect
# | ------   | ------
# | (space)  | A space instead of `+` for numeric output (`-` is shown), for containers skips delimiters.
# | #        | Alternate format; prefix `0x/0x`, `0` (octal) and `0b/0B` for binary, Floats force decimal '.'. For g/G keep trailing `0`.
# | +        | Show sign `+/-` depending on value's sign, changes `x`, `X`, `o`, `b`, `B` format to not use 2's complement form.
# | -        | Left justify the value in the given width.
# | 0        | Pad with `0` instead of space for widths larger than value.
# | <[({\|   | Defines an enclosing pair `<> [] () {} or \| \|` when used with a container type.
#
# ### Conversion to Boolean
#
# Accepts a single value as argument:
#
# * Float `0.0` is `false`, all other float values are `true`
# * Integer `0` is `false`, all other integer values are `true`
# * Strings
#   * `true` if 'true', 'yes', 'y' (case independent compare)
#   * `false` if 'false', 'no', 'n' (case independent compare)
# * Boolean is already boolean and is simply returned
#
# ### Conversion to Array and Tuple
#
# When given a single value as argument:
#
# * A non empty `Hash` is converted to an array matching `Array[Tuple[Any,Any], 1]`.
# * An empty `Hash` becomes an empty array.
# * An `Array` is simply returned.
# * An `Iterable[T]` is turned into an array of `T` instances.
# * A `Binary` is converted to an `Array[Integer[0,255]]` of byte values
#
# When given a second Boolean argument:
#
# * if `true`, a value that is not already an array is returned as a one element array.
# * if `false`, (the default), converts the first argument as shown above.
#
# @example Ensuring value is an array
#
# ```puppet
# $arr = Array($value, true)
# ```
#
# Conversion to a `Tuple` works exactly as conversion to an `Array`, only that the constructed array is
# asserted against the given tuple type.
#
# ### Conversion to Hash and Struct
#
# Accepts a single value as argument:
#
# * An empty `Array` becomes an empty `Hash`
# * An `Array` matching `Array[Tuple[Any,Any], 1]` is converted to a hash where each tuple describes a key/value entry
# * An `Array` with an even number of entries is interpreted as `[key1, val1, key2, val2, ...]`
# * An `Iterable` is turned into an `Array` and then converted to hash as per the array rules
# * A `Hash` is simply returned
#
# Alternatively, a tree can be constructed by giving two values; an array of tuples on the form `[path, value]`
# (where the `path` is the path from the root of a tree, and `value` the value at that position in the tree), and
# either the option `'tree'` (do not convert arrays to hashes except the top level), or
# `'hash_tree'` (convert all arrays to hashes).
#
# The tree/hash_tree forms of Hash creation are suited for transforming the result of an iteration
# using `tree_each` and subsequent filtering or mapping.
#
# @example Mapping a hash tree
#
# Mapping an arbitrary structure in a way that keeps the structure, but where some values are replaced
# can be done by using the `tree_each` function, mapping, and then constructing a new Hash from the result:
#
# ```puppet
# # A hash tree with 'water' at different locations
# $h = { a => { b => { x => 'water'}}, b => { y => 'water'} }
# # a helper function that turns water into wine
# function make_wine($x) { if $x == 'water' { 'wine' } else { $x } }
# # create a flattened tree with water turned into wine
# $flat_tree = $h.tree_each.map |$entry| { [$entry[0], make_wine($entry[1])] }
# # create a new Hash and log it
# notice Hash($flat_tree, 'hash_tree')
# ```
#
# Would notice the hash `{a => {b => {x => wine}}, b => {y => wine}}`
#
# Conversion to a `Struct` works exactly as conversion to a `Hash`, only that the constructed hash is
# asserted against the given struct type.
#
# ### Conversion to a Regexp
#
# A `String` can be converted into a `Regexp`
#
# **Example**: Converting a String into a Regexp
# ```puppet
# $s = '[a-z]+\.com'
# $r = Regexp($s)
# if('foo.com' =~ $r) {
#   ...
# }
# ```
#
# ### Creating a SemVer
#
# A SemVer object represents a single [Semantic Version](http://semver.org/).
# It can be created from a String, individual values for its parts, or a hash specifying the value per part.
# See the specification at [semver.org](http://semver.org/) for the meaning of the SemVer's parts.
#
# The signatures are:
#
# ```puppet
# type PositiveInteger = Integer[0,default]
# type SemVerQualifier = Pattern[/\A(?<part>[0-9A-Za-z-]+)(?:\.\g<part>)*\Z/]
# type SemVerString = String[1]
# type SemVerHash =Struct[{
#   major                => PositiveInteger,
#   minor                => PositiveInteger,
#   patch                => PositiveInteger,
#   Optional[prerelease] => SemVerQualifier,
#   Optional[build]      => SemVerQualifier
# }]
#
# function SemVer.new(SemVerString $str)
#
# function SemVer.new(
#         PositiveInteger           $major
#         PositiveInteger           $minor
#         PositiveInteger           $patch
#         Optional[SemVerQualifier] $prerelease = undef
#         Optional[SemVerQualifier] $build = undef
#         )
#
# function SemVer.new(SemVerHash $hash_args)
# ```
#
# @example `SemVer` and `SemVerRange` usage
#
# ```puppet
# # As a type, SemVer can describe disjunct ranges which versions can be
# # matched against - here the type is constructed with two
# # SemVerRange objects.
# #
# $t = SemVer[
#   SemVerRange('>=1.0.0 <2.0.0'),
#   SemVerRange('>=3.0.0 <4.0.0')
# ]
# notice(SemVer('1.2.3') =~ $t) # true
# notice(SemVer('2.3.4') =~ $t) # false
# notice(SemVer('3.4.5') =~ $t) # true
# ```
#
# ### Creating a `SemVerRange`
#
# A `SemVerRange` object represents a range of `SemVer`. It can be created from
# a `String`, or from two `SemVer` instances, where either end can be given as
# a literal `default` to indicate infinity. The string format of a `SemVerRange` is specified by
# the [Semantic Version Range Grammar](https://github.com/npm/node-semver#ranges).
#
# > Use of the comparator sets described in the grammar (joining with `||`) is not supported.
#
# The signatures are:
#
# ```puppet
# type SemVerRangeString = String[1]
# type SemVerRangeHash = Struct[{
#   min                   => Variant[Default, SemVer],
#   Optional[max]         => Variant[Default, SemVer],
#   Optional[exclude_max] => Boolean
# }]
#
# function SemVerRange.new(
#   SemVerRangeString $semver_range_string
# )
#
# function SemVerRange.new(
#   Variant[Default,SemVer] $min
#   Variant[Default,SemVer] $max
#   Optional[Boolean]       $exclude_max = undef
# )
#
# function SemVerRange.new(
#   SemVerRangeHash $semver_range_hash
# )
# ```
#
# For examples of `SemVerRange` use see "Creating a SemVer"
#
# ### Creating a Binary
#
# A `Binary` object represents a sequence of bytes and it can be created from a String in Base64 format,
# an Array containing byte values. A Binary can also be created from a Hash containing the value to convert to
# a `Binary`.
#
# The signatures are:
#
# ```puppet
# type ByteInteger = Integer[0,255]
# type Base64Format = Enum["%b", "%u", "%B", "%s"]
# type StringHash = Struct[{value => String, "format" => Optional[Base64Format]}]
# type ArrayHash = Struct[{value => Array[ByteInteger]}]
# type BinaryArgsHash = Variant[StringHash, ArrayHash]
#
# function Binary.new(
#   String $base64_str,
#   Optional[Base64Format] $format
# )
#
#
# function Binary.new(
#   Array[ByteInteger] $byte_array
# }
#
# # Same as for String, or for Array, but where arguments are given in a Hash.
# function Binary.new(BinaryArgsHash $hash_args)
# ```
#
# The formats have the following meaning:
#
# | format | explanation |
# | ----   | ----        |
# | B | The data is in base64 strict encoding
# | u | The data is in URL safe base64 encoding
# | b | The data is in base64 encoding, padding as required by base64 strict, is added by default
# | s | The data is a puppet string. The string must be valid UTF-8, or convertible to UTF-8 or an error is raised.
# | r | (Ruby Raw) the byte sequence in the given string is used verbatim irrespective of possible encoding errors
#
# * The default format is `%B`.
# * Note that the format `%r` should be used sparingly, or not at all. It exists for backwards compatibility reasons when someone receiving
#   a string from some function and that string should be treated as Binary. Such code should be changed to return a Binary instead of a String.
#
# @example Creating a Binary
#
# ```puppet
# # create the binary content "abc"
# $a = Binary('YWJj')
#
# # create the binary content from content in a module's file
# $b = binary_file('mymodule/mypicture.jpg')
# ```
#
# * Since 4.5.0
# * Binary type since 4.8.0
#
# ### Creating an instance of a `Type` using the `Init` type
#
# The type `Init[T]` describes a value that can be used when instantiating a type. When used as the first argument in a call to `new`, it
# will dispatch the call to its contained type and optionally augment the parameter list with additional arguments.
#
# @example Creating an instance of Integer using Init[Integer]
#
# ```puppet
# # The following declaration
# $x = Init[Integer].new('128')
# # is exactly the same as
# $x = Integer.new('128')
# ```
#
# or, with base 16 and using implicit new
#
# ```puppet
# # The following declaration
# $x = Init[Integer,16]('80')
# # is exactly the same as
# $x = Integer('80', 16)
# ```
#
# @example Creating an instance of String using a predefined format
#
# ```puppet
# $fmt = Init[String,'%#x']
# notice($fmt(256)) # will notice '0x100'
# ```
#
# @since 4.5.0
#
Puppet::Functions.create_function(:new, Puppet::Functions::InternalFunction) do
  dispatch :new_instance do
    scope_param
    param          'Type', :type
    repeated_param 'Any',  :args
    optional_block_param
  end

  def new_instance(scope, t, *args)
    return args[0] if args.size == 1 && !t.is_a?(Puppet::Pops::Types::PInitType) && t.instance?(args[0])

    result = assert_type(t, new_function_for_type(t).call(scope, *args))
    return block_given? ? yield(result) : result
  end

  def new_function_for_type(t)
    @new_function_cache ||= {}

    unless @new_function_cache.key?(t)
      @new_function_cache[t] = t.new_function.new(nil, loader)
    end

    @new_function_cache[t]
  end

  def assert_type(type, value)
    Puppet::Pops::Types::TypeAsserter.assert_instance_of(['Converted value from %s.new()', type], type, value)
  end
end
