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

"""
    accept_page(accept_code, accept_type, page_id)
    accept_page(accept_code, accept_type, page_id, params::Dict{String,<:Any})

Used to acknowledge an engagement to a contact channel during an incident.

# Arguments
- `accept_code`: The accept code is a 6-digit code used to acknowledge the page.
- `accept_type`: The type indicates if the page was DELIVERED or READ.
- `page_id`: The Amazon Resource Name (ARN) of the engagement to a contact channel.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AcceptCodeValidation"`: An optional field that Incident Manager uses to ENFORCE
  AcceptCode validation when acknowledging an page. Acknowledgement can occur by replying to
  a page, or when entering the AcceptCode in the console. Enforcing AcceptCode validation
  causes Incident Manager to verify that the code entered by the user matches the code sent
  by Incident Manager with the page. Incident Manager can also IGNORE AcceptCode validation.
  Ignoring AcceptCode validation causes Incident Manager to accept any value entered for the
  AcceptCode.
- `"ContactChannelId"`: The ARN of the contact channel.
- `"Note"`: Information provided by the user when the user acknowledges the page.
"""
function accept_page(
    AcceptCode, AcceptType, PageId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "AcceptPage",
        Dict{String,Any}(
            "AcceptCode" => AcceptCode, "AcceptType" => AcceptType, "PageId" => PageId
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function accept_page(
    AcceptCode,
    AcceptType,
    PageId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "AcceptPage",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "AcceptCode" => AcceptCode,
                    "AcceptType" => AcceptType,
                    "PageId" => PageId,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    activate_contact_channel(activation_code, contact_channel_id)
    activate_contact_channel(activation_code, contact_channel_id, params::Dict{String,<:Any})

Activates a contact's contact channel. Incident Manager can't engage a contact until the
contact channel has been activated.

# Arguments
- `activation_code`: The code sent to the contact channel when it was created in the
  contact.
- `contact_channel_id`: The Amazon Resource Name (ARN) of the contact channel.

"""
function activate_contact_channel(
    ActivationCode, ContactChannelId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "ActivateContactChannel",
        Dict{String,Any}(
            "ActivationCode" => ActivationCode, "ContactChannelId" => ContactChannelId
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function activate_contact_channel(
    ActivationCode,
    ContactChannelId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "ActivateContactChannel",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ActivationCode" => ActivationCode,
                    "ContactChannelId" => ContactChannelId,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_contact(alias, plan, type)
    create_contact(alias, plan, type, params::Dict{String,<:Any})

Contacts are either the contacts that Incident Manager engages during an incident or the
escalation plans that Incident Manager uses to engage contacts in phases during an
incident.

# Arguments
- `alias`: The short name to quickly identify a contact or escalation plan. The contact
  alias must be unique and identifiable.
- `plan`: A list of stages. A contact has an engagement plan with stages that contact
  specified contact channels. An escalation plan uses stages that contact specified contacts.
- `type`: To create an escalation plan use ESCALATION. To create a contact use PERSONAL.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DisplayName"`: The full name of the contact or escalation plan.
- `"IdempotencyToken"`: A token ensuring that the operation is called only once with the
  specified details.
- `"Tags"`: Adds a tag to the target. You can only tag resources created in the first
  Region of your replication set.
"""
function create_contact(
    Alias, Plan, Type; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "CreateContact",
        Dict{String,Any}(
            "Alias" => Alias,
            "Plan" => Plan,
            "Type" => Type,
            "IdempotencyToken" => string(uuid4()),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_contact(
    Alias,
    Plan,
    Type,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "CreateContact",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "Alias" => Alias,
                    "Plan" => Plan,
                    "Type" => Type,
                    "IdempotencyToken" => string(uuid4()),
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_contact_channel(contact_id, delivery_address, name, type)
    create_contact_channel(contact_id, delivery_address, name, type, params::Dict{String,<:Any})

A contact channel is the method that Incident Manager uses to engage your contact.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact you are adding the contact
  channel to.
- `delivery_address`: The details that Incident Manager uses when trying to engage the
  contact channel. The format is dependent on the type of the contact channel. The following
  are the expected formats:   SMS - '+' followed by the country code and phone number   VOICE
  - '+' followed by the country code and phone number   EMAIL - any standard email format
- `name`: The name of the contact channel.
- `type`: Incident Manager supports three types of contact channels:    SMS     VOICE
  EMAIL

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DeferActivation"`: If you want to activate the channel at a later time, you can choose
  to defer activation. Incident Manager can't engage your contact channel until it has been
  activated.
- `"IdempotencyToken"`: A token ensuring that the operation is called only once with the
  specified details.
"""
function create_contact_channel(
    ContactId,
    DeliveryAddress,
    Name,
    Type;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "CreateContactChannel",
        Dict{String,Any}(
            "ContactId" => ContactId,
            "DeliveryAddress" => DeliveryAddress,
            "Name" => Name,
            "Type" => Type,
            "IdempotencyToken" => string(uuid4()),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_contact_channel(
    ContactId,
    DeliveryAddress,
    Name,
    Type,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "CreateContactChannel",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ContactId" => ContactId,
                    "DeliveryAddress" => DeliveryAddress,
                    "Name" => Name,
                    "Type" => Type,
                    "IdempotencyToken" => string(uuid4()),
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

To no longer receive Incident Manager engagements to a contact channel, you can deactivate
the channel.

# Arguments
- `contact_channel_id`: The Amazon Resource Name (ARN) of the contact channel you're
  deactivating.

"""
function deactivate_contact_channel(
    ContactChannelId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "DeactivateContactChannel",
        Dict{String,Any}("ContactChannelId" => ContactChannelId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function deactivate_contact_channel(
    ContactChannelId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "DeactivateContactChannel",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("ContactChannelId" => ContactChannelId), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

To remove a contact from Incident Manager, you can delete the contact. Deleting a contact
removes them from all escalation plans and related response plans. Deleting an escalation
plan removes it from all related response plans. You will have to recreate the contact and
its contact channels before you can use it again.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact that you're deleting.

"""
function delete_contact(ContactId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "DeleteContact",
        Dict{String,Any}("ContactId" => ContactId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_contact(
    ContactId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "DeleteContact",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ContactId" => ContactId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

To no longer receive engagements on a contact channel, you can delete the channel from a
contact. Deleting the contact channel removes it from the contact's engagement plan. If you
delete the only contact channel for a contact, you won't be able to engage that contact
during an incident.

# Arguments
- `contact_channel_id`: The Amazon Resource Name (ARN) of the contact channel.

"""
function delete_contact_channel(
    ContactChannelId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "DeleteContactChannel",
        Dict{String,Any}("ContactChannelId" => ContactChannelId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_contact_channel(
    ContactChannelId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "DeleteContactChannel",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("ContactChannelId" => ContactChannelId), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Incident Manager uses engagements to engage contacts and escalation plans during an
incident. Use this command to describe the engagement that occurred during an incident.

# Arguments
- `engagement_id`: The Amazon Resource Name (ARN) of the engagement you want the details of.

"""
function describe_engagement(
    EngagementId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "DescribeEngagement",
        Dict{String,Any}("EngagementId" => EngagementId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_engagement(
    EngagementId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "DescribeEngagement",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("EngagementId" => EngagementId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists details of the engagement to a contact channel.

# Arguments
- `page_id`: The ID of the engagement to a contact channel.

"""
function describe_page(PageId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "DescribePage",
        Dict{String,Any}("PageId" => PageId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_page(
    PageId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "DescribePage",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("PageId" => PageId), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves information about the specified contact or escalation plan.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact or escalation plan.

"""
function get_contact(ContactId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "GetContact",
        Dict{String,Any}("ContactId" => ContactId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_contact(
    ContactId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "GetContact",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ContactId" => ContactId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

List details about a specific contact channel.

# Arguments
- `contact_channel_id`: The Amazon Resource Name (ARN) of the contact channel you want
  information about.

"""
function get_contact_channel(
    ContactChannelId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "GetContactChannel",
        Dict{String,Any}("ContactChannelId" => ContactChannelId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_contact_channel(
    ContactChannelId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "GetContactChannel",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("ContactChannelId" => ContactChannelId), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves the resource policies attached to the specified contact or escalation plan.

# Arguments
- `contact_arn`: The Amazon Resource Name (ARN) of the contact or escalation plan.

"""
function get_contact_policy(ContactArn; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "GetContactPolicy",
        Dict{String,Any}("ContactArn" => ContactArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_contact_policy(
    ContactArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "GetContactPolicy",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ContactArn" => ContactArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists all contact channels for the specified contact.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of contact channels per page.
- `"NextToken"`: The pagination token to continue to the next page of results.
"""
function list_contact_channels(ContactId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "ListContactChannels",
        Dict{String,Any}("ContactId" => ContactId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_contact_channels(
    ContactId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "ListContactChannels",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ContactId" => ContactId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists all contacts and escalation plans in Incident Manager.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AliasPrefix"`: Used to list only contacts who's aliases start with the specified prefix.
- `"MaxResults"`: The maximum number of contacts and escalation plans per page of results.
- `"NextToken"`: The pagination token to continue to the next page of results.
- `"Type"`: The type of contact. A contact is type PERSONAL and an escalation plan is type
  ESCALATION.
"""
function list_contacts(; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "ListContacts"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_contacts(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "ListContacts", params; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end

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

Lists all engagements that have happened in an incident.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"IncidentId"`: The Amazon Resource Name (ARN) of the incident you're listing engagements
  for.
- `"MaxResults"`: The maximum number of engagements per page of results.
- `"NextToken"`: The pagination token to continue to the next page of results.
- `"TimeRangeValue"`: The time range to lists engagements for an incident.
"""
function list_engagements(; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "ListEngagements"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_engagements(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "ListEngagements", params; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end

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

Lists all of the engagements to contact channels that have been acknowledged.

# Arguments
- `page_id`: The Amazon Resource Name (ARN) of the engagement to a specific contact channel.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of acknowledgements per page of results.
- `"NextToken"`: The pagination token to continue to the next page of results.
"""
function list_page_receipts(PageId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "ListPageReceipts",
        Dict{String,Any}("PageId" => PageId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_page_receipts(
    PageId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "ListPageReceipts",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("PageId" => PageId), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists the engagements to a contact's contact channels.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact you are retrieving
  engagements for.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of engagements to contact channels to list per page of
  results.
- `"NextToken"`: The pagination token to continue to the next page of results.
"""
function list_pages_by_contact(ContactId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "ListPagesByContact",
        Dict{String,Any}("ContactId" => ContactId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_pages_by_contact(
    ContactId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "ListPagesByContact",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ContactId" => ContactId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists the engagements to contact channels that occurred by engaging a contact.

# Arguments
- `engagement_id`: The Amazon Resource Name (ARN) of the engagement.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of engagements to contact channels to list per page of
  results.
- `"NextToken"`: The pagination token to continue to the next page of results.
"""
function list_pages_by_engagement(
    EngagementId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "ListPagesByEngagement",
        Dict{String,Any}("EngagementId" => EngagementId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_pages_by_engagement(
    EngagementId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "ListPagesByEngagement",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("EngagementId" => EngagementId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists the tags of an escalation plan or contact.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the contact or escalation plan.

"""
function list_tags_for_resource(
    ResourceARN; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "ListTagsForResource",
        Dict{String,Any}("ResourceARN" => ResourceARN);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_tags_for_resource(
    ResourceARN,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "ListTagsForResource",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ResourceARN" => ResourceARN), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    put_contact_policy(contact_arn, policy)
    put_contact_policy(contact_arn, policy, params::Dict{String,<:Any})

Adds a resource to the specified contact or escalation plan.

# Arguments
- `contact_arn`: The Amazon Resource Name (ARN) of the contact or escalation plan.
- `policy`: Details of the resource policy.

"""
function put_contact_policy(
    ContactArn, Policy; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "PutContactPolicy",
        Dict{String,Any}("ContactArn" => ContactArn, "Policy" => Policy);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function put_contact_policy(
    ContactArn,
    Policy,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "PutContactPolicy",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ContactArn" => ContactArn, "Policy" => Policy),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Sends an activation code to a contact channel. The contact can use this code to activate
the contact channel in the console or with the ActivateChannel operation. Incident Manager
can't engage a contact channel until it has been activated.

# Arguments
- `contact_channel_id`: The Amazon Resource Name (ARN) of the contact channel.

"""
function send_activation_code(
    ContactChannelId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "SendActivationCode",
        Dict{String,Any}("ContactChannelId" => ContactChannelId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function send_activation_code(
    ContactChannelId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "SendActivationCode",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("ContactChannelId" => ContactChannelId), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    start_engagement(contact_id, content, sender, subject)
    start_engagement(contact_id, content, sender, subject, params::Dict{String,<:Any})

Starts an engagement to a contact or escalation plan. The engagement engages each contact
specified in the incident.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact being engaged.
- `content`: The secure content of the message that was sent to the contact. Use this field
  for engagements to VOICE or EMAIL.
- `sender`: The user that started the engagement.
- `subject`: The secure subject of the message that was sent to the contact. Use this field
  for engagements to VOICE or EMAIL.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"IdempotencyToken"`: A token ensuring that the operation is called only once with the
  specified details.
- `"IncidentId"`: The ARN of the incident that the engagement is part of.
- `"PublicContent"`: The insecure content of the message that was sent to the contact. Use
  this field for engagements to SMS.
- `"PublicSubject"`: The insecure subject of the message that was sent to the contact. Use
  this field for engagements to SMS.
"""
function start_engagement(
    ContactId, Content, Sender, Subject; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "StartEngagement",
        Dict{String,Any}(
            "ContactId" => ContactId,
            "Content" => Content,
            "Sender" => Sender,
            "Subject" => Subject,
            "IdempotencyToken" => string(uuid4()),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function start_engagement(
    ContactId,
    Content,
    Sender,
    Subject,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "StartEngagement",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ContactId" => ContactId,
                    "Content" => Content,
                    "Sender" => Sender,
                    "Subject" => Subject,
                    "IdempotencyToken" => string(uuid4()),
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Stops an engagement before it finishes the final stage of the escalation plan or engagement
plan. Further contacts aren't engaged.

# Arguments
- `engagement_id`: The Amazon Resource Name (ARN) of the engagement.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Reason"`: The reason that you're stopping the engagement.
"""
function stop_engagement(EngagementId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "StopEngagement",
        Dict{String,Any}("EngagementId" => EngagementId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function stop_engagement(
    EngagementId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "StopEngagement",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("EngagementId" => EngagementId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    tag_resource(resource_arn, tags)
    tag_resource(resource_arn, tags, params::Dict{String,<:Any})

Tags a contact or escalation plan. You can tag only contacts and escalation plans in the
first region of your replication set.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the contact or escalation plan.
- `tags`: A list of tags that you are adding to the contact or escalation plan.

"""
function tag_resource(ResourceARN, Tags; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "TagResource",
        Dict{String,Any}("ResourceARN" => ResourceARN, "Tags" => Tags);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function tag_resource(
    ResourceARN,
    Tags,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "TagResource",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ResourceARN" => ResourceARN, "Tags" => Tags),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    untag_resource(resource_arn, tag_keys)
    untag_resource(resource_arn, tag_keys, params::Dict{String,<:Any})

Removes tags from the specified resource.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the contact or escalation plan.
- `tag_keys`: The key of the tag that you want to remove.

"""
function untag_resource(
    ResourceARN, TagKeys; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "UntagResource",
        Dict{String,Any}("ResourceARN" => ResourceARN, "TagKeys" => TagKeys);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function untag_resource(
    ResourceARN,
    TagKeys,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "UntagResource",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ResourceARN" => ResourceARN, "TagKeys" => TagKeys),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Updates the contact or escalation plan specified.

# Arguments
- `contact_id`: The Amazon Resource Name (ARN) of the contact or escalation plan you're
  updating.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DisplayName"`: The full name of the contact or escalation plan.
- `"Plan"`: A list of stages. A contact has an engagement plan with stages for specified
  contact channels. An escalation plan uses these stages to contact specified contacts.
"""
function update_contact(ContactId; aws_config::AbstractAWSConfig=global_aws_config())
    return ssm_contacts(
        "UpdateContact",
        Dict{String,Any}("ContactId" => ContactId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_contact(
    ContactId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "UpdateContact",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ContactId" => ContactId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Updates a contact's contact channel.

# Arguments
- `contact_channel_id`: The Amazon Resource Name (ARN) of the contact channel you want to
  update.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DeliveryAddress"`: The details that Incident Manager uses when trying to engage the
  contact channel.
- `"Name"`: The name of the contact channel.
"""
function update_contact_channel(
    ContactChannelId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return ssm_contacts(
        "UpdateContactChannel",
        Dict{String,Any}("ContactChannelId" => ContactChannelId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_contact_channel(
    ContactChannelId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return ssm_contacts(
        "UpdateContactChannel",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("ContactChannelId" => ContactChannelId), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end


text:
module MITprof

using Dates, MeshArrays, NCDatasets, OrderedCollections, UnPack, Glob, DataFrames, CSV

import ArgoData.ProfileNative
import ArgoData.ProfileStandard
import ArgoData.MITprofStandard
import ArgoData.ArgoTools
import ArgoData.GriddedFields
import ArgoData.GDAC
import ArgoData.thisversion

## writing MITprof files

"""
    MITprof.write(meta,profiles,profiles_std;path="")

Create an MITprof file from meta data + profiles during `MITprof.format`.

```
MITprof.write(meta,profiles,profiles_std)
```
"""
function write(meta::Dict,profiles::Array,profiles_std::Array;path="")

    isempty(path) ? p=tempdir() : p=path
    fil=joinpath(p,meta["fileOut"])

    z=Float64.(meta["depthLevels"])
    k=findall((z.>=meta["depthRange"][1]) .& (z.<=meta["depthRange"][2]))

    iPROF = length(profiles[:])
    iDEPTH = length(k)

    ##

    NCDataset(fil,"c") do ds
        defDim(ds,"iPROF",iPROF)
        defDim(ds,"iDEPTH",iDEPTH)
        ds.attrib["title"] = "MITprof file created by ArgoData.jl (v$(thisversion))"
    end

    ##

    NCDataset(fil,"a") do ds
      defVar(ds,"prof_depth",z[k],("iDEPTH",),
            attrib = OrderedDict(
         "units" => "m",
         "_FillValue" => -9999.,
         "long_name" => "Depth"
      ))
    end

    ##

    ID=parse(Int,split(basename(fil),'_')[1])
    NCDataset(fil,"a") do ds
        defVar(ds,"prof_ID",fill(ID,iPROF),("iPROF",),
            attrib = OrderedDict(
        "long_name" => "wmo number"
        ))
    end

    ##

    data1 = Array{Union{Missing, Float64}, 1}(undef, iPROF)

    ##

    [data1[i]=profiles[i].lon[1] for i in 1:iPROF]
    ncwrite_1d(data1,fil,"prof_lon","Longitude (degree East)","degrees_east")

    [data1[i]=profiles[i].lat[1] for i in 1:iPROF]
    ncwrite_1d(data1,fil,"prof_lat","Latitude (degree North)","degrees_north")

    [data1[i]=profiles[i].date[1] for i in 1:iPROF]
    ncwrite_1d(data1,fil,"prof_date","Julian day since Jan-1-0000"," ") ##need units

    [data1[i]=profiles[i].ymd[1] for i in 1:iPROF]
    ncwrite_1d(data1,fil,"prof_YYYYMMDD","year (4 digits), month (2 digits), day (2 digits)"," ") ##need units

    [data1[i]=profiles[i].hms[1] for i in 1:iPROF]
    ncwrite_1d(data1,fil,"prof_HHMMSS","hour (2 digits), minute (2 digits), second (2 digits)"," ") ##need units

# 	double prof_basin(iPROF) ;
# 	double prof_point(iPROF) ;

    ##

    data = Array{Union{Missing, Float64}, 2}(undef, iPROF, iDEPTH)

    ##

    [data[i,:].=profiles_std[i].T[k] for i in 1:iPROF]
    ncwrite_2d(data,fil,"prof_T","Temperature","degree_Celsius")

    [data[i,:].=profiles_std[i].Tweight[k] for i in 1:iPROF]
    ncwrite_2d(data,fil,"prof_Tweight","Temperature least-square weight","(degree C)^-2")

    [data[i,:].=profiles_std[i].Testim[k] for i in 1:iPROF]
    ncwrite_2d(data,fil,"prof_Testim","Temperature atlas (monthly clim.)","degree_Celsius")

# 	double prof_Terr(iPROF, iDEPTH) ;
# 	double prof_Tflag(iPROF, iDEPTH) ;

    ##

    [data[i,:].=profiles_std[i].S[k] for i in 1:iPROF]
    ncwrite_2d(data,fil,"prof_S","Salinity","psu")

    [data[i,:].=profiles_std[i].Sweight[k] for i in 1:iPROF]
    ncwrite_2d(data,fil,"prof_Sweight","Salinity least-square weight","(psu)^-2")

    [data[i,:].=profiles_std[i].Sestim[k] for i in 1:iPROF]
    ncwrite_2d(data,fil,"prof_Sestim","Salinity atlas (monthly clim.)","psu")

# 	double prof_Serr(iPROF, iDEPTH) ;
# 	double prof_Sflag(iPROF, iDEPTH) ;

    fil
end

##

"""
    write(fil::String,mp::MITprofStandard)

Create an MITprof file from an MITprofStandard input.
"""
write(fil::String,mp::MITprofStandard) = write(fil,[mp])

"""
    write(fil::String,mps::Vector{MITprofStandard})

Create an MITprof file from a vector of MITprofStandard inputs.
"""
function write(fil::String,mps::Vector{MITprofStandard})

    nps=cumsum([size(mps[i].T,1) for i in 1:length(mps)])

    iPROF = nps[end]
    iDEPTH = size(mps[1].T,2)

    NCDataset(fil,"c") do ds
        defDim(ds,"iPROF",iPROF)
        defDim(ds,"iDEPTH",iDEPTH)
        ds.attrib["title"] = "MITprof file created by ArgoData.jl (v$(thisversion))"
    end

    ##

    list_variables=(:lon,:lat,:date,:depth,:T,:Te,:Tw,:S,:Se,:Sw)
    #to be added : ID, ymd, hms, and maybe more
    [defVar_fromVar(fil,getfield(mps[1],var)) for var in list_variables]

    list_variables=(:lon,:lat,:date,:T,:Te,:Tw,:S,:Se,:Sw)
    ds=NCDataset(fil,"a")
    for var in list_variables
        for ii in 1:length(mps)
            tmp=getfield(mps[ii],var)
            ii==1 ? jj=collect(1:nps[1]) : jj=collect(nps[ii-1]+1:nps[ii])
            if ndims(tmp)==1
                ds[name(tmp)][jj]=tmp[:]
            else
                ds[name(tmp)][jj,:]=tmp[:,:]
            end
        end
    end
    close(ds)

end

##

function ncread(f::String,v::String)
    Dataset(f,"r") do ds
        ds[v][:]
    end
end

function defVar_fromVar(fil,var)
    T=eltype(skipmissing(var))
    NCDataset(fil,"a") do ds
        defVar(ds,name(var),T,dimnames(var),
            attrib = OrderedDict(
            "units" => var.attrib["units"],
            "_FillValue" => var.attrib["_FillValue"],
            "long_name" => var.attrib["long_name"]
        ))
    end
end

function ncwrite_2d(data,fil,name,long_name,units)
    NCDataset(fil,"a") do ds
      defVar(ds,name,data,("iPROF","iDEPTH"),
            attrib = OrderedDict(
         "units" => units,
         "_FillValue" => -9999.,
         "long_name" => long_name
      ))
    end
end

function ncwrite_1d(data,fil,name,long_name,units)
    NCDataset(fil,"a") do ds
        defVar(ds,name,data,("iPROF",),
            attrib = OrderedDict(
        "units" => units,
        "_FillValue" => -9999.,
        "long_name" => long_name
        ))
    end
end

##

"""
    format(gridded_fields,input_file)

From Argo file name as input : read input file content, process into the MITprof format, and write to MITprof file.

```
MITprof.format(gridded_fields,input_file)
```
"""
function format(gridded_fields,input_file)
    output_file=input_file[1:end-8]*"_MITprof.nc"
    meta=ArgoTools.meta(input_file,output_file)
    format(meta,gridded_fields,input_file,output_file)
end

"""
    format(meta,gridded_fields,input_file,output_file="")

From Argo file name as input : read input file content, process into the MITprof format, and write to MITprof file.

```
MITprof.format(meta,gridded_fields,input_file)
```
"""
function format(meta,gridded_fields,input_file,output_file="")
    @unpack Γ,msk,T,S,σT,σS = gridded_fields
    z_std = meta["z_std"]

    isempty(output_file) ? output_file=joinpath(tempdir(),"MITprof_"*input_file) : nothing

    argo_data=Dataset(input_file)
    haskey(argo_data.dim,"N_PROF") ? np=argo_data.dim["N_PROF"] : np=NaN

    nz=length(z_std)

    profiles=Array{ProfileNative,1}(undef,np)
    profiles_std=Array{ProfileStandard,1}(undef,np)

    prof_σT=Array{Union{Missing, Float64},1}(missing,50)
    prof_σS=Array{Union{Missing, Float64},1}(missing,50)
    tmp1=Array{Union{Missing, Float64},1}(missing,50)
    tmp2=Array{Union{Missing, Float64},1}(missing,50)

    for m in 1:np
        #println(m)

        prof=ArgoTools.GetOneProfile(argo_data,m)
        prof_std=ProfileStandard(nz)

        ArgoTools.prof_convert!(prof,meta)
        ArgoTools.prof_interp!(prof,prof_std,meta)

        ArgoTools.prof_test_set1!(prof,prof_std,meta)

        ##

        prof_σT.=missing
        prof_σS.=missing
        tmp1.=missing
        tmp2.=missing

        if prof.lat[1]>-89.99

        (f,i,j,w)=InterpolationFactors(Γ,prof.lon[1],prof.lat[1])
        📚=(f=f,i=i,j=j,w=w)

        GriddedFields.interp_h(σT,📚.f,📚.i,📚.j,📚.w,prof_σT)
        GriddedFields.interp_h(σS,📚.f,📚.i,📚.j,📚.w,prof_σS)

        if sum( (!isnan).(prof_σT) )>0
            tmp_σT=ArgoTools.interp_z(-Γ.RC,prof_σT,z_std)
            tmp_σS=ArgoTools.interp_z(-Γ.RC,prof_σS,z_std)

            #3. combine instrumental and representation error
            prof_std.Tweight.=1 ./(tmp_σT.^2 .+ prof_std.T_ERR.^2)
            prof_std.Sweight.=1 ./(tmp_σS.^2 .+ prof_std.S_ERR.^2)
        else
            prof_std.Tweight.=0.0
            prof_std.Sweight.=0.0
        end

        ##

        if sum( (!isnan).(prof_σT) )>0
            fac,rec=ArgoTools.monthly_climatology_factors(prof.date[1])

            GriddedFields.interp_h(T[rec[1]],📚.f,📚.i,📚.j,📚.w,tmp1)
            GriddedFields.interp_h(T[rec[2]],📚.f,📚.i,📚.j,📚.w,tmp2)
            prof_std.Testim.=ArgoTools.interp_z(-Γ.RC,fac[1]*tmp1+fac[2]*tmp2,z_std)

            GriddedFields.interp_h(S[rec[1]],📚.f,📚.i,📚.j,📚.w,tmp1)
            GriddedFields.interp_h(S[rec[2]],📚.f,📚.i,📚.j,📚.w,tmp2)
            prof_std.Sestim.=ArgoTools.interp_z(-Γ.RC,fac[1]*tmp1+fac[2]*tmp2,z_std)
        else
            prof_std.Testim.=missing
            prof_std.Sestim.=missing
        end

        end #if prof.lat[1]>-89.99

        #

        ArgoTools.prof_test_set2!(prof_std,meta)

        #

        profiles[m]=prof
        profiles_std[m]=prof_std
    end

    MITprof.write(meta,profiles,profiles_std)
end

"""
    format_loop(II)

Loop over files and call `format`.

```
gridded_fields=GriddedFields.load()
fil=joinpath(tempdir(),"Argo_MITprof_files","input","Argo_float_files.csv")
files_list=GDAC.files_list(fil)
MITprof.format_loop(gridded_fields,files_list,1:10)
```
"""
function format_loop(gridded_fields,files_list,II)

    pth0=joinpath(tempdir(),"Argo_MITprof_files")
    pth1=joinpath(pth0,"input")
    pth2=joinpath(pth0,"MITprof")

    fil=joinpath(pth1,"ar_greylist.txt")
    isfile(fil) ? greylist=GDAC.grey_list(fil) : greylist=""

    for i in II
        println(i)

        wmo=string(files_list[i,:wmo])
        input_file=joinpath(pth1,files_list[i,:folder],wmo,wmo*"_prof.nc")
        output_file=joinpath(pth2,wmo*"_MITprof.nc")

        meta=ArgoTools.meta(input_file,output_file)
        meta["greylist"]=greylist

        if isfile(input_file)
            ds=Dataset(input_file)
            if haskey(ds,"PSAL")*haskey(ds,"TEMP")
                output_file=MITprof.format(meta,gridded_fields,input_file,output_file)
                println("✔ $(wmo)")
            else
                println(output_file[1:end-3])
                io = open(output_file[1:end-3]*".txt", "w")
                Base.write(io, "Skipped file $(wmo) <- missing PSAL or TEMP\n")
                close(io)

                println("... skipping $(wmo)!")
            end

        else
            io = open(output_file[1:end-3]*".txt", "w")
            Base.write(io, "Skipped file $(wmo) <- no input file\n")
            close(io)

            println("... skipping $(wmo)!")
        end
    end
end

end

module AnalysisMethods

using Dates, MeshArrays, NCDatasets, Glob, DataFrames, CSV

import ArgoData.MITprofStandard

"""
    cost_functions(vv="prof_T",JJ=[])

Loop through files and compute nb profiles, nb non-blank profiles, nb levels mean, cost mean.

```
pth="MITprof/"
nt,np,nz,cost=MITprof.cost_functions(pth,"prof_S")

using JLD2
jldsave(joinpath("csv","prof_S_stats.jld2"); nt,np,nz,cost)
```
"""
function cost_functions(pth,vv="prof_T",JJ=[])
    list_nc=glob("*.nc",pth)
    list_txt=glob("*.txt",pth)

    nt=[]
    np=[]
    nz=[]
    cost=[]

    isempty(JJ) ? II=(1:length(list_nc)) : II=JJ
    time0=time()
    for ii in II
        mod(ii,100)==0 ? println(time()-time0) : nothing
        mod(ii,100)==0 ? println(ii) : nothing
        ds=Dataset(list_nc[ii])
        push!(nt,size(ds[vv],1))
        tmp1=sum((!ismissing).(ds[vv]),dims=2)
        tmp2=sum(tmp1.>0)
        if tmp2>0
            push!(np,tmp2)
            push!(nz,sum(tmp1)/tmp2)
            tmp1=(ds[vv]-ds[vv*"estim"]).^2 .*ds[vv*"weight"]
            if ~isa(tmp1,Matrix{Missing})
                tmp1[findall(ismissing.(tmp1))].=0.0
                push!(cost,sum(tmp1[:])/sum((tmp1[:].>0.0)))
            else
                push!(cost,missing)
            end
        else
            push!(np,0)
            push!(nz,0)
            push!(cost,0)
        end
        close(ds)
    end

    return nt,np,nz,cost
end

"""
    profile_positions(path)

Create table (`DataFrame`) of the positions and dates obtained by looping through files in `path`.
Additional information such as float `ID`, position on the ECCO grid `pos`, number of
valid data points for T and S (`nbT` ,`nbS`).

```
using ArgoData
path="MITprof/"
csv_file="csv/profile_positions.csv"

using MeshArrays
γ=GridSpec("LatLonCap",MeshArrays.GRID_LLC90)
Γ=GridLoad(γ)

df=MITprof.profile_positions(path,Γ)
CSV.write(csv_file, df)
```
"""
function profile_positions(path,Γ)
    list=glob("*.nc",path)
    nfiles=length(list)

    y=fill(0.0,nfiles,2)
    d=fill(DataFrame(),nfiles)

    #println("starting step 1")

    for ff in 1:nfiles
        output_file=list[ff]
        mod(ff,100)==0 ? println("output_file $(ff) is "*output_file) : nothing

        mp=MITprofStandard(output_file)

        da=Dates.julian2datetime.(Dates.datetime2julian(DateTime(0,1,1)) .+mp.date)
        y[ff,1]=year(minimum(da))
        y[ff,2]=year(maximum(da))

        (f,i,j,c)=MeshArrays.knn(Γ.XC,Γ.YC,mp.lon[:],mp.lat[:])
        pos=[[f[ii],i[ii],j[ii]] for ii in 1:length(c)]

        nbT=sum((!ismissing).(mp.T[:,:]),dims=2)
        nbS=sum((!ismissing).(mp.S[:,:]),dims=2)

        d[ff]=DataFrame(ID=parse.(Int,mp.ID),lon=mp.lon,lat=mp.lat,
            date=da,pos=c[:],nbT=nbT[:],nbS=nbS[:])
    end

    #println("starting step 2")

    nd=length(findall((!isempty).(d)))
    df=d[1]
    [append!(df,d[ff]) for ff in 2:nd]

    df
end

"""
    profile_variables(name::String)

Create Array of all values for one variable, obtained by looping through files in `path`.

```
@everywhere using ArgoData, CSV, DataFrames
@everywhere list_v=("prof_T","prof_Testim","prof_Tweight","prof_S","prof_Sestim","prof_Sweight")
@distributed for v in list_v
    output_file="csv/"*v*".csv"
    tmp=MITprof.profile_variables(v)
    CSV.write(output_file,DataFrame(tmp,:auto))
end
```
"""
function profile_variables(name::String)
    path="MITprof/"
    csv_file="csv/profile_positions.csv"
    df=CSV.read(csv_file,DataFrame)

    list=glob("*.nc",path)
    nfiles= length(list)
    x=Array{Union{Float64,Missing},2}(undef,size(df,1),55)
    n0=[0]
    for ff in 1:nfiles
        tmp=Dataset(list[ff],"r") do ds
            ds[name][:,:]
        end # ds is closed
        s=size(tmp)
        x[n0[1]+1:n0[1]+s[1],:].=tmp
        n0[1]+=s[1]
    end

    x
end

"""
    profile_levels()

Create Array of all values for one level, obtained by looping through files in `csv/`.
"""
function profile_levels(k=0)
    k==0 ? kk=collect(1:55) : kk=[k]
    list_v=("prof_T","prof_Testim","prof_Tweight","prof_S","prof_Sestim","prof_Sweight")
    list_n=("T","Te","Tw","S","Se","Sw")

    csv_file="csv/profile_positions.csv"
    df0=CSV.read(csv_file,DataFrame)

    path="csv/"

    nfiles= length(list_v)
    for ff in 1:nfiles
        println(list_v[ff])
        df=CSV.read(path*list_v[ff]*".csv",DataFrame)
        name=list_n[ff]
        for k in kk
            fil=path*"k$(k).csv"
            if ff==1
                df1=DataFrame(date=df0.date)
            else
                df1=CSV.read(fil,DataFrame)
            end
            println("x$(k)")
            df1[:,name]=df[:,Symbol("x$(k)")]
            CSV.write(fil,df1)
        end
    end

end

"""
    profile_add_level!(df,k)

```
df=CSV.read("csv/profile_positions.csv",DataFrame)
MITprof.profile_add_level!(df,5)
```
"""
function profile_add_level!(df,k)
    df1=CSV.read("csv/k$(k).csv",DataFrame)
    list_n=("T","Te","Tw","S","Se","Sw")
    [df[:,Symbol(i)]=df1[:,Symbol(i)] for i in list_n]
end

"""
    profile_subset(df,lons,lats,dates)

```
df=CSV.read("csv/profile_positions.csv",DataFrame)
d0=DateTime("2012-06-11T18:50:04")
d1=DateTime("2012-07-11T18:50:04")
tmp=MITprof.profile_subset(df,(0,10),(-5,5),(d0,d1))
```
"""
profile_subset(df,lons,lats,dates) =
    df[ (df.lon .> lons[1]) .& (df.lon .<= lons[2]) .&
    (df.lat .> lats[1]) .& (df.lat .<= lats[2]) .&
    (df.date .> dates[1]) .& (df.date .<= dates[2]) ,:]

#profile_subset(df,lons,lats,dates) =
#    subset(df, [:lon, :lat, :date] => (lon, lat, date) ->
#    lon .> lons[1] .&& lon .<= lons[2] .&&
#    lat .> lats[1] .&& lat .<= lats[2] .&&
#    date .> dates[1] .&& date .<= dates[2])

"""
    profile_trim(df)
"""
profile_trim(df) = df[
    (!ismissing).(df.T) .& (!ismissing).(df.Te) .& (df.Tw.>0) .&
    (!ismissing).(df.S) .& (!ismissing).(df.Se) .& (df.Sw.>0) .&
    (df.date .> DateTime(1000,1,1)) .& (df.date .< DateTime(2022,4,1))
    ,:]

end

ext: jl


text:
# Tests for the conditional mutual information function

# Test conditional mutual information formula
@test get_conditional_mutual_information(arr1, arr2, arr3) ≈ get_entropy(arr1, arr3) + get_entropy(arr2, arr3) - get_entropy(arr1, arr2, arr3) - get_entropy(arr3)
println("Conditional mutual information formula passed.")


text:
# utilities

function unsafe_message(ptr, args...)
    str = unsafe_string(ptr, args...)
    API.LLVMDisposeMessage(ptr)
    str
end

## defining types in the LLVM type hierarchy

# llvm.org/docs/doxygen/html/group__LLVMCSupportTypes.html

# macro that adds an inner constructor to a type definition,
# calling `refcheck` on the ref field argument
macro checked(typedef)
    # decode structure definition
    if Meta.isexpr(typedef, :macrocall)
        # handle `@compat` prefixing 0.6-style type declarations
        typedef = macroexpand(typedef)
    end
    if Meta.isexpr(typedef, :struct)
        structure = typedef.args[2]
        body = typedef.args[3]
    else
        error("argument is not a structure definition")
    end
    if isa(structure, Symbol)
        # basic type definition
        typename = structure
    elseif Meta.isexpr(structure, :<:)
        # typename <: parentname
        all(e->isa(e,Symbol), structure.args) ||
            error("typedef should consist of plain types, ie. not parametric ones")
        typename = structure.args[1]
    else
        error("malformed type definition: cannot decode type name")
    end

    # decode fields
    field_names = Symbol[]
    field_defs = Union{Symbol,Expr}[]
    for arg in body.args
        if isa(arg, LineNumberNode)
            continue
        elseif isa(arg, Symbol)
            push!(field_names, arg)
            push!(field_defs, arg)
        elseif Meta.isexpr(arg, :(::))
            push!(field_names, arg.args[1])
            push!(field_defs, arg)
        end
    end
    :ref in field_names || error("structure definition should contain 'ref' field")

    # insert checked constructor
    push!(body.args, :(
        $typename($(field_defs...)) = (refcheck($typename, ref); new($(field_names...)))
    ))

    return esc(typedef)
end

# the most basic check is asserting that we don't use a null pointer
@inline function refcheck(::Type, ref::Ptr)
    ref==C_NULL && throw(UndefRefError())
end

## runtime ccall wrapper

"""
    @runtime_ccall((function_name, library), returntype, (argtype1, ...), argvalue1, ...)

Extension of `ccall` that performs the lookup of `function_name` in `library` at run time.
This is useful in the case that `library` might not be available, in which case a function
that performs a `ccall` to that library would fail to compile.

After a slower first call to load the library and look up the function, no additional
overhead is expected compared to regular `ccall`.
"""
macro runtime_ccall(target, args...)
    if VERSION >= v"1.6.0-DEV.819"
        quote
            ccall($(esc(target)), $(map(esc, args)...))
        end
    else
        # decode ccall function/library target
        Meta.isexpr(target, :tuple) || error("Expected (function_name, library) tuple")
        function_name, library = target.args

        # global const ref to hold the function pointer
        @gensym fptr_cache
        @eval __module__ begin
            # uses atomics (release store, acquire load) for thread safety.
            # see https://github.com/JuliaGPU/CUDAapi.jl/issues/106 for details
            const $fptr_cache = Threads.Atomic{UInt}(0)
        end

        quote
            # use a closure to hold the lookup and avoid code bloat in the caller
            @noinline function cache_fptr!()
                library = Libdl.dlopen($(esc(library)))
                $(esc(fptr_cache))[] = Libdl.dlsym(library, $(esc(function_name)))

                $(esc(fptr_cache))[]
            end

            fptr = $(esc(fptr_cache))[]
            if fptr == 0        # folded into the null check performed by ccall
                fptr = cache_fptr!()
            end

            ccall(reinterpret(Ptr{Cvoid}, fptr), $(map(esc, args)...))
        end
    end
end


text:
using Turing
using Turkie
using OnlineStats
using GLMakie
@model function demo(x)
    v ~ InverseGamma(3, 2)
    s ~ InverseGamma(2, v)
    m ~ Normal(0, √s)
    for i in eachindex(x)
        x[i] ~ Normal(m, √s)
    end
end

xs = randn(100) .+ 1;
m = demo(xs);

cb = TurkieCallback(m);
# chain = sample(m,  HMC(0.5, 10), 40; callback = cb);
chain = sample(m,  NUTS(0.65), 500; callback = cb);

# using Makie
# record(cb.scene, joinpath(@__DIR__, "video.gif")) do io
#     addIO!(cb, io)
#     sample(m,  NUTS(0.65), 300; callback = cb)
# end


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct ApplicationGatewayAutoscaleConfiguration <: SwaggerModel
    minCapacity::Any # spec type: Union{ Nothing, Int32 } # spec name: minCapacity
    maxCapacity::Any # spec type: Union{ Nothing, Int32 } # spec name: maxCapacity

    function ApplicationGatewayAutoscaleConfiguration(;minCapacity=nothing, maxCapacity=nothing)
        o = new()
        validate_property(ApplicationGatewayAutoscaleConfiguration, Symbol("minCapacity"), minCapacity)
        setfield!(o, Symbol("minCapacity"), minCapacity)
        validate_property(ApplicationGatewayAutoscaleConfiguration, Symbol("maxCapacity"), maxCapacity)
        setfield!(o, Symbol("maxCapacity"), maxCapacity)
        o
    end
end # type ApplicationGatewayAutoscaleConfiguration

const _property_map_ApplicationGatewayAutoscaleConfiguration = Dict{Symbol,Symbol}(Symbol("minCapacity")=>Symbol("minCapacity"), Symbol("maxCapacity")=>Symbol("maxCapacity"))
const _property_types_ApplicationGatewayAutoscaleConfiguration = Dict{Symbol,String}(Symbol("minCapacity")=>"Int32", Symbol("maxCapacity")=>"Int32")
Base.propertynames(::Type{ ApplicationGatewayAutoscaleConfiguration }) = collect(keys(_property_map_ApplicationGatewayAutoscaleConfiguration))
Swagger.property_type(::Type{ ApplicationGatewayAutoscaleConfiguration }, name::Symbol) = Union{Nothing,eval(Base.Meta.parse(_property_types_ApplicationGatewayAutoscaleConfiguration[name]))}
Swagger.field_name(::Type{ ApplicationGatewayAutoscaleConfiguration }, property_name::Symbol) =  _property_map_ApplicationGatewayAutoscaleConfiguration[property_name]

function check_required(o::ApplicationGatewayAutoscaleConfiguration)
    (getproperty(o, Symbol("minCapacity")) === nothing) && (return false)
    true
end

function validate_property(::Type{ ApplicationGatewayAutoscaleConfiguration }, name::Symbol, val)
    if name === Symbol("minCapacity")
        Swagger.validate_param(name, "ApplicationGatewayAutoscaleConfiguration", :minimum, val, 0, false)
    end
    if name === Symbol("maxCapacity")
        Swagger.validate_param(name, "ApplicationGatewayAutoscaleConfiguration", :minimum, val, 2, false)
    end
end


text:
using JuliaAcademyData
jad = JuliaAcademyData


text:
@generated function eig{T<:Real, SM <: StaticMatrix}(A::Base.LinAlg.RealHermSymComplexHerm{T,SM}; permute::Bool=true, scale::Bool=true)
    if size(SM) == (1,1)
        return quote
            $(Expr(:meta, :inline))
            @inbounds return (SVector{1,T}((A[1],)), eye(SMatrix{1,1,T}))
        end
    elseif size(SM) == (2,2)
        return quote
            $(Expr(:meta, :inline))
            a = A.data

            if A.uplo == 'U'
                @inbounds t_half = real(a[1] + a[4])/2
                @inbounds d = real(a[1]*a[4] - a[3]'*a[3]) # Should be real

                tmp2 = t_half*t_half - d
                tmp2 < 0 ? tmp = zero(tmp2) : tmp = sqrt(tmp2) # Numerically stable for identity matrices, etc.
                vals = SVector(t_half - tmp, t_half + tmp)

                @inbounds if a[3] == 0
                    vecs = eye(SMatrix{2,2,T})
                else
                    @inbounds v11 = vals[1]-a[4]
                    @inbounds n1 = sqrt(v11'*v11 + a[3]'*a[3])
                    v11 = v11 / n1
                    @inbounds v12 = a[3]' / n1

                    @inbounds v21 = vals[2]-a[4]
                    @inbounds n2 = sqrt(v21'*v21 + a[3]'*a[3])
                    v21 = v21 / n2
                    @inbounds v22 = a[3]' / n2

                    vecs = @SMatrix [ v11  v21 ;
                                      v12  v22 ]
                end
                return (vals,vecs)
            else
                @inbounds t_half = real(a[1] + a[4])/2
                @inbounds d = real(a[1]*a[4] - a[2]'*a[2]) # Should be real

                tmp2 = t_half*t_half - d
                tmp2 < 0 ? tmp = zero(tmp2) : tmp = sqrt(tmp2) # Numerically stable for identity matrices, etc.
                vals = SVector(t_half - tmp, t_half + tmp)

                @inbounds if a[2] == 0
                    vecs = eye(SMatrix{2,2,T})
                else
                    @inbounds v11 = vals[1]-a[4]
                    @inbounds n1 = sqrt(v11'*v11 + a[2]'*a[2])
                    v11 = v11 / n1
                    @inbounds v12 = a[2] / n1

                    @inbounds v21 = vals[2]-a[4]
                    @inbounds n2 = sqrt(v21'*v21 + a[2]'*a[2])
                    v21 = v21 / n2
                    @inbounds v22 = a[2] / n2

                    vecs = @SMatrix [ v11  v21 ;
                                      v12  v22 ]
                end
                return (vals,vecs)
            end
        end
    elseif size(SM) == (3,3)
        if !(T <: Real) # TODO adapt the below to be complex-safe
            return quote
                $(Expr(:meta, :inline))
                eigen = eigfact(A)
                return (eigen.values, eigen.vectors)
            end
        else
            S = typeof((one(T)*zero(T) + zero(T))/one(T))

            return quote
                $(Expr(:meta, :inline))

                uplo = A.uplo
                data = A.data
                if uplo == 'U'
                    @inbounds A = SMatrix{3,3}(data[1], data[4], data[7], data[4], data[5], data[8], data[7], data[8], data[9])
                else
                    @inbounds A = SMatrix{3,3}(data[1], data[2], data[3], data[2], data[5], data[6], data[3], data[6], data[9])
                end

                # Adapted from Wikipedia
                @inbounds p1 = A[4]*A[4] + A[7]*A[7] + A[8]*A[8]
                if (p1 == 0)
                    # A is diagonal.
                    @inbounds eig1 = A[1]
                    @inbounds eig2 = A[5]
                    @inbounds eig3 = A[9]

                    return (SVector{3,$S}(eig1, eig2, eig3), eye(SMatrix{3,3,$S}))
                else
                    q = trace(A)/3
                    @inbounds p2 = (A[1] - q)^2 + (A[5] - q)^2 + (A[9] - q)^2 + 2 * p1
                    p = sqrt(p2 / 6)
                    B = (1 / p) * (A - UniformScaling(q)) # q*I
                    r = det(B) / 2

                    # In exact arithmetic for a symmetric matrix  -1 <= r <= 1
                    # but computation error can leave it slightly outside this range.
                    if (r <= -1)
                        phi = pi / 3
                    elseif (r >= 1)
                        phi = 0
                    else
                        phi = acos(r) / 3
                    end

                    # the eigenvalues satisfy eig1 <= eig2 <= eig3
                    eig3 = q + 2 * p * cos(phi)
                    eig1 = q + 2 * p * cos(phi + (2*pi/3))
                    eig2 = 3 * q - eig1 - eig3     # since trace(A) = eig1 + eig2 + eig3

                    # Now get the eigenvectors
                    # TODO branch for when eig1 == eig2?
                    @inbounds tmp1 = SVector(A[1] - eig1, A[2], A[3])
                    @inbounds tmp2 = SVector(A[4], A[5] - eig1, A[6])
                    v1 = cross(tmp1, tmp2)
                    v1 = v1 / vecnorm(v1)

                    @inbounds tmp1 = SVector(A[1] - eig2, A[2], A[3])
                    @inbounds tmp2 = SVector(A[4], A[5] - eig2, A[6])
                    v2 = cross(tmp1, tmp2)
                    v2 = v2 / vecnorm(v2)

                    v3 = cross(v1, v2) # should be normalized already

                    @inbounds return (SVector((eig1, eig2, eig3)), SMatrix{3,3}((v1[1], v1[2], v1[3], v2[1], v2[2], v2[3], v3[1], v3[2], v3[3])))
                end
            end
        end
    else
        return quote
            $(Expr(:meta, :inline))
            eigen = eigfact(A)
            return (eigen.values, eigen.vectors)
        end
    end
end

# TODO: the non-symmetric case: type stable version (real -> real) since it is more useful to us!

#=
@generated function eig{T<:Real, SM <: StaticMatrix}(A::Base.LinAlg.RealHermSymComplexHerm{T,SM}; permute::Bool=true, scale::Bool=true)
    if size(SM) == (1,1)
        return quote
            $(Expr(:meta, :inline))
            @inbounds return (SVector{1,T}((A[1],)), eye(SMatrix{1,1,T}))
        end
    elseif size(SM) == (2,2)
        return quote
            $(Expr(:meta, :inline))
            a = A.data

            if m2.uplo == 'U'
                @inbounds t_half = real(A[1] + A[4])/2
                @inbounds d = real(A[1]*A[4] - A[3]'*A[3]) # Should be real

                tmp2 = t_half*t_half - d
                tmp2 < 0 ? tmp = zero(tmp2) : tmp = sqrt(tmp2) # Numerically stable for identity matrices, etc.
                vals = SVector(t_half - tmp, t_half + tmp)

                @inbounds if A[3] == 0
                    @inbounds if A[3] == 0
                        vecs = eye(SMatrix{2,2,T})
                    else
                        @inbounds vecs = @SMatrix [ A[3]          A[3]         ;
                                                    vals[1]-A[1]  vals[2]-A[1] ]
                    end
                else
                    @inbounds v11 = vals[1]-A[4]
                    @inbounds n1 = sqrt(v11'*v11 + A[2]'*A[2])
                    v11 = v11 / n1
                    @inbounds v12 = A[2] / n1

                    @inbounds v21 = vals[2]-A[4]
                    @inbounds n2 = sqrt(v21'*v21 + A[2]'*A[2])
                    v21 = v21 / n2
                    @inbounds v22 = A[2] / n2

                    vecs = @SMatrix [ v11  v21 ;
                                      v12  v22 ]
                end
                return (vals,vecs)
            else

            end
        end
    elseif size(SM) == (3,3)
        error("not implemented")
    else
        return quote
            $(Expr(:meta, :inline))
            eigen = eigfact(A)
            return (eigen.values, eigen.vectors)
        end
    end
end
=#


text:
using Printf
using Statistics

using Oceananigans
using Oceananigans.Units
using Oceananigans.OutputReaders: FieldTimeSeries
using Oceananigans.Grids: xnode, ynode, znode
using Random

import Oceananigans.Grids: ynode

hydrostatic = false
stretched_grid = false
const Lx = 1000kilometers # zonal domain length [m]
const Ly = 2000kilometers # meridional domain length [m]

# file output
prefix = "fluxernathy_nh_ns"

# time
Δt = 300 * 2
stop_time = 300years
# number of grid points
Nx = 16 * 8
Ny = Nx * 2
Nz = 16 * 2

# stretched grid
k_center = collect(1:Nz)
Δz_center = @. 10 * 1.104^(Nz - k_center)
const Lz = sum(Δz_center)
z_faces = vcat([-Lz], -Lz .+ cumsum(Δz_center))
z_faces[Nz+1] = 0

arch = GPU()
FT = Float64
topology = (Periodic, Bounded, Bounded)
if stretched_grid
    grid = VerticallyStretchedRectilinearGrid(architecture = arch,
                                            topology = topology,
                                            size = (Nx, Ny, Nz),
                                            halo = (3, 3, 3),
                                            x = (0, Lx),
                                            y = (0, Ly),
                                            z_faces = z_faces)
else
    grid = RegularRectilinearGrid(topology=topology,
                            size=(Nx, Ny, Nz),
                            x=(0, Lx), y=(0, Ly), z=(-Lz, 0))
end

@info "Built a grid: $grid."

#####
##### Boundary conditions
#####

α  = 2e-4     # [K⁻¹] thermal expansion coefficient
g  = 9.8061   # [m/s²] gravitational constant
cᵖ = 3994.0   # [J/K]  heat capacity
ρ  = 999.8    # [kg/m³] reference density

parameters = (
    Ly = Ly,
    Lz = Lz,
    Qᵇ = 10/(ρ * cᵖ) * α * g,            # buoyancy flux magnitude [m² s⁻³]
    y_shutoff = 5/6 * Ly,                # shutoff location for buoyancy flux [m]
    τ = 0.2/ρ,                           # surface kinematic wind stress [m² s⁻²]
    μ = 1.1e-3,                          # linear drag [m/s]
    ΔB = 8 * α * g,                      # surface vertical buoyancy gradient [s⁻²]
    H = Lz,                              # domain depth [m]
    h = 1000.0,                          # exponential decay scale of stable stratification [m]
    y_sponge = 19/20 * Ly,               # southern boundary of sponge layer [m]
    λt = 7.0days                         # relaxation time scale [s]
)

@inline function buoyancy_flux(i, j, grid, clock, model_fields, p)
    y = ynode(Center(), j, grid)
    return ifelse(y < p.y_shutoff, p.Qᵇ * cos(3π * y / p.Ly), 0.0)
end

buoyancy_flux_bc = FluxBoundaryCondition(buoyancy_flux, discrete_form=true, parameters=parameters)

@inline function u_stress(i, j, grid, clock, model_fields, p)
    y = ynode(Center(), j, grid)
    return - p.τ * sin(π * y / p.Ly)
end

u_stress_bc = FluxBoundaryCondition(u_stress, discrete_form=true, parameters=parameters)

@inline u_drag(i, j, grid, clock, model_fields, p) = @inbounds - p.μ * model_fields.u[i, j, 1]
@inline v_drag(i, j, grid, clock, model_fields, p) = @inbounds - p.μ * model_fields.v[i, j, 1]

u_drag_bc = FluxBoundaryCondition(u_drag, discrete_form=true, parameters=parameters)
v_drag_bc = FluxBoundaryCondition(v_drag, discrete_form=true, parameters=parameters)

b_bcs = FieldBoundaryConditions(top = buoyancy_flux_bc)

u_bcs = FieldBoundaryConditions(top = u_stress_bc, bottom = u_drag_bc)
v_bcs = FieldBoundaryConditions(bottom = v_drag_bc)

#####
##### Coriolis
#####

const f = -1e-4
const β = 1 * 10^(-11)
coriolis = BetaPlane(FT, f₀ = f, β = β)

#####
##### Forcing and initial condition
#####

@inline initial_buoyancy(z, p) = p.ΔB * (exp(z / p.h) - exp(-p.Lz / p.h)) / (1 - exp(-p.Lz / p.h))
@inline mask(y, p) = max(0.0, y - p.y_sponge) / (Ly - p.y_sponge)

@inline function buoyancy_relaxation(i, j, k, grid, clock, model_fields, p)
    timescale = p.λt
    y = ynode(Center(), j, grid)
    z = znode(Center(), k, grid)
    target_b = initial_buoyancy(z, p)
    b = @inbounds model_fields.b[i, j, k]
    return - 1 / timescale  * mask(y, p) * (b - target_b)
end

Fb = Forcing(buoyancy_relaxation, discrete_form = true, parameters = parameters)

# closure

κh = 0.5e-5 # [m²/s] horizontal diffusivity
νh = 12.0   # [m²/s] horizontal viscocity
κz = 0.5e-5 # [m²/s] vertical diffusivity
νz = 3e-4   # [m²/s] vertical viscocity

closure = AnisotropicDiffusivity(νh=νh, νz=νz, κh=κh, κz=κz)

convective_adjustment = ConvectiveAdjustmentVerticalDiffusivity(convective_κz = 1.0,
                                                               convective_νz = 0.0)

#####
##### Model building
#####

@info "Building a model..."

if hydrostatic
    model = HydrostaticFreeSurfaceModel(architecture = arch,
                                        grid = grid,
                                        free_surface = ImplicitFreeSurface(),
                                        momentum_advection = WENO5(),
                                        tracer_advection = WENO5(),
                                        buoyancy = BuoyancyTracer(),
                                        coriolis = coriolis,
                                        closure = (closure, convective_adjustment),
                                        tracers = :b,
                                        boundary_conditions = (b=b_bcs, u=u_bcs, v=v_bcs),
                                        forcing = (b=Fb,),
                                        )
else
    model = NonhydrostaticModel(architecture = arch,
                                        grid = grid,
                                        advection = WENO5(),
                                        buoyancy = BuoyancyTracer(),
                                        coriolis = coriolis,
                                        closure = (closure),
                                        tracers = :b,
                                        boundary_conditions = (b=b_bcs, u=u_bcs, v=v_bcs),
                                        forcing = (b=Fb,),
                                        )
end

@info "Built $model."

#####
##### Initial conditions
#####

# resting initial condition
Random.seed!(1)
ε(σ) = σ * randn()
bᵢ(x, y, z) = parameters.ΔB * ( exp(z/parameters.h) - exp(-Lz/parameters.h) ) / (1 - exp(-Lz/parameters.h)) + ε(1e-8)

set!(model, b=bᵢ)

#####
##### Simulation building

wizard = Δt # TimeStepWizard(cfl=0.1, Δt=Δt, max_change=1.0, max_Δt=Δt)

wall_clock = [time_ns()]

function print_progress(sim)
    @printf("[%05.2f%%] i: %d, t: %s, wall time: %s, max(u): (%6.3e, %6.3e, %6.3e) m/s, next Δt: %s\n",
            100 * (sim.model.clock.time / sim.stop_time),
            sim.model.clock.iteration,
            prettytime(sim.model.clock.time),
            prettytime(1e-9 * (time_ns() - wall_clock[1])),
            maximum(abs, sim.model.velocities.u),
            maximum(abs, sim.model.velocities.v),
            maximum(abs, sim.model.velocities.w),
 #           prettytime(sim.Δt.Δt))
            prettytime(sim.Δt))

    wall_clock[1] = time_ns()

    return nothing
end

simulation = Simulation(model, Δt=wizard, stop_time=stop_time, progress=print_progress, iteration_interval=1000)

#####
##### Diagnostics
#####

u, v, w = model.velocities
b = model.tracers.b

# ζ = ComputedField(∂x(v) - ∂y(u))

averaged_outputs = Dict(
    :u => AveragedField(u, dims=(1,)),
    :v => AveragedField(v, dims=(1,)),
    :w => AveragedField(w, dims=(1,)),
    :b => AveragedField(b, dims=(1,)),

    :uu => AveragedField(u * u, dims= 1),
    :vv => AveragedField(v * v, dims= 1),
    :ww => AveragedField(w * w, dims= 1),
    :uv => AveragedField(u * v, dims= 1),
    :vw => AveragedField(w * v, dims= 1),
    :uw => AveragedField(w * u, dims= 1),
    :ub => AveragedField(u * b, dims= 1),
    :vb => AveragedField(v * b, dims= 1),
    :wb => AveragedField(w * b, dims= 1),
)

# #####
# ##### Build checkpointer and output writer
# #####

simulation.output_writers[:checkpointer] = Checkpointer(model,
                                                    schedule = TimeInterval(5 * 365days),
                                                    prefix = prefix,
                                                    force = true)

simulation.output_writers[:averages] = JLD2OutputWriter(model, averaged_outputs,
                                                    schedule = AveragedTimeInterval(10*365days, window=10*365days, stride=10),
                                                    prefix = prefix * "_averages",
                                                    verbose = true,
                                                    force = true)

 @info "Running the simulation..."

#  try
run!(simulation, pickup=false)

max_stars_repo_name: sandreza/Mesoscale

max_issues_repo_name: sandreza/Mesoscale

max_forks_repo_name: sandreza/Mesoscale