text:
# This file is auto-generated by AWSMetadata.jl
using AWS
using AWS.AWSServices: elasticsearch_service
using AWS.Compat
using AWS.UUIDs

"""
    accept_inbound_cross_cluster_search_connection(connection_id)
    accept_inbound_cross_cluster_search_connection(connection_id, params::Dict{String,<:Any})

Allows the destination domain owner to accept an inbound cross-cluster search connection
request.

# Arguments
- `connection_id`: The id of the inbound connection that you want to accept.

"""
accept_inbound_cross_cluster_search_connection(ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("PUT", "/2015-01-01/es/ccs/inboundConnection/$(ConnectionId)/accept"; aws_config=aws_config)
accept_inbound_cross_cluster_search_connection(ConnectionId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("PUT", "/2015-01-01/es/ccs/inboundConnection/$(ConnectionId)/accept", params; aws_config=aws_config)

"""
    add_tags(arn, tag_list)
    add_tags(arn, tag_list, params::Dict{String,<:Any})

Attaches tags to an existing Elasticsearch domain. Tags are a set of case-sensitive key
value pairs. An Elasticsearch domain may have up to 10 tags. See  Tagging Amazon
Elasticsearch Service Domains for more information.

# Arguments
- `arn`:  Specify the ARN for which you want to add the tags.
- `tag_list`:  List of Tag that need to be added for the Elasticsearch domain.

"""
add_tags(ARN, TagList; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/tags", Dict{String, Any}("ARN"=>ARN, "TagList"=>TagList); aws_config=aws_config)
add_tags(ARN, TagList, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/tags", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ARN"=>ARN, "TagList"=>TagList), params)); aws_config=aws_config)

"""
    associate_package(domain_name, package_id)
    associate_package(domain_name, package_id, params::Dict{String,<:Any})

Associates a package with an Amazon ES domain.

# Arguments
- `domain_name`: Name of the domain that you want to associate the package with.
- `package_id`: Internal ID of the package that you want to associate with a domain. Use
  DescribePackages to find this value.

"""
associate_package(DomainName, PackageID; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/associate/$(PackageID)/$(DomainName)"; aws_config=aws_config)
associate_package(DomainName, PackageID, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/associate/$(PackageID)/$(DomainName)", params; aws_config=aws_config)

"""
    cancel_elasticsearch_service_software_update(domain_name)
    cancel_elasticsearch_service_software_update(domain_name, params::Dict{String,<:Any})

Cancels a scheduled service software update for an Amazon ES domain. You can only perform
this operation before the AutomatedUpdateDate and when the UpdateStatus is in the
PENDING_UPDATE state.

# Arguments
- `domain_name`: The name of the domain that you want to stop the latest service software
  update on.

"""
cancel_elasticsearch_service_software_update(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/serviceSoftwareUpdate/cancel", Dict{String, Any}("DomainName"=>DomainName); aws_config=aws_config)
cancel_elasticsearch_service_software_update(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/serviceSoftwareUpdate/cancel", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("DomainName"=>DomainName), params)); aws_config=aws_config)

"""
    create_elasticsearch_domain(domain_name)
    create_elasticsearch_domain(domain_name, params::Dict{String,<:Any})

Creates a new Elasticsearch domain. For more information, see Creating Elasticsearch
Domains in the Amazon Elasticsearch Service Developer Guide.

# Arguments
- `domain_name`: The name of the Elasticsearch domain that you are creating. Domain names
  are unique across the domains owned by an account within an AWS region. Domain names must
  start with a lowercase letter and can contain the following characters: a-z (lowercase),
  0-9, and - (hyphen).

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccessPolicies"`:  IAM access policy as a JSON-formatted string.
- `"AdvancedOptions"`:  Option to allow references to indices in an HTTP request body. Must
  be false when configuring access to individual sub-resources. By default, the value is
  true. See Configuration Advanced Options for more information.
- `"AdvancedSecurityOptions"`: Specifies advanced security options.
- `"AutoTuneOptions"`: Specifies Auto-Tune options.
- `"CognitoOptions"`: Options to specify the Cognito user and identity pools for Kibana
  authentication. For more information, see Amazon Cognito Authentication for Kibana.
- `"DomainEndpointOptions"`: Options to specify configuration that will be applied to the
  domain endpoint.
- `"EBSOptions"`: Options to enable, disable and specify the type and size of EBS storage
  volumes.
- `"ElasticsearchClusterConfig"`: Configuration options for an Elasticsearch domain.
  Specifies the instance type and number of instances in the domain cluster.
- `"ElasticsearchVersion"`: String of format X.Y to specify version for the Elasticsearch
  domain eg. \"1.5\" or \"2.3\". For more information, see Creating Elasticsearch Domains in
  the Amazon Elasticsearch Service Developer Guide.
- `"EncryptionAtRestOptions"`: Specifies the Encryption At Rest Options.
- `"LogPublishingOptions"`: Map of LogType and LogPublishingOption, each containing options
  to publish a given type of Elasticsearch log.
- `"NodeToNodeEncryptionOptions"`: Specifies the NodeToNodeEncryptionOptions.
- `"SnapshotOptions"`: Option to set time, in UTC format, of the daily automated snapshot.
  Default value is 0 hours.
- `"TagList"`: A list of Tag added during domain creation.
- `"VPCOptions"`: Options to specify the subnets and security groups for VPC endpoint. For
  more information, see Creating a VPC in VPC Endpoints for Amazon Elasticsearch Service
  Domains
"""
create_elasticsearch_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/domain", Dict{String, Any}("DomainName"=>DomainName); aws_config=aws_config)
create_elasticsearch_domain(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/domain", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("DomainName"=>DomainName), params)); aws_config=aws_config)

"""
    create_outbound_cross_cluster_search_connection(connection_alias, destination_domain_info, source_domain_info)
    create_outbound_cross_cluster_search_connection(connection_alias, destination_domain_info, source_domain_info, params::Dict{String,<:Any})

Creates a new cross-cluster search connection from a source domain to a destination domain.

# Arguments
- `connection_alias`: Specifies the connection alias that will be used by the customer for
  this connection.
- `destination_domain_info`: Specifies the DomainInformation for the destination
  Elasticsearch domain.
- `source_domain_info`: Specifies the DomainInformation for the source Elasticsearch domain.

"""
create_outbound_cross_cluster_search_connection(ConnectionAlias, DestinationDomainInfo, SourceDomainInfo; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/ccs/outboundConnection", Dict{String, Any}("ConnectionAlias"=>ConnectionAlias, "DestinationDomainInfo"=>DestinationDomainInfo, "SourceDomainInfo"=>SourceDomainInfo); aws_config=aws_config)
create_outbound_cross_cluster_search_connection(ConnectionAlias, DestinationDomainInfo, SourceDomainInfo, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/ccs/outboundConnection", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ConnectionAlias"=>ConnectionAlias, "DestinationDomainInfo"=>DestinationDomainInfo, "SourceDomainInfo"=>SourceDomainInfo), params)); aws_config=aws_config)

"""
    create_package(package_name, package_source, package_type)
    create_package(package_name, package_source, package_type, params::Dict{String,<:Any})

Create a package for use with Amazon ES domains.

# Arguments
- `package_name`: Unique identifier for the package.
- `package_source`: The customer S3 location PackageSource for importing the package.
- `package_type`: Type of package. Currently supports only TXT-DICTIONARY.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"PackageDescription"`: Description of the package.
"""
create_package(PackageName, PackageSource, PackageType; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages", Dict{String, Any}("PackageName"=>PackageName, "PackageSource"=>PackageSource, "PackageType"=>PackageType); aws_config=aws_config)
create_package(PackageName, PackageSource, PackageType, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("PackageName"=>PackageName, "PackageSource"=>PackageSource, "PackageType"=>PackageType), params)); aws_config=aws_config)

"""
    delete_elasticsearch_domain(domain_name)
    delete_elasticsearch_domain(domain_name, params::Dict{String,<:Any})

Permanently deletes the specified Elasticsearch domain and all of its data. Once a domain
is deleted, it cannot be recovered.

# Arguments
- `domain_name`: The name of the Elasticsearch domain that you want to permanently delete.

"""
delete_elasticsearch_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/domain/$(DomainName)"; aws_config=aws_config)
delete_elasticsearch_domain(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/domain/$(DomainName)", params; aws_config=aws_config)

"""
    delete_elasticsearch_service_role()
    delete_elasticsearch_service_role(params::Dict{String,<:Any})

Deletes the service-linked role that Elasticsearch Service uses to manage and maintain VPC
domains. Role deletion will fail if any existing VPC domains use the role. You must delete
any such Elasticsearch domains before deleting the role. See Deleting Elasticsearch Service
Role in VPC Endpoints for Amazon Elasticsearch Service Domains.

"""
delete_elasticsearch_service_role(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/role"; aws_config=aws_config)
delete_elasticsearch_service_role(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/role", params; aws_config=aws_config)

"""
    delete_inbound_cross_cluster_search_connection(connection_id)
    delete_inbound_cross_cluster_search_connection(connection_id, params::Dict{String,<:Any})

Allows the destination domain owner to delete an existing inbound cross-cluster search
connection.

# Arguments
- `connection_id`: The id of the inbound connection that you want to permanently delete.

"""
delete_inbound_cross_cluster_search_connection(ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/ccs/inboundConnection/$(ConnectionId)"; aws_config=aws_config)
delete_inbound_cross_cluster_search_connection(ConnectionId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/ccs/inboundConnection/$(ConnectionId)", params; aws_config=aws_config)

"""
    delete_outbound_cross_cluster_search_connection(connection_id)
    delete_outbound_cross_cluster_search_connection(connection_id, params::Dict{String,<:Any})

Allows the source domain owner to delete an existing outbound cross-cluster search
connection.

# Arguments
- `connection_id`: The id of the outbound connection that you want to permanently delete.

"""
delete_outbound_cross_cluster_search_connection(ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/ccs/outboundConnection/$(ConnectionId)"; aws_config=aws_config)
delete_outbound_cross_cluster_search_connection(ConnectionId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/es/ccs/outboundConnection/$(ConnectionId)", params; aws_config=aws_config)

"""
    delete_package(package_id)
    delete_package(package_id, params::Dict{String,<:Any})

Delete the package.

# Arguments
- `package_id`: Internal ID of the package that you want to delete. Use DescribePackages to
  find this value.

"""
delete_package(PackageID; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/packages/$(PackageID)"; aws_config=aws_config)
delete_package(PackageID, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("DELETE", "/2015-01-01/packages/$(PackageID)", params; aws_config=aws_config)

"""
    describe_domain_auto_tunes(domain_name)
    describe_domain_auto_tunes(domain_name, params::Dict{String,<:Any})

Provides scheduled Auto-Tune action details for the Elasticsearch domain, such as Auto-Tune
action type, description, severity, and scheduled date.

# Arguments
- `domain_name`: Specifies the domain name for which you want Auto-Tune action details.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"NextToken"`: NextToken is sent in case the earlier API call results contain the
  NextToken. It is used for pagination.
"""
describe_domain_auto_tunes(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/domain/$(DomainName)/autoTunes"; aws_config=aws_config)
describe_domain_auto_tunes(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/domain/$(DomainName)/autoTunes", params; aws_config=aws_config)

"""
    describe_elasticsearch_domain(domain_name)
    describe_elasticsearch_domain(domain_name, params::Dict{String,<:Any})

Returns domain configuration information about the specified Elasticsearch domain,
including the domain ID, domain endpoint, and domain ARN.

# Arguments
- `domain_name`: The name of the Elasticsearch domain for which you want information.

"""
describe_elasticsearch_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/domain/$(DomainName)"; aws_config=aws_config)
describe_elasticsearch_domain(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/domain/$(DomainName)", params; aws_config=aws_config)

"""
    describe_elasticsearch_domain_config(domain_name)
    describe_elasticsearch_domain_config(domain_name, params::Dict{String,<:Any})

Provides cluster configuration information about the specified Elasticsearch domain, such
as the state, creation date, update version, and update date for cluster options.

# Arguments
- `domain_name`: The Elasticsearch domain that you want to get information about.

"""
describe_elasticsearch_domain_config(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/domain/$(DomainName)/config"; aws_config=aws_config)
describe_elasticsearch_domain_config(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/domain/$(DomainName)/config", params; aws_config=aws_config)

"""
    describe_elasticsearch_domains(domain_names)
    describe_elasticsearch_domains(domain_names, params::Dict{String,<:Any})

Returns domain configuration information about the specified Elasticsearch domains,
including the domain ID, domain endpoint, and domain ARN.

# Arguments
- `domain_names`: The Elasticsearch domains for which you want information.

"""
describe_elasticsearch_domains(DomainNames; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/domain-info", Dict{String, Any}("DomainNames"=>DomainNames); aws_config=aws_config)
describe_elasticsearch_domains(DomainNames, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/domain-info", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("DomainNames"=>DomainNames), params)); aws_config=aws_config)

"""
    describe_elasticsearch_instance_type_limits(elasticsearch_version, instance_type)
    describe_elasticsearch_instance_type_limits(elasticsearch_version, instance_type, params::Dict{String,<:Any})

 Describe Elasticsearch Limits for a given InstanceType and ElasticsearchVersion. When
modifying existing Domain, specify the  DomainName  to know what Limits are supported for
modifying.

# Arguments
- `elasticsearch_version`:  Version of Elasticsearch for which  Limits  are needed.
- `instance_type`:  The instance type for an Elasticsearch cluster for which Elasticsearch
  Limits  are needed.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"domainName"`:  DomainName represents the name of the Domain that we are trying to
  modify. This should be present only if we are querying for Elasticsearch  Limits  for
  existing domain.
"""
describe_elasticsearch_instance_type_limits(ElasticsearchVersion, InstanceType; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/instanceTypeLimits/$(ElasticsearchVersion)/$(InstanceType)"; aws_config=aws_config)
describe_elasticsearch_instance_type_limits(ElasticsearchVersion, InstanceType, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/instanceTypeLimits/$(ElasticsearchVersion)/$(InstanceType)", params; aws_config=aws_config)

"""
    describe_inbound_cross_cluster_search_connections()
    describe_inbound_cross_cluster_search_connections(params::Dict{String,<:Any})

Lists all the inbound cross-cluster search connections for a destination domain.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`:  A list of filters used to match properties for inbound cross-cluster search
  connection. Available Filter names for this operation are:
  cross-cluster-search-connection-id source-domain-info.domain-name
  source-domain-info.owner-id source-domain-info.region destination-domain-info.domain-name
- `"MaxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"NextToken"`:  NextToken is sent in case the earlier API call results contain the
  NextToken. It is used for pagination.
"""
describe_inbound_cross_cluster_search_connections(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/ccs/inboundConnection/search"; aws_config=aws_config)
describe_inbound_cross_cluster_search_connections(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/ccs/inboundConnection/search", params; aws_config=aws_config)

"""
    describe_outbound_cross_cluster_search_connections()
    describe_outbound_cross_cluster_search_connections(params::Dict{String,<:Any})

Lists all the outbound cross-cluster search connections for a source domain.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`:  A list of filters used to match properties for outbound cross-cluster
  search connection. Available Filter names for this operation are:
  cross-cluster-search-connection-id destination-domain-info.domain-name
  destination-domain-info.owner-id destination-domain-info.region
  source-domain-info.domain-name
- `"MaxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"NextToken"`:  NextToken is sent in case the earlier API call results contain the
  NextToken. It is used for pagination.
"""
describe_outbound_cross_cluster_search_connections(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/ccs/outboundConnection/search"; aws_config=aws_config)
describe_outbound_cross_cluster_search_connections(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/ccs/outboundConnection/search", params; aws_config=aws_config)

"""
    describe_packages()
    describe_packages(params::Dict{String,<:Any})

Describes all packages available to Amazon ES. Includes options for filtering, limiting the
number of results, and pagination.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`: Only returns packages that match the DescribePackagesFilterList values.
- `"MaxResults"`: Limits results to a maximum number of packages.
- `"NextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
describe_packages(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/describe"; aws_config=aws_config)
describe_packages(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/describe", params; aws_config=aws_config)

"""
    describe_reserved_elasticsearch_instance_offerings()
    describe_reserved_elasticsearch_instance_offerings(params::Dict{String,<:Any})

Lists available reserved Elasticsearch instance offerings.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"nextToken"`: NextToken should be sent in case if earlier API call produced result
  containing NextToken. It is used for pagination.
- `"offeringId"`: The offering identifier filter value. Use this parameter to show only the
  available offering that matches the specified reservation identifier.
"""
describe_reserved_elasticsearch_instance_offerings(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/reservedInstanceOfferings"; aws_config=aws_config)
describe_reserved_elasticsearch_instance_offerings(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/reservedInstanceOfferings", params; aws_config=aws_config)

"""
    describe_reserved_elasticsearch_instances()
    describe_reserved_elasticsearch_instances(params::Dict{String,<:Any})

Returns information about reserved Elasticsearch instances for this account.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"nextToken"`: NextToken should be sent in case if earlier API call produced result
  containing NextToken. It is used for pagination.
- `"reservationId"`: The reserved instance identifier filter value. Use this parameter to
  show only the reservation that matches the specified reserved Elasticsearch instance ID.
"""
describe_reserved_elasticsearch_instances(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/reservedInstances"; aws_config=aws_config)
describe_reserved_elasticsearch_instances(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/reservedInstances", params; aws_config=aws_config)

"""
    dissociate_package(domain_name, package_id)
    dissociate_package(domain_name, package_id, params::Dict{String,<:Any})

Dissociates a package from the Amazon ES domain.

# Arguments
- `domain_name`: Name of the domain that you want to associate the package with.
- `package_id`: Internal ID of the package that you want to associate with a domain. Use
  DescribePackages to find this value.

"""
dissociate_package(DomainName, PackageID; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/dissociate/$(PackageID)/$(DomainName)"; aws_config=aws_config)
dissociate_package(DomainName, PackageID, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/dissociate/$(PackageID)/$(DomainName)", params; aws_config=aws_config)

"""
    get_compatible_elasticsearch_versions()
    get_compatible_elasticsearch_versions(params::Dict{String,<:Any})

 Returns a list of upgrade compatible Elastisearch versions. You can optionally pass a
DomainName  to get all upgrade compatible Elasticsearch versions for that specific domain.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"domainName"`:
"""
get_compatible_elasticsearch_versions(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/compatibleVersions"; aws_config=aws_config)
get_compatible_elasticsearch_versions(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/compatibleVersions", params; aws_config=aws_config)

"""
    get_package_version_history(package_id)
    get_package_version_history(package_id, params::Dict{String,<:Any})

Returns a list of versions of the package, along with their creation time and commit
message.

# Arguments
- `package_id`: Returns an audit history of versions of the package.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Limits results to a maximum number of versions.
- `"nextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
get_package_version_history(PackageID; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/packages/$(PackageID)/history"; aws_config=aws_config)
get_package_version_history(PackageID, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/packages/$(PackageID)/history", params; aws_config=aws_config)

"""
    get_upgrade_history(domain_name)
    get_upgrade_history(domain_name, params::Dict{String,<:Any})

Retrieves the complete history of the last 10 upgrades that were performed on the domain.

# Arguments
- `domain_name`:

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`:
- `"nextToken"`:
"""
get_upgrade_history(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/upgradeDomain/$(DomainName)/history"; aws_config=aws_config)
get_upgrade_history(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/upgradeDomain/$(DomainName)/history", params; aws_config=aws_config)

"""
    get_upgrade_status(domain_name)
    get_upgrade_status(domain_name, params::Dict{String,<:Any})

Retrieves the latest status of the last upgrade or upgrade eligibility check that was
performed on the domain.

# Arguments
- `domain_name`:

"""
get_upgrade_status(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/upgradeDomain/$(DomainName)/status"; aws_config=aws_config)
get_upgrade_status(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/upgradeDomain/$(DomainName)/status", params; aws_config=aws_config)

"""
    list_domain_names()
    list_domain_names(params::Dict{String,<:Any})

Returns the name of all Elasticsearch domains owned by the current user's account.

"""
list_domain_names(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/domain"; aws_config=aws_config)
list_domain_names(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/domain", params; aws_config=aws_config)

"""
    list_domains_for_package(package_id)
    list_domains_for_package(package_id, params::Dict{String,<:Any})

Lists all Amazon ES domains associated with the package.

# Arguments
- `package_id`: The package for which to list domains.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Limits results to a maximum number of domains.
- `"nextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
list_domains_for_package(PackageID; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/packages/$(PackageID)/domains"; aws_config=aws_config)
list_domains_for_package(PackageID, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/packages/$(PackageID)/domains", params; aws_config=aws_config)

"""
    list_elasticsearch_instance_types(elasticsearch_version)
    list_elasticsearch_instance_types(elasticsearch_version, params::Dict{String,<:Any})

List all Elasticsearch instance types that are supported for given ElasticsearchVersion

# Arguments
- `elasticsearch_version`: Version of Elasticsearch for which list of supported
  elasticsearch instance types are needed.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"domainName"`: DomainName represents the name of the Domain that we are trying to
  modify. This should be present only if we are querying for list of available Elasticsearch
  instance types when modifying existing domain.
- `"maxResults"`:  Set this value to limit the number of results returned. Value provided
  must be greater than 30 else it wont be honored.
- `"nextToken"`: NextToken should be sent in case if earlier API call produced result
  containing NextToken. It is used for pagination.
"""
list_elasticsearch_instance_types(ElasticsearchVersion; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/instanceTypes/$(ElasticsearchVersion)"; aws_config=aws_config)
list_elasticsearch_instance_types(ElasticsearchVersion, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/instanceTypes/$(ElasticsearchVersion)", params; aws_config=aws_config)

"""
    list_elasticsearch_versions()
    list_elasticsearch_versions(params::Dict{String,<:Any})

List all supported Elasticsearch versions

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`:  Set this value to limit the number of results returned. Value provided
  must be greater than 10 else it wont be honored.
- `"nextToken"`:
"""
list_elasticsearch_versions(; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/versions"; aws_config=aws_config)
list_elasticsearch_versions(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/es/versions", params; aws_config=aws_config)

"""
    list_packages_for_domain(domain_name)
    list_packages_for_domain(domain_name, params::Dict{String,<:Any})

Lists all packages associated with the Amazon ES domain.

# Arguments
- `domain_name`: The name of the domain for which you want to list associated packages.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Limits results to a maximum number of packages.
- `"nextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
list_packages_for_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/domain/$(DomainName)/packages"; aws_config=aws_config)
list_packages_for_domain(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/domain/$(DomainName)/packages", params; aws_config=aws_config)

"""
    list_tags(arn)
    list_tags(arn, params::Dict{String,<:Any})

Returns all tags for the given Elasticsearch domain.

# Arguments
- `arn`:  Specify the ARN for the Elasticsearch domain to which the tags are attached that
  you want to view.

"""
list_tags(arn; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/tags/", Dict{String, Any}("arn"=>arn); aws_config=aws_config)
list_tags(arn, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("GET", "/2015-01-01/tags/", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("arn"=>arn), params)); aws_config=aws_config)

"""
    purchase_reserved_elasticsearch_instance_offering(reservation_name, reserved_elasticsearch_instance_offering_id)
    purchase_reserved_elasticsearch_instance_offering(reservation_name, reserved_elasticsearch_instance_offering_id, params::Dict{String,<:Any})

Allows you to purchase reserved Elasticsearch instances.

# Arguments
- `reservation_name`: A customer-specified identifier to track this reservation.
- `reserved_elasticsearch_instance_offering_id`: The ID of the reserved Elasticsearch
  instance offering to purchase.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"InstanceCount"`: The number of Elasticsearch instances to reserve.
"""
purchase_reserved_elasticsearch_instance_offering(ReservationName, ReservedElasticsearchInstanceOfferingId; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/purchaseReservedInstanceOffering", Dict{String, Any}("ReservationName"=>ReservationName, "ReservedElasticsearchInstanceOfferingId"=>ReservedElasticsearchInstanceOfferingId); aws_config=aws_config)
purchase_reserved_elasticsearch_instance_offering(ReservationName, ReservedElasticsearchInstanceOfferingId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/purchaseReservedInstanceOffering", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ReservationName"=>ReservationName, "ReservedElasticsearchInstanceOfferingId"=>ReservedElasticsearchInstanceOfferingId), params)); aws_config=aws_config)

"""
    reject_inbound_cross_cluster_search_connection(connection_id)
    reject_inbound_cross_cluster_search_connection(connection_id, params::Dict{String,<:Any})

Allows the destination domain owner to reject an inbound cross-cluster search connection
request.

# Arguments
- `connection_id`: The id of the inbound connection that you want to reject.

"""
reject_inbound_cross_cluster_search_connection(ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("PUT", "/2015-01-01/es/ccs/inboundConnection/$(ConnectionId)/reject"; aws_config=aws_config)
reject_inbound_cross_cluster_search_connection(ConnectionId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("PUT", "/2015-01-01/es/ccs/inboundConnection/$(ConnectionId)/reject", params; aws_config=aws_config)

"""
    remove_tags(arn, tag_keys)
    remove_tags(arn, tag_keys, params::Dict{String,<:Any})

Removes the specified set of tags from the specified Elasticsearch domain.

# Arguments
- `arn`: Specifies the ARN for the Elasticsearch domain from which you want to delete the
  specified tags.
- `tag_keys`: Specifies the TagKey list which you want to remove from the Elasticsearch
  domain.

"""
remove_tags(ARN, TagKeys; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/tags-removal", Dict{String, Any}("ARN"=>ARN, "TagKeys"=>TagKeys); aws_config=aws_config)
remove_tags(ARN, TagKeys, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/tags-removal", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ARN"=>ARN, "TagKeys"=>TagKeys), params)); aws_config=aws_config)

"""
    start_elasticsearch_service_software_update(domain_name)
    start_elasticsearch_service_software_update(domain_name, params::Dict{String,<:Any})

Schedules a service software update for an Amazon ES domain.

# Arguments
- `domain_name`: The name of the domain that you want to update to the latest service
  software.

"""
start_elasticsearch_service_software_update(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/serviceSoftwareUpdate/start", Dict{String, Any}("DomainName"=>DomainName); aws_config=aws_config)
start_elasticsearch_service_software_update(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/serviceSoftwareUpdate/start", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("DomainName"=>DomainName), params)); aws_config=aws_config)

"""
    update_elasticsearch_domain_config(domain_name)
    update_elasticsearch_domain_config(domain_name, params::Dict{String,<:Any})

Modifies the cluster configuration of the specified Elasticsearch domain, setting as
setting the instance type and the number of instances.

# Arguments
- `domain_name`: The name of the Elasticsearch domain that you are updating.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccessPolicies"`: IAM access policy as a JSON-formatted string.
- `"AdvancedOptions"`: Modifies the advanced option to allow references to indices in an
  HTTP request body. Must be false when configuring access to individual sub-resources. By
  default, the value is true. See Configuration Advanced Options for more information.
- `"AdvancedSecurityOptions"`: Specifies advanced security options.
- `"AutoTuneOptions"`: Specifies Auto-Tune options.
- `"CognitoOptions"`: Options to specify the Cognito user and identity pools for Kibana
  authentication. For more information, see Amazon Cognito Authentication for Kibana.
- `"DomainEndpointOptions"`: Options to specify configuration that will be applied to the
  domain endpoint.
- `"EBSOptions"`: Specify the type and size of the EBS volume that you want to use.
- `"ElasticsearchClusterConfig"`: The type and number of instances to instantiate for the
  domain cluster.
- `"EncryptionAtRestOptions"`: Specifies the Encryption At Rest Options.
- `"LogPublishingOptions"`: Map of LogType and LogPublishingOption, each containing options
  to publish a given type of Elasticsearch log.
- `"NodeToNodeEncryptionOptions"`: Specifies the NodeToNodeEncryptionOptions.
- `"SnapshotOptions"`: Option to set the time, in UTC format, for the daily automated
  snapshot. Default value is 0 hours.
- `"VPCOptions"`: Options to specify the subnets and security groups for VPC endpoint. For
  more information, see Creating a VPC in VPC Endpoints for Amazon Elasticsearch Service
  Domains
"""
update_elasticsearch_domain_config(DomainName; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/domain/$(DomainName)/config"; aws_config=aws_config)
update_elasticsearch_domain_config(DomainName, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/domain/$(DomainName)/config", params; aws_config=aws_config)

"""
    update_package(package_id, package_source)
    update_package(package_id, package_source, params::Dict{String,<:Any})

Updates a package for use with Amazon ES domains.

# Arguments
- `package_id`: Unique identifier for the package.
- `package_source`:

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"CommitMessage"`: An info message for the new version which will be shown as part of
  GetPackageVersionHistoryResponse.
- `"PackageDescription"`: New description of the package.
"""
update_package(PackageID, PackageSource; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/update", Dict{String, Any}("PackageID"=>PackageID, "PackageSource"=>PackageSource); aws_config=aws_config)
update_package(PackageID, PackageSource, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/packages/update", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("PackageID"=>PackageID, "PackageSource"=>PackageSource), params)); aws_config=aws_config)

"""
    upgrade_elasticsearch_domain(domain_name, target_version)
    upgrade_elasticsearch_domain(domain_name, target_version, params::Dict{String,<:Any})

Allows you to either upgrade your domain or perform an Upgrade eligibility check to a
compatible Elasticsearch version.

# Arguments
- `domain_name`:
- `target_version`: The version of Elasticsearch that you intend to upgrade the domain to.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"PerformCheckOnly"`:  This flag, when set to True, indicates that an Upgrade Eligibility
  Check needs to be performed. This will not actually perform the Upgrade.
"""
upgrade_elasticsearch_domain(DomainName, TargetVersion; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/upgradeDomain", Dict{String, Any}("DomainName"=>DomainName, "TargetVersion"=>TargetVersion); aws_config=aws_config)
upgrade_elasticsearch_domain(DomainName, TargetVersion, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = elasticsearch_service("POST", "/2015-01-01/es/upgradeDomain", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("DomainName"=>DomainName, "TargetVersion"=>TargetVersion), params)); aws_config=aws_config)


text:
struct Help
  help::Symbol
end
Help(;help=:help) = Help(help)

ext: jl


text:
#!/usr/bin/env julia
# Exceptions and Bug Handling
# Exercise 1-5

struct InvalidLengthError <: Exception
    val1::Int64
    val2::Int64
end
Base.showerror(io::IO, e::InvalidLengthError) = print(io, "The parsed length (", e.val1, ") does not equal the calculated length (",e.val2,")")

function parse_prot(f)
    seq = ""
    sp_id = ""
    sp_ac = ""
    aa_len = 0
    FLAG = false

    for line in eachline(f)
        key, value = line[begin:2], rstrip(line[6:end])
        if key == "ID"
            i = 1
            while value[i] != ' '
                i += 1
            end
            sp_id = string(value[begin:i])
            println("The ID is: ", sp_id)
        elseif key == "AC"
            sp_ac = string(value[begin:end-1])
            println("The accession numbers are: ", value[begin:end-1])
        end

        if key == "//"
            FLAG = false
        end
        if FLAG
            for char in value
                if char != ' '
                    seq *= char
                end
            end
        end
        if key == "SQ"
            FLAG = true
            aa_end = aa_start = 12
            while value[aa_end] != ' '
                aa_end += 1
            end
            aa_len = parse(Int64, value[aa_start:aa_end])
        end
    end
    println("Sequence: ",seq)

    if aa_len != length(seq)
        throw(InvalidLengthError(aa_len,length(seq)))
    else
        println("AA length: ", aa_len)
    end
    seq, aa_len, sp_ac, sp_id
end

read_prot(filename) = try
    open(parse_prot, filename)
catch e
    msg = sprint(showerror, e)
    println(msg)
end

println("Enter file containing SwissProt database entry to analyze: ")

seq, aa_len, sp_ac, sp_id = read_prot(chomp(readline()))

#Ex 6
function to_outfile(seq, aa_len, sp_ac, sp_id)
    outfile = open("sprot.fsa", "w")
    println(outfile,">sp|", sp_ac, "|", sp_id)

    for i in 1:60:length(seq)
        if i+59 > length(seq)
            println(outfile, seq[i:end])
        else
            println(outfile, seq[i:(i+59)])
        end
    end

    close(outfile)
end

to_outfile(seq, aa_len, sp_ac, sp_id)

# Exercise 7
function parse_dna(f)
    fasta_info = readline(f)
    seq = ""
    for line in eachline(f)
        seq *= chomp(line)
    end
    for i in 1:(length(seq)-2)
        println(seq[i:i+2])
        if seq[i:i+2] == "ATG"
            return seq, i
        end
    end
    seq, nothing
end

read_dna(filename = "data/dna.fsa") = try
    seq = open(parse_dna, filename)
catch e
    msg = sprint(showerror, e)
    println(msg)
end

println(read_dna())

# Exercise 8
function parse_dna(f)
    fasta_info = readline(f)
    seq = ""
    for line in eachline(f)
        seq *= chomp(line)
    end
    for i in 1:(length(seq)-2)
        println(seq[i:i+2])
        if seq[i:i+2] == "ATG"
            for j in i:3:(length(seq)-2)
                if seq[j:j+2] in ("TAA", "TAG", "TGA")
                    return seq, i, j
                end
            end
        end
    end
    seq, nothing, nothing
end

read_dna(filename = "data/dna.fsa") = try
    seq = open(parse_dna, filename)
catch e
    msg = sprint(showerror, e)
    println(msg)
end

println(read_dna())

# Exercise 9
function parse_orphans(match, f)
    match_count = 0
    for line in eachline(f)
        if line[1] != '>'
            i = 1
            while line[i] != '_'
                i += 1
            end
            if line[i+1:i+length(match)] == match
                match_count += 1
            end
        end
    end
    match_count
end

search_orphans(match, filename = "data/orphans.sp") = try
    seq = open(f->parse_orphans(match, f), filename)
catch e
    msg = sprint(showerror, e)
    println(msg)
end

println(search_orphans("MOUSE"))

# Exercise 10
function guess_number(minimum, maximum)
    ori_min = minimum
    ori_max = maximum

    gameFinished = false

    guess = convert(Int64, floor((minimum + maximum)/2))

    while !gameFinished
        if minimum == maximum # only 1 possible number left
            println("Your number is ", minimum, "!")
            gameFinished = true
        elseif minimum > maximum # the limits are weird, since you lied!
            println("You lied to me, you son of a gun! Bund and start over :)")
            minimum = ori_min
            maximum = ori_max
            #gameFinished = True
        else
            println("Is your number ", guess, "? If not, higher or lower?")
            choice = chomp(readline())
            if choice == "higher"
                minimum = guess + 1
            elseif choice == "lower"
                maximum = guess - 1
            elseif choice == "yes"
                println("Gotcha!")
                gameFinished = true
            end
        end
        guess = convert(Int64, floor((minimum + maximum)/2))
    end
end

guess_number(1, 10)


text:
import ProgressMeter
using ProgressMeter: Progress

# TODO: maybe data cannot always be held in memory and would be better
# to have the options of either retrieving from memory or from disk
# could introduce a `retrieve` function which dispatches depending on how
# the list of training data is passed in.
#
# TODO: flag to turn off the progressmeter? Should this be a flag separate from
# the verbose flag?

# ---------- Single-threaded iteration over configurations
#  this is just a prototype implementation for tests and compatibility

function siterate(f, data; verbose=true, msg = "serial", costs = cost.(data))
   progmtr = Progress(sum(costs))
   progctr = 0

   t0 = time_ns()

   for (idat, (dat, c)) in enumerate(zip(data, costs))
      f(idat, dat)
      progctr += c
      ProgressMeter.update!(progmtr, progctr)
   end

   t1 = time_ns()
   verbose && @info("Elapsed: $(round((t1-t0)*1e-9, digits=1))s")

   return nothing
end

# ---------- Multi-threaded iteration

using Base.Threads: @threads, nthreads, SpinLock, threadid, lock, unlock

"""
`titerate(f, data; kwargs...)`
Multi-threaded map loop. At each iteration the function f(dat) is executed,
where `dat in data`. The order is not necessarily preserved. In fact the
`costs` array is used to sort the data by decreasing cost to ensure the
most costly configurations are encountered first. This helps avoid threads
without work at the end of the loop.
"""
function titerate(f, data; verbose=true, msg="multi-threaded",
                           costs = cost.(data),
                           maxnthreads=nthreads() )
   progmtr = Progress(sum(costs))
   progctr = 0
   t0 = time_ns()
   nthr = max(1, min(nthreads(), maxnthreads))
   if verbose
      @info("Iterate with $(nthreads()) threads")
      ProgressMeter.update!(progmtr, 0)  # not sure this is useful/needed
      prog_lock = SpinLock()
   end
   # sort the tasks by cost: do the expensive ones first
   Isort = sortperm(costs, rev=true)
   # remember what the last job was
   last_job = 0
   last_job_lock = SpinLock()
   # start a simple loop over nthreads() just to split this
   # into parallel "tasks"
   rgidx = Vector{Int}(undef, nthreads())
   @threads for i = 1:nthreads()
      while true
         # acquire a new job index
         tid = threadid()
         lock(last_job_lock)
         last_job += 1
         if last_job > length(Isort)
            unlock(last_job_lock)
            break # break the while loop and then wait for the
                  # other threads to finish
         end
         rgidx[tid] = Isort[last_job]
         unlock(last_job_lock)
         # do the job
         f(rgidx[tid], data[rgidx[tid]])
         # submit progress
         if verbose
            lock(prog_lock)
            progctr += costs[rgidx[tid]]
            ProgressMeter.update!(progmtr, progctr)
            unlock(prog_lock)
         end
      end
   end
   t1 = time_ns()
   verbose && @info("Elapsed: $(round((t1-t0)*1e-9, digits=1))s")
   return nothing
end

# ---------- Distributed iteration

# TODO: Andres?


text:
###############
# H-OBCA: Hierarchical Optimization-based Collision Avoidance - a path planner for autonomous parking
# Copyright (C) 2018
# Atsushi SAKAI [atsushisakai@global.komatsu; Komatsu Ltd / MPC Lab]
# Alexander LINIGER [liniger@control.ee.ethz.ch; Automatic Control Lab, ETH Zurich]
# Xiaojing ZHANG [xiaojing.zhang@berkeley.edu; MPC Lab, UC Berkeley]
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
###############
# The paper describing the theory can be found here:
# 	X. Zhang, A. Liniger and F. Borrelli; "Optimization-Based Collision Avoidance"; Technical Report, 2017
#   X. Zhang, A. Liniger, A. Sakai and F. Borrelli; "Autonomous  Parking  using  Optimization-Based  Collision  Avoidance"; Technical Report, 2018 [add URL]
###############

###############
# Reeds Shepp path planner
###############

module reeds_shepp

using PyPlot

const STEP_SIZE = 0.1

type Path
    lengths::Array{Float64} #lengths of each part of the path +: forward, -: backward
    ctypes::Array{String} # type of each part of the path
    L::Float64 # total path length
    x::Array{Float64} # final x positions [m]
    y::Array{Float64} # final y positions [m]
    yaw::Array{Float64} # final yaw angles [rad]
    directions::Array{Int8} # forward:1, backward:-1
end

function pi_2_pi(iangle::Float64)::Float64
    while (iangle > pi)
        iangle -= 2.0 * pi
    end
    while (iangle < -pi)
        iangle += 2.0 * pi
    end

    return iangle
end

function calc_shortest_path(sx::Float64, sy::Float64, syaw::Float64,
                            gx::Float64, gy::Float64, gyaw::Float64,
                            maxc::Float64;
                            step_size::Float64 = STEP_SIZE)
    # println("Find Shortest Path")
    paths = calc_paths(sx,sy,syaw,gx,gy,gyaw,maxc,step_size=step_size)

    minL = Inf
    best_path_index = -1
    for i in 1:length(paths)
        if paths[i].L <= minL
            minL = paths[i].L
            best_path_index = i
        end
    end

    return paths[best_path_index]
end

function calc_shortest_path_length(sx::Float64, sy::Float64, syaw::Float64,
                            gx::Float64, gy::Float64, gyaw::Float64,
                            maxc::Float64;
                            step_size::Float64 = STEP_SIZE)
    q0 = [sx, sy, syaw]
    q1 = [gx, gy, gyaw]
    paths = generate_path(q0, q1, maxc)

    minL = Inf
    for i in 1:length(paths)
        L = paths[i].L/maxc
        if L <= minL
            minL = L
        end
    end

    return minL
end

function calc_paths(sx::Float64, sy::Float64, syaw::Float64,
                    gx::Float64, gy::Float64, gyaw::Float64,
                    maxc::Float64; step_size::Float64 = STEP_SIZE)::Array{Path}
    q0 = [sx, sy, syaw]
    q1 = [gx, gy, gyaw]

    paths = generate_path(q0, q1, maxc)
    for path in paths
        x, y, yaw, directions = generate_local_course(path.L, path.lengths, path.ctypes, maxc, step_size*maxc)

        # convert global coordinate
        path.x = [cos(-q0[3]) * ix + sin(-q0[3]) * iy + q0[1] for (ix, iy) in zip(x, y)]
        path.y = [-sin(-q0[3]) * ix + cos(-q0[3]) * iy + q0[2] for (ix, iy) in zip(x, y)]
        path.yaw = pi_2_pi.([iyaw + q0[3] for iyaw in yaw])
        path.directions = directions
        path.lengths = [l/maxc for l in path.lengths]
        path.L = path.L/maxc

    end

    return paths
end

function get_label(path::Path)
    label =""

    for (m,l) in zip(path.ctypes, path.lengths)
        label = string(label, m)
        if l > 0.0
            label = string(label, "+")
        else
            label = string(label, "-")
        end
    end

    return label
end

function polar(x::Float64, y::Float64)
    r = sqrt(x^2+y^2)
    theta = atan2(y, x)
    return r, theta
end

function mod2pi(x::Float64)
    v = mod(x, 2.0*pi)
    if v < -pi
        v += 2.0*pi;
    else
        if v > pi
            v -= 2.0*pi
        end
    end
    return v
end

function LSL(x::Float64, y::Float64, phi::Float64)
    u, t = polar(x - sin(phi), y - 1.0 + cos(phi))
    if t >= 0.0
        v = mod2pi(phi - t)
        if (v >= 0.0)
            return true, t, u, v
       end
    end

    return false, 0.0, 0.0, 0.0
end

function LSR(x::Float64, y::Float64, phi::Float64)
    u1, t1 = polar(x + sin(phi), y - 1.0 - cos(phi))
    u1 = u1^2;
    if u1 >= 4.0
        u = sqrt(u1 - 4.0)
        theta = atan2(2.0, u)
        t = mod2pi(t1 + theta)
        v = mod2pi(t - phi)

        if t >= 0.0 && v >= 0.0
            return true, t, u, v
        end
    end

    return false, 0.0, 0.0, 0.0
end

function LRL(x::Float64, y::Float64, phi::Float64)
    u1, t1 = polar(x - sin(phi), y - 1.0 + cos(phi))

    if u1 <= 4.0
        u = -2.0*asin(0.25 * u1)
        t = mod2pi(t1 + 0.5 * u + pi);
        v = mod2pi(phi - t + u);

        if t >= 0.0 && u <= 0.0
            return true, t, u, v
        end
    end

    return false, 0.0, 0.0, 0.0
end

function set_path(paths::Array{Path}, lengths::Array{Float64}, ctypes::Array{String})

    path = Path([],[],0.0,[],[],[],[])
    path.ctypes = ctypes
    path.lengths = lengths

    # check same path exist
    for tpath in paths
        typeissame = (tpath.ctypes == path.ctypes)
        if typeissame
            if sum(tpath.lengths - path.lengths) <= 0.01
                return paths # not insert path
            end
        end
    end

    path.L = sum([abs(i) for i in lengths])

    Base.Test.@test path.L >= 0.01

    push!(paths, path)

    return paths
end

function SCS(x::Float64, y::Float64, phi::Float64, paths::Array{Path})::Array{Path}
    flag, t, u, v = SLS(x, y, phi)
    if flag
        # println("SCS1")
        paths = set_path(paths, [t, u, v], ["S","L","S"])
    end
    flag, t, u, v = SLS(x, -y, -phi)
    if flag
        # println("SCS2")
        paths = set_path(paths, [t, u, v], ["S","R","S"])
    end

    return paths
end

function SLS(x::Float64, y::Float64, phi::Float64)
    # println(x,",", y,",", phi, ",", mod2pi(phi))
    phi = mod2pi(phi)
    if y > 0.0 && phi > 0.0 && phi < pi*0.99
        xd = - y/tan(phi) + x
        t =  xd - tan(phi/2.0)
        u = phi
        v = sqrt((x-xd)^2+y^2)-tan(phi/2.0)
        # println("1,",t,",",u,",",v)
        return true, t, u, v
    elseif y < 0.0 && phi > 0.0 && phi < pi*0.99
        xd = - y/tan(phi) + x
        t =  xd - tan(phi/2.0)
        u = phi
        v = -sqrt((x-xd)^2+y^2)-tan(phi/2.0)
        # println("2,",t,",",u,",",v)
        return true, t, u, v
    end

    return false, 0.0, 0.0, 0.0
end

function CSC(x::Float64, y::Float64, phi::Float64, paths::Array{Path})
    flag, t, u, v = LSL(x, y, phi)
    if flag
        # println("CSC1")
        paths = set_path(paths, [t, u, v], ["L","S","L"])
    end
    flag, t, u, v = LSL(-x, y, -phi)
    if flag
        # println("CSC2")
        paths = set_path(paths, [-t, -u, -v], ["L","S","L"])
    end
    flag, t, u, v = LSL(x, -y, -phi)
    if flag
        # println("CSC3")
        paths = set_path(paths, [t, u, v], ["R","S","R"])
    end
    flag, t, u, v = LSL(-x, -y, phi)
    if flag
        # println("CSC4")
        paths = set_path(paths, [-t, -u, -v], ["R","S","R"])
    end
    flag, t, u, v = LSR(x, y, phi)
    if flag
        # println("CSC5")
        paths = set_path(paths, [t, u, v], ["L","S","R"])
    end
    flag, t, u, v = LSR(-x, y, -phi)
    if flag
        # println("CSC6")
        paths = set_path(paths, [-t, -u, -v], ["L","S","R"])
    end
    flag, t, u, v = LSR(x, -y, -phi)
    if flag
        # println("CSC7")
        paths = set_path(paths, [t, u, v], ["R","S","L"])
    end
    flag, t, u, v = LSR(-x, -y, phi)
    if flag
        # println("CSC8")
        paths = set_path(paths, [-t, -u, -v], ["R","S","L"])
    end

    return paths
end

function CCC(x::Float64, y::Float64, phi::Float64, paths::Array{Path})

    flag, t, u, v = LRL(x, y, phi)
    if flag
        # println("CCC1")
        paths = set_path(paths, [t, u, v], ["L","R","L"])
    end
    flag, t, u, v = LRL(-x, y, -phi)
    if flag
        # println("CCC2")
        paths = set_path(paths, [-t, -u, -v], ["L","R","L"])
    end
    flag, t, u, v = LRL(x, -y, -phi)
    if flag
        # println("CCC3")
        paths = set_path(paths, [t, u, v], ["R","L","R"])
    end
    flag, t, u, v = LRL(-x, -y, phi)
    if flag
        # println("CCC4")
        paths = set_path(paths, [-t, -u, -v], ["R","L","R"])
    end

    # backwards
    xb = x*cos(phi) + y*sin(phi)
    yb = x*sin(phi) - y*cos(phi)
    # println(xb, ",", yb,",",x,",",y)

    flag, t, u, v = LRL(xb, yb, phi)
    if flag
        # println("CCC5")
        paths = set_path(paths, [v, u, t], ["L","R","L"])
    end
    flag, t, u, v = LRL(-xb, yb, -phi)
    if flag
        # println("CCC6")
        paths = set_path(paths, [-v, -u, -t], ["L","R","L"])
    end
    flag, t, u, v = LRL(xb, -yb, -phi)
    if flag
        # println("CCC7")
        paths = set_path(paths, [v, u, t], ["R","L","R"])
    end
    flag, t, u, v = LRL(-xb, -yb, phi)
    if flag
        # println("CCC8")
        paths = set_path(paths, [-v, -u, -t], ["R","L","R"])
    end

    return paths
end

function calc_tauOmega(u::Float64, v::Float64, xi::Float64, eta::Float64, phi::Float64)
    delta = mod2pi(u-v)
    A = sin(u) - sin(delta)
    B = cos(u) - cos(delta) - 1.0

    t1 = atan2(eta*A - xi*B, xi*A + eta*B)
    t2 = 2.0 * (cos(delta) - cos(v) - cos(u)) + 3.0;

    if t2 < 0
        tau = mod2pi(t1+pi)
    else
        tau = mod2pi(t1)
    end
    omega = mod2pi(tau - u + v - phi)

    return tau, omega
end

function LRLRn(x::Float64, y::Float64, phi::Float64)
    xi = x + sin(phi)
    eta = y - 1.0 - cos(phi)
    rho = 0.25 * (2.0 + sqrt(xi*xi + eta*eta))

    if rho <= 1.0
        u = acos(rho)
        t, v = calc_tauOmega(u, -u, xi, eta, phi);
        if t >= 0.0 && v <= 0.0
            return true, t, u, v
        end
    end

    return false, 0.0, 0.0, 0.0
end

function LRLRp(x::Float64, y::Float64, phi::Float64)
    xi = x + sin(phi)
    eta = y - 1.0 - cos(phi)
    rho = (20.0 - xi*xi - eta*eta) / 16.0;
    # println(xi,",",eta,",",rho)

    if (rho>=0.0 && rho<=1.0)
        u = -acos(rho);
        if (u >= -0.5 * pi)
            t, v = calc_tauOmega(u, u, xi, eta, phi);
            if t >= 0.0 && v >= 0.0
                return true, t, u, v
            end
        end
    end

    return false, 0.0, 0.0, 0.0
end

function CCCC(x::Float64, y::Float64, phi::Float64, paths::Array{Path})

    flag, t, u, v = LRLRn(x, y, phi)
    if flag
        # println("CCCC1")
        paths = set_path(paths, [t, u, -u, v], ["L","R","L","R"])
    end

    flag, t, u, v = LRLRn(-x, y, -phi)
    if flag
        # println("CCCC2")
        paths = set_path(paths, [-t, -u, u, -v], ["L","R","L","R"])
    end

    flag, t, u, v = LRLRn(x, -y, -phi)
    if flag
        # println("CCCC3")
        paths = set_path(paths, [t, u, -u, v], ["R","L","R","L"])
    end

    flag, t, u, v = LRLRn(-x, -y, phi)
    if flag
        # println("CCCC4")
        paths = set_path(paths, [-t, -u, u, -v], ["R","L","R","L"])
    end

    flag, t, u, v = LRLRp(x, y, phi)
    if flag
        # println("CCCC5")
        paths = set_path(paths, [t, u, u, v], ["L","R","L","R"])
    end

    flag, t, u, v = LRLRp(-x, y, -phi)
    if flag
        # println("CCCC6")
        paths = set_path(paths, [-t, -u, -u, -v], ["L","R","L","R"])
    end

    flag, t, u, v = LRLRp(x, -y, -phi)
    if flag
        # println("CCCC7")
        paths = set_path(paths, [t, u, u, v], ["R","L","R","L"])
    end

    flag, t, u, v = LRLRp(-x, -y, phi)
    if flag
        # println("CCCC8")
        paths = set_path(paths, [-t, -u, -u, -v], ["R","L","R","L"])
    end

    return paths
end

function LRSR(x::Float64, y::Float64, phi::Float64)

    xi = x + sin(phi)
    eta = y - 1.0 - cos(phi)
    rho, theta = polar(-eta, xi)

    if rho >= 2.0
        t = theta
        u = 2.0 - rho
        v = mod2pi(t + 0.5*pi - phi)
        if t >= 0.0 && u <= 0.0 && v <=0.0
            return true, t, u, v
        end
    end

    return false, 0.0, 0.0, 0.0
end

function LRSL(x::Float64, y::Float64, phi::Float64)
    xi = x - sin(phi)
    eta = y - 1.0 + cos(phi)
    rho, theta = polar(xi, eta)

    if rho >= 2.0
        r = sqrt(rho*rho - 4.0);
        u = 2.0 - r;
        t = mod2pi(theta + atan2(r, -2.0));
        v = mod2pi(phi - 0.5*pi - t);
        if t >= 0.0 && u<=0.0 && v<=0.0
            return true, t, u, v
        end
    end

    return false, 0.0, 0.0, 0.0
end

function CCSC(x::Float64, y::Float64, phi::Float64, paths::Array{Path})

    flag, t, u, v = LRSL(x, y, phi)
    if flag
        # println("CCSC1")
        paths = set_path(paths, [t, -0.5*pi, u, v], ["L","R","S","L"])
    end

    flag, t, u, v = LRSL(-x, y, -phi)
    if flag
        # println("CCSC2")
        paths = set_path(paths, [-t, 0.5*pi, -u, -v], ["L","R","S","L"])
    end

    flag, t, u, v = LRSL(x, -y, -phi)
    if flag
        # println("CCSC3")
        paths = set_path(paths, [t, -0.5*pi, u, v], ["R","L","S","R"])
    end

    flag, t, u, v = LRSL(-x, -y, phi)
    if flag
        # println("CCSC4")
        paths = set_path(paths, [-t, 0.5*pi, -u, -v], ["R","L","S","R"])
    end

    flag, t, u, v = LRSR(x, y, phi)
    if flag
        # println("CCSC5")
        paths = set_path(paths, [t, -0.5*pi, u, v], ["L","R","S","R"])
    end

    flag, t, u, v = LRSR(-x, y, -phi)
    if flag
        # println("CCSC6")
        paths = set_path(paths, [-t, 0.5*pi, -u, -v], ["L","R","S","R"])
    end

    flag, t, u, v = LRSR(x, -y, -phi)
    if flag
        # println("CCSC7")
        paths = set_path(paths, [t, -0.5*pi, u, v], ["R","L","S","L"])
    end

    flag, t, u, v = LRSR(-x, -y, phi)
    if flag
        # println("CCSC8")
        paths = set_path(paths, [-t, 0.5*pi, -u, -v], ["R","L","S","L"])
    end

    # backwards
    xb = x*cos(phi) + y*sin(phi)
    yb = x*sin(phi) - y*cos(phi)
    flag, t, u, v = LRSL(xb, yb, phi)
    if flag
        # println("CCSC9")
        paths = set_path(paths, [v, u, -0.5*pi, t], ["L","S","R","L"])
    end

    flag, t, u, v = LRSL(-xb, yb, -phi)
    if flag
        # println("CCSC10")
        paths = set_path(paths, [-v, -u, 0.5*pi, -t], ["L","S","R","L"])
    end

    flag, t, u, v = LRSL(xb, -yb, -phi)
    if flag
        # println("CCSC11")
        paths = set_path(paths, [v, u, -0.5*pi, t], ["R","S","L","R"])
    end

    flag, t, u, v = LRSL(-xb, -yb, phi)
    if flag
        # println("CCSC12")
        paths = set_path(paths, [-v, -u, 0.5*pi, -t], ["R","S","L","R"])
    end

    flag, t, u, v = LRSR(xb, yb, phi)
    if flag
        # println("CCSC13")
        paths = set_path(paths, [v, u, -0.5*pi, t], ["R","S","R","L"])
    end

    flag, t, u, v = LRSR(-xb, yb, -phi)
    if flag
        # println("CCSC14")
        paths = set_path(paths, [-v, -u, 0.5*pi, -t], ["R","S","R","L"])
    end

    flag, t, u, v = LRSR(xb, -yb, -phi)
    if flag
        # println("CCSC15")
        paths = set_path(paths, [v, u, -0.5*pi, t], ["L","S","L","R"])
    end

    flag, t, u, v = LRSR(-xb, -yb, phi)
    if flag
        # println("CCSC16")
        paths = set_path(paths, [-v, -u, 0.5*pi, -t], ["L","S","L","R"])
    end

    return paths
end

function LRSLR(x::Float64, y::Float64, phi::Float64)
    # formula 8.11 *** TYPO IN PAPER ***
    xi = x + sin(phi)
    eta = y - 1.0 - cos(phi)
    rho, theta = polar(xi, eta)
    if rho >= 2.0
        u = 4.0 - sqrt(rho*rho - 4.0)
        if u <= 0.0
            t = mod2pi(atan2((4.0-u)*xi -2.0*eta, -2.0*xi + (u-4.0)*eta));
            v = mod2pi(t - phi);

            if t >= 0.0 && v >=0.0
                return true, t, u, v
            end
        end
    end

    return false, 0.0, 0.0, 0.0
end

function CCSCC(x::Float64, y::Float64, phi::Float64, paths::Array{Path})
    flag, t, u, v = LRSLR(x, y, phi)
    if flag
        # println("CCSCC1")
        paths = set_path(paths, [t, -0.5*pi, u, -0.5*pi, v], ["L","R","S","L","R"])
    end
    flag, t, u, v = LRSLR(-x, y, -phi)
    if flag
        # println("CCSCC2")
        paths = set_path(paths, [-t, 0.5*pi, -u, 0.5*pi, -v], ["L","R","S","L","R"])
    end

    flag, t, u, v = LRSLR(x, -y, -phi)
    if flag
        # println("CCSCC3")
        paths = set_path(paths, [t, -0.5*pi, u, -0.5*pi, v], ["R","L","S","R","L"])
    end

    flag, t, u, v = LRSLR(-x, -y, phi)
    if flag
        # println("CCSCC4")
        paths = set_path(paths, [-t, 0.5*pi, -u, 0.5*pi, -v], ["R","L","S","R","L"])
    end

    return paths
end

function generate_local_course(L::Float64,
                               lengths::Array{Float64},
                               mode::Array{String},
                               maxc::Float64,
                               step_size::Float64)
    npoint = trunc(Int64, L/step_size) + length(lengths)+3
    # println(npoint, ",", L, ",", step_size, ",", L/step_size)

    px = fill(0.0, npoint)
    py = fill(0.0, npoint)
    pyaw = fill(0.0, npoint)
    directions = fill(0, npoint)
    ind = 2

    if lengths[1] > 0.0
        directions[1] = 1
    else
        directions[1] = -1
    end

    if lengths[1] > 0.0
        d = step_size
    else
        d = -step_size
    end

    pd = d
    ll = 0.0

    for (m, l, i) in zip(mode, lengths, 1:length(mode))

        if l > 0.0
            d = step_size
        else
            d = -step_size
        end

        # set prigin state
        ox, oy, oyaw = px[ind], py[ind], pyaw[ind]

        ind -= 1
        if i >= 2 && (lengths[i-1]*lengths[i])>0
            pd = - d - ll
        else
            pd = d - ll
        end

        while abs(pd) <= abs(l)
            ind += 1
            px, py, pyaw, directions = interpolate(ind, pd, m, maxc, ox, oy, oyaw, px, py, pyaw, directions)
            pd += d
        end

        ll = l - pd - d # calc remain length

        ind += 1
        px, py, pyaw, directions = interpolate(ind, l, m, maxc, ox, oy, oyaw, px, py, pyaw, directions)
    end

    #remove unused data
    while px[end] == 0.0
        pop!(px)
        pop!(py)
        pop!(pyaw)
        pop!(directions)
    end

    return px, py, pyaw, directions
end

function interpolate(ind::Int64, l::Float64, m::String, maxc::Float64,
                     ox::Float64, oy::Float64, oyaw::Float64,
                     px::Array{Float64}, py::Array{Float64}, pyaw::Array{Float64},
                     directions::Array{Int64})

    if m == "S"
        px[ind] = ox + l / maxc * cos(oyaw)
        py[ind] = oy + l / maxc * sin(oyaw)
        pyaw[ind] =  oyaw
    else # curve
        ldx = sin(l) / maxc
        if m == "L"  # left turn
            ldy = (1.0 - cos(l)) / maxc
        elseif m == "R"  # right turn
            ldy = (1.0 - cos(l)) / -maxc
        end
        gdx = cos(-oyaw) * ldx + sin(-oyaw) * ldy
        gdy = -sin(-oyaw) * ldx + cos(-oyaw) * ldy
        px[ind] = ox + gdx
        py[ind] = oy + gdy
    end

    if m == "L"  # left turn
        pyaw[ind] = oyaw + l
    elseif m == "R"  # right turn
        pyaw[ind] = oyaw - l
    end

    if l > 0.0
        directions[ind] = 1
    else
        directions[ind] = -1
    end

    return px, py, pyaw, directions
end

function generate_path(q0::Array{Float64}, q1::Array{Float64}, maxc::Float64)::Array{Path}
    dx = q1[1] - q0[1]
    dy = q1[2] - q0[2]
    dth = q1[3] - q0[3]
    c = cos(q0[3])
    s = sin(q0[3]);
    x = (c*dx + s*dy)*maxc
    y = (-s*dx + c*dy)*maxc

    paths = Path[]
    paths = SCS(x, y, dth, paths)
    paths = CSC(x, y, dth, paths)
    paths = CCC(x, y, dth, paths)
    paths = CCCC(x, y, dth, paths)
    paths = CCSC(x, y, dth, paths)
    paths = CCSCC(x, y, dth, paths)

    return paths
end

function calc_curvature(x,y,yaw, directions)

    c = Float64[]
    ds = Float64[]

    for i in 2:length(x)-1
        dxn = x[i]-x[i-1]
        dxp = x[i+1]-x[i]
        dyn = y[i]-y[i-1]
        dyp = y[i+1]-y[i]
        dn =sqrt(dxn^2.0+dyn^2.0)
        dp =sqrt(dxp^2.0+dyp^2.0)
        dx = 1.0/(dn+dp)*(dp/dn*dxn+dn/dp*dxp)
        ddx = 2.0/(dn+dp)*(dxp/dp-dxn/dn)
        dy = 1.0/(dn+dp)*(dp/dn*dyn+dn/dp*dyp)
        ddy = 2.0/(dn+dp)*(dyp/dp-dyn/dn)
        curvature = (ddy*dx-ddx*dy)/(dx^2+dy^2)
        d = (dn+dp)/2.0

        if isnan(curvature)
            curvature = 0.0
        end

        if directions[i] <= 0.0
            curvature = -curvature
        end

        if length(c) == 0
            push!(ds, d)
            push!(c, curvature)
        end

        push!(ds, d)
        push!(c, curvature)
    end

    push!(ds, ds[end])
    push!(c, c[end] )

    return c, ds
end

function check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)
    # println("Test")
    # println(start_x,",", start_y, "," ,start_yaw, ",", max_curvature)
    paths = calc_paths(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    Base.Test.@test length(paths) >= 1

    for path in paths
        Base.Test.@test abs(path.x[1] - start_x) <= 0.01
        Base.Test.@test abs(path.y[1] - start_y) <= 0.01
        Base.Test.@test abs(path.yaw[1] - start_yaw) <= 0.01
        Base.Test.@test abs(path.x[end] - end_x) <= 0.01
        Base.Test.@test abs(path.y[end] - end_y) <= 0.01
        Base.Test.@test abs(path.yaw[end] - end_yaw) <= 0.01

        #course distance check
        d = [sqrt(dx^2+dy^2) for (dx, dy) in zip(diff(path.x[1:end-1]), diff(path.y[1:end-1]))]

        for i in length(d)
            Base.Test.@test abs(d[i] - STEP_SIZE) <= 0.001
        end
    end

end

function test()
    println("Test1")
    start_x = 0.0  # [m]
    start_y = 0.0  # [m]
    start_yaw = deg2rad(10.0)  # [rad]
    end_x = 7.0  # [m]
    end_y = -8.0  # [m]
    end_yaw = deg2rad(50.0)  # [rad]
    max_curvature = 2.0

    check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    start_x = 0.0  # [m]
    start_y = 0.0  # [m]
    start_yaw = deg2rad(10.0)  # [rad]
    end_x = 7.0  # [m]
    end_y = -8.0  # [m]
    end_yaw = deg2rad(-50.0)  # [rad]
    max_curvature = 2.0

    check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    start_x = 0.0  # [m]
    start_y = 10.0  # [m]
    start_yaw = deg2rad(-10.0)  # [rad]
    end_x = -7.0  # [m]
    end_y = -8.0  # [m]
    end_yaw = deg2rad(-50.0)  # [rad]
    max_curvature = 2.0

    check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    start_x = 0.0  # [m]
    start_y = 10.0  # [m]
    start_yaw = deg2rad(-10.0)  # [rad]
    end_x = -7.0  # [m]
    end_y = -8.0  # [m]
    end_yaw = deg2rad(150.0)  # [rad]
    max_curvature = 1.0

    check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    start_x = 0.0  # [m]
    start_y = 10.0  # [m]
    start_yaw = deg2rad(-10.0)  # [rad]
    end_x = 7.0  # [m]
    end_y = 8.0  # [m]
    end_yaw = deg2rad(150.0)  # [rad]
    max_curvature = 2.0
    check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    start_x = -40.0  # [m]
    start_y = 549.0  # [m]
    start_yaw = 2.44346 # [rad]
    end_x =  36.0 # [m]
    end_y =  446.0 # [m]
    end_yaw = -0.698132
    max_curvature = 0.05890904077226434
    check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    # Random test
    for i in 1:100
        start_x = rand()*100.0 - 50.0
        start_y = rand()*100.0 - 50.0
        start_yaw = deg2rad(rand()*360.0 - 180.0)
        end_x = rand()*100.0 - 50.0
        end_y = rand()*100.0 - 50.0
        end_yaw = deg2rad(rand()*360.0 - 180.0)
        max_curvature = rand()/10.0
        # println(i, ",", start_x, ",", start_y,",", start_yaw,",",end_x,",",end_y,",", end_yaw)
        check_path(start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)
    end
end

function main()
    println(PROGRAM_FILE," start!!")
    test()

    start_x = 3.0  # [m]
    start_y = 10.0  # [m]
    start_yaw = deg2rad(40.0)  # [rad]
    end_x = 0.0  # [m]
    end_y = 1.0  # [m]
    end_yaw = deg2rad(0.0)  # [rad]
    max_curvature = 0.1

    @time bpath = calc_shortest_path(
        start_x, start_y, start_yaw, end_x, end_y, end_yaw, max_curvature)

    rc, rds = calc_curvature(bpath.x, bpath.y, bpath.yaw, bpath.directions)

    subplots(1)
    plot(bpath.x, bpath.y,"-r", label=get_label(bpath))

    plot(start_x, start_y)
    plot(end_x, end_y)

    legend()
    grid(true)
    axis("equal")

    subplots(1)
    plot(rc, ".r", label="reeds shepp")
    grid(true)
    title("Curvature")

    show()

    println(PROGRAM_FILE," Done!!")
end

if length(PROGRAM_FILE)!=0 &&
    contains(@__FILE__, PROGRAM_FILE)

    main()
end

end #module

max_stars_repo_name: treyfortmuller/barc

max_issues_repo_name: treyfortmuller/barc


text:
using Images, MLDatasets, ImageCore
using Flux, Statistics, CUDA
using Base.Iterators: partition
using Random
using Flux.Losses: logitbinarycrossentropy
using Flux.Optimise
using ProgressBars
using ImageIO
using ImageMagick
using LinearAlgebra
import Distributions: Uniform
include("hparams.jl")
include("networks.jl")
#using CUDAnative, CUDA, CUDAapi

#if has_cuda()
#    @info "CUDA is on"
    #CUDA.allowscalar(false)
#end

hparams = HPARAMS()

# load the training dataset
train_x, _ = MLDatasets.CIFAR10.traindata(Float32);
# load the test dataset
tester, _ = MLDatasets.CIFAR10.testdata(Float32);

data_x = [train_x[:,:,:,r] for r in partition(1:40000, hparams.batch_size)];
test_x = [tester[:,:,:,r] for r in partition(1:hparams.len_test, hparams.batch_size)];
#data_y = [train_y[r] for r in partition(1:40000,64)];

#=
#
#  Loss functions
#
=#

function d_loss(real_output, fake_output)
	real_loss = log(mean(logitbinarycrossentropy.(
					real_output,
					cu(ones(size(real_output))) |> gpu)))
	fake_loss = log(mean(logitbinarycrossentropy.(
					fake_output,
					cu(zeros(size(fake_output))) |> gpu)))

	return real_loss + fake_loss
end

function g_loss(real_output, fake_output)
	real_loss = log(mean(logitbinarycrossentropy.(
					real_output,
					cu(zeros(size(real_output))) |> gpu)))
	fake_loss = log(mean(logitbinarycrossentropy.(
					fake_output,
					cu(ones(size(fake_output))) |> gpu)))

	return real_loss + fake_loss
end

function o_loss(obs_real, obs_fake, control_factor)
	loss = 0.0

	real_loss = log(mean(logitbinarycrossentropy.(
					obs_real, zeros(size(obs_real)) |> gpu)))
	fake_loss = log(mean(logitbinarycrossentropy.(
					obs_fake, ones(size(obs_fake)) |> gpu)))
	loss = control_factor * abs(real_loss + fake_loss)
	return loss
end
#=
#
# Lipschitz stuff
#
=#

# K-Lipschitz condition
function lipschitz_bound(control_factor,observer, real_preimage, fake_preimage)
	diff_1 = mean(logitbinarycrossentropy.(real_preimage,
										   ones(size(real_preimage)) |> gpu))
	diff_2 = mean(logitbinarycrossentropy.(fake_preimage,
										   zeros(size(fake_preimage)) |> gpu))
	discriminator_distance = abs(diff_1^2 + diff_2^2)

	# assert homeomorphic properties on the observer
	# i.e. cts, 1 -> 1, 0 -> 0
	real_observation =  observer(real_preimage)
	false_observation =  observer(fake_preimage)
	obs_ones = ones(size(false_observation)) |> gpu
	obs_zeros = zeros(size(real_observation)) |> gpu

	diff_1 = log1p(mean(logitbinarycrossentropy.(real_observation, obs_zeros)))
	diff_2 = log1p(mean(logitbinarycrossentropy.(false_observation, obs_ones)))
	observer_distance = (diff_1 + diff_2)

	return discriminator_distance < abs(control_factor * observer_distance)
end

function lipschitz_switch(gen,dscr,obs,control_factor, x, hparams::HPARAMS)
	#noise = cu(randn!(
	#		similar(x, (hparams.latent_dim, hparams.batch_size)))) |> gpu
	noise = cu(randn(size(x))) |> gpu
	real_preimage = dscr(x) |> gpu
	fake_preimage = dscr(gen(noise)) |> gpu

	switch = lipschitz_bound(control_factor,obs, real_preimage, fake_preimage)
	return switch
end
#=
#
#	Separate training modules
#
=#

function make_observations!(gen,dscr,obs, x, opt_dscr,opt_gen,opt_obs,
                             hparams::HPARAMS,control_factor,
                             rng::MersenneTwister, controller::Bool=true)
     counter_in_loop = 0
     loss_obs = 0.0

     rr = hparams.reduction_rate
     lc = hparams.loop_condition
     while controller
         # Now we will train the observer
         #noise = cu(randn!(
		 #similar(x, (hparams.latent_dim, hparams.batch_size)))) |> gpu
		 noise = cu(randn(size(x))) |> gpu
		 fakers = gen(noise)

         x = x |> gpu
         # data from the discriminator
         real_preimage = dscr(x)
         fake_preimage = dscr(fakers)
         # stuff from the observer
         real_obs = obs(real_preimage)
         fake_obs = obs(fake_preimage)

         # now take the parameters of the observer
         ps_obs = Flux.params(obs)
         loss_obs, back_obs = Flux.pullback(ps_obs) do
             o_loss(real_obs, fake_obs, control_factor)
         end
		 grad_obs = back_obs(1f0)
		 update!(opt_obs, ps_obs, grad_obs)
		 #noise = cu(randn!(
				#similar(x,(hparams.latent_dim, hparams.batch_size)))) |> gpu
		 noise = cu(randn(size(x)))|>gpu
		 fake_input = gen(noise)
		 ps_dscr = Flux.params(dscr)

		 #Taking gradient
		 loss_dscr, back_dscr = Flux.pullback(ps_dscr) do
			 d_loss(dscr(fake_input), dscr(fake_input))
		 end
		 grad_dscr = back_dscr(1f0)
		 update!(opt_dscr, ps_dscr, grad_dscr)

		 # apply the Lipschitz condition
		 real_preimage =  dscr(x) |> gpu
		 fake_preimage =  dscr(gen(noise)) |> gpu
		 controller = !lipschitz_bound(control_factor, obs,
								real_preimage,fake_preimage)

		 counter_in_loop += 1

		 if counter_in_loop > hparams.loop_condition * hparams.multiplier_lc
			 controller = false
			 if hparams.loop_condition > hparams.reduction_limit
				 hparams.reduction_rate = rr*(1.0 + randn(1)[1])
			 end
		 elseif counter_in_loop > hparams.loop_condition
			 # train the generator when the loop_condition is violated
			 loss_gen = train_generator!(gen,dscr,x,opt_gen, hparams)
			 end
		 end
return loss_obs, control_factor
end

# Train discriminator
function train_discriminator!(gen, dscr, x, opt_dscr, hparams::HPARAMS)
	# initialize
	loss_obs = 0.0
	noise = cu(randn(size(x))) |> gpu #randn!(similar(x, (hparams.latent_dim, hparams.batch_size))) |>gpu
	fake_input = gen(noise) |> gpu
	ps = Flux.params(dscr)
	#Taking gradient
	loss, back = Flux.pullback(ps) do
		d_loss(dscr(x)|>gpu, dscr(fake_input)|>gpu)
	end
	grad = back(1f0)
	update!(opt_dscr, ps, grad)

	return loss
end

# Train Generator (and the observer...)
function train_generator!(gen, dscr, x, opt_gen, hparams::HPARAMS)
	noise = cu(randn(size(x))) |> gpu #randn!(
			#similar(x, (hparams.latent_dim, hparams.batch_size))) |> gpu

	# Taking gradient
		control_factor = randn(1)[1] |>gpu
		loss = 0.0
		noise = cu(randn(size(x))) |>gpu #randn!(
				#similar(x, (hparams.latent_dim, hparams.batch_size))) |> gpu

		ps = Flux.params(gen) |> gpu
		dscr_x = dscr(x)
		loss, back = Flux.pullback(ps) do
			g_loss(dscr(gen(noise)), dscr_x)
		end
		grad = back(1f0)
		update!(opt_gen, ps, grad)

		return loss
end

#=
#
#	Main Train Module
#
=#

function train(; kws...)
	hparams = HPARAMS(;kws...)
	if ispath("./logs")
		rm("./logs", recursive=true)
		mkpath("./logs")
	else
		mkpath("./logs")
	end

	if ispath("./output")
		rm("./output", recursive=true)
		mkpath("./output")
	else
		mkpath("./output")
	end

	# Path to the logger
	a = "logs/logs.csv"
	first_line = "Step, Discriminator loss, Generator loss, Observer loss, Control factor\n"
	io = open(a,"a")
	write(io, first_line)
	# load the training dataset
	train_x, _ = MLDatasets.CIFAR10.traindata(Float32);
	# load the test dataset
	test_x, _ = MLDatasets.CIFAR10.testdata(Float32);
	data = [train_x[:,:,:,r] for r in partition(1:40000, 64)];
	#data_y = [train_y[r] for r in partition(1:40000,64)

	fixed_noise = [randn(hparams.latent_dim, 1) |> gpu
					for _=1:hparams.output_x*hparams.output_y]

	# Discriminator
	dscr = Discriminator()
	dscr = dscr |> gpu
	# Generator
	gen =  Generator(hparams)
	gen = gen |> gpu
	# Observer
	obs = Observer()
	obs = obs |> gpu

	# Optimizers
	opt_dscr = Optimiser(ExpDecay(),ADAM(hparams.lr_dscr))
	opt_gen = Optimiser(ExpDecay(),ADAM(hparams.lr_gen))
	opt_obs = Optimiser(ExpDecay(),ADAM(hparams.lr_obs))
	# Training
	train_steps = 0
	switch::Bool = false
	loss_obs = 0.0
	rng = MersenneTwister(1234)
	control_factor = rand(Uniform(0,hparams.max_lipschitz_bound),1)[1]
	for ep in 1:hparams.epochs
		@info "Epoch $ep"
		for y in ProgressBar(data)
			x = cu(y)
			# add noise on x every hparams.noise_addition steps
			if train_steps % hparams.noise_addition == 0
				x = x .* cu(randn(size(x)))
			elseif train_steps % hparams.noise_addition == 2
				x = x .+ cu(randn(size(x)))
			end

			# train the discriminator / observer first
			loss_dscr = train_discriminator!(gen, dscr, x, opt_dscr, hparams)
			# then train the generator
			loss_gen = train_generator!(gen, dscr, x, opt_gen, hparams)

			# this will determine whether we'd need to make observations or not
			if train_steps > hparams.start_observations
				#switch = lipschitz_switch(gen,dscr,obs,control_factor, x, hparams)
				switch = !switch
			end

			# now make the observations if needed
			if switch
				loss_obs, control_factor = make_observations!(
												gen,dscr,obs, x,
												opt_dscr,opt_gen,opt_obs,
												hparams,control_factor,
												rng, switch)
				loss_gen = train_generator!(gen,dscr,x,opt_gen, hparams)
			end

			# print the losses and the control_factor to a csv file
			if mod(train_steps, 0:10) == 0

				printer = "$(train_steps) , $(loss_dscr) , $(loss_gen) , $(loss_obs) , $(control_factor) \n"
				write(io, printer)
			end

			# output the sample images
			if train_steps % hparams.verbose_freq == 0
				@info("Train step $(train_steps), Discriminator loss = $(loss_dscr), Generator loss = $(loss_gen), Observer loss = $(loss_obs)")
				#@info("Test the discriminator")
				#test_val = 0.0
				#for a in ProgressBar(test_x)
				#	println(size(a))
				#	exit()
				#	noise = cu(randn(size(a))) |> gpu
				#	fake_input = gen(noise)
				#	test_d_loss = d_loss(dscr(a),dscr(fake_input))
				#	test_val += test_d_loss
				#end
				#a = test_x[:,:,:,mod(train_steps, 9999)]
				#noise = cu(randn(size(a))) |> gpu
				#fake = gen(noise)
				#test_val = d_loss(dscr(a),dscr(fake_input))
				#println("The test result: ",test_val)
				# Save generated fake image
				#output_image = create_output_image(gen, fixed_noise, hparams)
				#output_filename = "output/step_$(train_steps).png"
				#println(output_filename)
				#save(output_filename, output_image)
			end
			train_steps += 1
		end
	end
	close(io) # closing the log file
	#output_image = create_output_image(gen, fixed_noise, hparams)
	#save("output/last_step_$(train_steps).png", output_image)
end

#
#	Run the train function
#
cd(@__DIR__)
train()


text:
export image_converter

using mlpack._Internal.io

import mlpack_jll
const image_converterLibrary = mlpack_jll.libmlpack_julia_image_converter

# Call the C binding of the mlpack image_converter binding.
function image_converter_mlpackMain()
  success = ccall((:image_converter, image_converterLibrary), Bool, ())
  if !success
    # Throw an exception---false means there was a C++ exception.
    throw(ErrorException("mlpack binding error; see output"))
  end
end

" Internal module to hold utility functions. "
module image_converter_internal
  import ..image_converterLibrary

end # module

"""
    image_converter(input; [channels, dataset, height, quality, save, verbose, width])

This utility takes an image or an array of images and loads them to a matrix.
You can optionally specify the height `height` width `width` and channel
`channels` of the images that needs to be loaded; otherwise, these parameters
will be automatically detected from the image.
There are other options too, that can be specified such as `quality`.

You can also provide a dataset and save them as images using `dataset` and
`save` as an parameter.

 An example to load an image :

```julia
julia> Y = image_converter(X; channels=3, height=256, width=256)
```

 An example to save an image is :

```julia
julia> using CSV
julia> Y = CSV.read("Y.csv")
julia> _ = image_converter(X; channels=3, dataset=Y, height=256,
            save=1, width=256)
```

# Arguments

 - `input::Vector{String}`: Image filenames which have to be
      loaded/saved.
 - `channels::Int`: Number of channels in the image.  Default value `0`.

 - `dataset::Array{Float64, 2}`: Input matrix to save as images.
 - `height::Int`: Height of the images.  Default value `0`.

 - `quality::Int`: Compression of the image if saved as jpg (0-100).
      Default value `90`.

 - `save::Bool`: Save a dataset as images.  Default value `false`.

 - `verbose::Bool`: Display informational messages and the full list of
      parameters and timers at the end of execution.  Default value `false`.

 - `width::Int`: Width of the image.  Default value `0`.

# Return values

 - `output::Array{Float64, 2}`: Matrix to save images data to, Onlyneeded
      if you are specifying 'save' option.

"""
function image_converter(input::Vector{String};
                         channels::Union{Int, Missing} = missing,
                         dataset = missing,
                         height::Union{Int, Missing} = missing,
                         quality::Union{Int, Missing} = missing,
                         save::Union{Bool, Missing} = missing,
                         verbose::Union{Bool, Missing} = missing,
                         width::Union{Int, Missing} = missing,
                         points_are_rows::Bool = true)
  # Force the symbols to load.
  ccall((:loadSymbols, image_converterLibrary), Nothing, ());

  IORestoreSettings("Image Converter")

  # Process each input argument before calling mlpackMain().
  IOSetParam("input", input)
  if !ismissing(channels)
    IOSetParam("channels", convert(Int, channels))
  end
  if !ismissing(dataset)
    IOSetParamMat("dataset", dataset, points_are_rows)
  end
  if !ismissing(height)
    IOSetParam("height", convert(Int, height))
  end
  if !ismissing(quality)
    IOSetParam("quality", convert(Int, quality))
  end
  if !ismissing(save)
    IOSetParam("save", convert(Bool, save))
  end
  if !ismissing(width)
    IOSetParam("width", convert(Int, width))
  end
  if verbose !== nothing && verbose === true
    IOEnableVerbose()
  else
    IODisableVerbose()
  end

  IOSetPassed("output")
  # Call the program.
  image_converter_mlpackMain()

  return IOGetParamMat("output", points_are_rows)
end


text:
@mlj_model mutable struct S1 <: Supervised
end

@mlj_model mutable struct U1 <: Unsupervised
end

@mlj_model mutable struct D1 <: Deterministic
    a::Int = 1::(_ > 0)
end

@mlj_model mutable struct P1 <: Probabilistic
    a::Int = 1::(_ > 0)
end

@mlj_model mutable struct I1 <: Interval
end

foo(::P1) = 0
bar(::P1) = nothing

@testset "traits" begin
    ms = S1()
    mu = U1()
    md = D1()
    mp = P1()
    mi = I1()

    @test input_scitype(ms)  == Unknown
    @test output_scitype(ms) == Unknown
    @test target_scitype(ms) == Unknown
    @test is_pure_julia(ms)  == false

    @test package_name(ms)    == "unknown"
    @test package_license(ms) == "unknown"
    @test load_path(ms)       == "unknown"
    @test package_uuid(ms)    == "unknown"
    @test package_url(ms)     == "unknown"

    @test is_wrapper(ms)       == false
    @test supports_online(ms)  == false
    @test supports_weights(ms) == false

    @test hyperparameter_ranges(md) == (nothing,)

    @test docstring(ms) == "S1 from unknown.jl.\n[Documentation](unknown)."
    @test name(ms)      == "S1"

    @test is_supervised(ms)
    @test !is_supervised(mu)
    @test prediction_type(ms) == :unknown
    @test prediction_type(md) == :deterministic
    @test prediction_type(mp) == :probabilistic
    @test prediction_type(mi) == :interval

    @test hyperparameters(md) == (:a,)
    @test hyperparameter_types(md) == ("Int64",)

    # implemented methods is deferred
    setlight()
    @test_throws M.InterfaceError implemented_methods(mp)
    setfull()
    M.implemented_methods(::FI, M::Type{<:MLJType}) =
        getfield.(methodswith(M), :name)
    @test Set(implemented_methods(mp)) == Set([:clean!,:bar,:foo])
end

struct FooMeasure <: MLJType end

@testset "extras" begin
    @test docstring(Float64) == "Float64"
    @test docstring(FooMeasure) == "FooMeasure"
    @test name(Float64) == "Float64"

    df = DataFrame(a=randn(2), b=randn(2))
    @test string(M.coretype(typeof(df))) == "DataFrame"
end