# -*- mode: shell-script -*-

replace_by_rematch_pattern() {
    local input="$1" output="" char next_char i
    # Loop through each character in the string
    for (( i=0; i<${#input}; i++ )); do
        char="${input:$i:1}"

        # If a dollar sign is found
        if [[ "$char" == '$' ]]; then
            next_char="${input:$((i+1)):1}"
            # Check if next character is a digit
            if [[ "$next_char" =~ [0-9] ]]; then
                # Replace $N with ${rematch[N]}
                output+='${rematch['"$next_char"']}'
                ((i++)) # Skip next character as it's already processed
                continue
            fi
        fi

        output+="$char"
    done

    echo "$output"
}
export -f replace_by_rematch_pattern



get_domains() {
    local cfg="$1" service_cfg="$2" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
          domains new_domains maps m regex rematch type value key domain
    if [ -e "$cache_file" ]; then
        cat "$cache_file"
        return 0
    fi
    domains=()
    for key in domain server-aliases; do
        type=$(e "$cfg" | shyaml -q get-type "$key" 2>/dev/null) || true
        value=$(e "$cfg" | shyaml -q get-value -y "$key" 2>/dev/null) || true
        while true; do
            case "$type" in
                sequence)
                    while read-0 domain; do
                        if [[ "$domain" != "None" ]]; then
                            if [ "${#domains[@]}" == 0 ] && [ "$key" == "server-aliases" ]; then
                                err "You can't specify server aliases if you don't have a domain."
                                return 1
                            fi

                            domains+=("$domain")
                        fi
                    done < <(e "$value" | shyaml get-values-0)
                    ;;
                str)
                    if ! domain=$(e "$value" | shyaml get-value 2>/dev/null); then                        
                        err "Failed to get domain value from config."
                        return 1
                    fi
                    [[ "$domain" == "" ]] && break
                    if [ "${#domains[@]}" == 0 ] && [ "$key" == "server-aliases" ]; then
                        err "You can't specify server aliases if you don't have a domain."
                        return 1
                    fi

                    domains+=("$domain")
                    ;;
                NoneType|"")
                    :
                    ;;
                \!*)
                    value=$(e "$value" | cfg-get-value "$key" 2>/dev/null) || true
                    type=$(e "$value" | shyaml -q get-type) || true
                    continue
                    ;;
            esac
            break
        done
        ## check and expand domain
        new_domains=()
        for domain in "${domains[@]}"; do
            if ! [[ "$domain" =~ ^[a-z0-9\{\}*\ \,.-]+$ ]]; then
                err "Invalid domain value '$domain' expression in ${WHITE}$key$NORMAL option."
                return 1
            fi
            new_domains+=($(eval echo "${domain//\*/\\*}"))
        done
        domains=("${new_domains[@]}")
    done
    ## Fill with service-domain-map
    if [ "${#domains[@]}" == 0 ] &&
           service_domain_map=$(e "$service_cfg" | cfg-get-value 'options.service-domain-map' 2>/dev/null) &&
           [ -n "$service_domain_map" ]; then
        while read-0 regex map; do
            if [[ "$BASE_SERVICE_NAME" =~ ^$regex$ ]]; then
                rematch=("${BASH_REMATCH[@]}")
                maps=()
                type=$(e "$map" | shyaml -q get-type 2>/dev/null) || true
                value=$(e "$map" | shyaml -q get-value -y 2>/dev/null) || true
                case "$type" in
                    sequence)
                        while read-0 m; do
                            if [[ "$m" != "None" ]] && [ -n "$m" ]; then
                                maps+=("$m")
                            fi
                        done < <(e "$value" | shyaml get-values-0)
                        ;;
                    str)
                        if ! m=$(e "$value" | shyaml get-value 2>/dev/null); then
                            err "Failed to get mapping value from config."
                            return 1
                        fi
                        [ -z "$m" ] && continue
                        maps+=("$m")
                        ;;
                    NoneType|"")
                        :
                        ;;
                esac
                for map in "${maps[@]}"; do 
                    if ! [[ "$map" =~ ^([a-z0-9*\{\}\ \,.-]|\$[0-9])+$ ]]; then
                        err "Invalid mapping value '$map' in ${WHITE}service-domain-map$NORMAL option."
                        return 1
                    fi

                    map="${map//\*/\\*}"  ## protect star from eval
                    map=$(replace_by_rematch_pattern "$map")

                    domains+=($(set -- "${BASH_REMATCH[@]}"; eval echo "${map//\*/\\*}"))
                done
                break
            fi
        done < <(e "$service_domain_map" | yq e -0 'to_entries | .[] | [.key, .value] |.[]')
    fi
    if [ "${#domains[@]}" == 0 ] &&
           [[ "$BASE_SERVICE_NAME" =~ ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$ ]]; then
        domains+=("$BASE_SERVICE_NAME")
    fi
    new_domains=()
    ## remove duplicates
    for domain in "${domains[@]}"; do
        if ! [[ " ${new_domains[*]} " == *" $domain "* ]]; then
            new_domains+=("$domain")
        fi
    done
    domains=("${new_domains[@]}")
    if [ "${#domains[@]}" == 0 ]; then
        err "No domain name set for your service ${YELLOW}$BASE_SERVICE_NAME${NORMAL}."
        echo "  You can specify a ${WHITE}domain$NORMAL option in the" \
             "relation with ${YELLOW}$TARGET_SERVICE_NAME${NORMAL}." >&2
        echo "  Or you can specify a service to domain mapping in" \
             "${WHITE}service-domain-map${NORMAL} option of ${YELLOW}$TARGET_SERVICE_NAME${NORMAL}." >&2
        return 1
    fi
    ## check that first domain should not have a wildcard
    if [[ "${domains[0]}" == *"*"* ]]; then
        err "First domain name '${domains[0]}' can't contain a wildcard."
        return 1
    fi
    echo "${domains[@]}" | tee "$cache_file"
}


##
## Master entrypoints
##

apache_proxy_dir() {
    local cfg="$1" service_cfg="$2"
    apache_vhost_create web_proxy "$cfg" "$service_cfg" || return 1
}
export -f apache_proxy_dir


apache_publish_dir() {
    local cfg="$1" service_cfg="$2"
    apache_vhost_create publish_dir "$cfg" "$service_cfg" || return 1
    apache_code_dir "$cfg" "$service_cfg" || return 1
    apache_data_dirs "$cfg" "$service_cfg"
}
export -f apache_publish_dir


apache_ssh_tunnel() {
    local cfg="$1" domain ssh_tunnel
    domain=$(e "$cfg" | cfg-get-value domain) || {
        err "${WHITE}domain${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
        return 1
    }
    protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
    if ! is_protocol_enabled https "$protocols"; then
        err "${WHITE}ssl${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
        return 1
    fi
    apache_vhost_create ssh_tunnel "$cfg" "" ",https," "000-$domain" || return 1
}
export -f apache_publish_dir


##
## Simple functions
##


apache_vhost_create() {
    local type="$1" cfg="$2" service_cfg="$3" protocols="$4" dest="$5" custom_rules vhost_statement creds \
          redirect domain ssl_plugin_fun ssl_cfg_value ssl_cfg_options

    export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
    if [ "$cfg" == "None" ]; then
        cfg=
    fi
    if [ -z "$protocols" ]; then
        protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
    fi

    domains=($(get_domains "$cfg" "$service_cfg")) && {
        [ "$RELATION_DATA_FILE" ] && relation-set domain "${domains[0]}"
    }
    echo "Domains: ${domains[*]}" >&2
    if is_protocol_enabled https "$protocols"; then
        [ "$RELATION_DATA_FILE" ] && {
            relation-set url "https://${domains[0]}"
        }

        if [ "${#domains[@]}" == 0 ]; then
            err "You must specify a domain for ssl to work."
            return 1
        fi
        read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
        "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "${domains[*]}" || return 1
        redirect=$(e "$cfg" | cfg-get-value 'redirect-to-ssl' 2>/dev/null) || true
        if is_protocol_enabled http "$protocols"; then
            redirect=${redirect:-true}
        else
            redirect=false
        fi
        if [ "$redirect" == "true" ]; then
            custom_rules=$(_get_custom_rules "$cfg") || return 1
            if [[ "$custom_rules" != *"## Auto-redirection from http to https"* ]]; then
                redirect_rule="- |
  ## Auto-redirection from http to https
  RewriteEngine On
  RewriteCond %{HTTPS} off
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L,QSA]
"
                [ "$RELATION_DATA_FILE" ] && \
                    relation-set apache-custom-rules "$redirect_rule
$(if [ "$custom_rules" ]; then
    echo "- |"$'\n'"$(echo "$custom_rules" | prefix "  ")"
  fi)"
                cfg=$(merge_yaml_str "$cfg" "$(yaml_key_val_str "apache-custom-rules" "$redirect_rule
$(if [ "$custom_rules" ]; then
    echo "- |"$'\n'"$(echo "$custom_rules" | prefix "  ")"
  fi)")") || return 1
            fi
        fi
    else
        [ "$RELATION_DATA_FILE" ] && {
            relation-set url "http://${domains[0]}"
        }
    fi
    vhost_statement=$(apache_vhost_statement "$type" "$protocols" "$cfg" "${domains[*]}") || {
        err "Failed to get vhost statement for type $type on ${protocols:1:-1}"
        return 1
    }

    dest=${dest:-${domains[0]}}
    if [ -z "$dest" ]; then
        err "Please set either a domain or set a destination file."
        return 1
    fi
    echo "$vhost_statement" | file_put "$APACHE_CONFIG_LOCATION/$dest.conf" || return 1

    creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
    if [ "$creds" ]; then
        apache_passwd_file "$cfg" "$dest"|| return 1
    fi

    if is_protocol_enabled https "$protocols"; then
        "$ssl_plugin_fun"_prepare "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "${domains[*]}" || return 1
    fi
}


is_protocol_enabled() {
    local protocol="$1" protocols="$2"
    [[ "$protocols" == *",$protocol,"* ]]
}
export -f is_protocol_enabled


_get_ssl_option_value() {
    local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
          target_relation rn ts rc td

    if [ -e "$cache_file" ]; then
        cat "$cache_file"
        return 0
    fi

    if ssl_cfg=$(e "$cfg" | cfg-get-value ssl 2>/dev/null); then
        if [[ "$ssl_cfg" =~ ^False|None|false|null$ ]]; then
            ssl_cfg=""
        fi
        echo "$ssl_cfg" | tee "$cache_file"
        return 0
    fi

    target_relation="cert-provider"
    while read-0 rn ts rc td; do
        [ "$rn" == "${target_relation}" ] || continue
        info "A cert-provider '$ts' declared as 'ssl' value"
        echo "$ts" | tee "$cache_file"
        return 0
    done < <(get_service_relations "$SERVICE_NAME")

    return 1
}


__vhost_cfg_normalize_protocol() {
    local cfg="$1" protocol ssl
    ## XXXvlab: can't cache if libcharm is not able to give me some checksums
    ##   indeed, ``_get_ssl_option_value`` depends on relations calculations...

    # local cfg="$1" cache_file="$CACHEDIR/$FUNCNAME.cache.$(p0 "$@" | md5_compat)" \
    #       protocol
    # if [ -e "$cache_file" ]; then
    #     #debug "$FUNCNAME: STATIC cache hit $1"
    #     cat "$cache_file" &&
    #     touch "$cache_file" || return 1
    #     return 0
    # fi

    if protocol=$(e "$cfg" | cfg-get-value protocol 2>/dev/null); then
        protocol=${protocol:-auto}
    else
        protocol=auto
    fi

    case "$protocol" in
        auto)
            ssl=$(_get_ssl_option_value "$cfg" 2>/dev/null)
            if [ "$ssl" ]; then
                protocol="http,https"
            else
                protocol="http"
            fi
            ;;
        both)
            protocol="http,https"
            ;;
        ssl|https)
            protocol="https"
            ;;
        http)
            protocol="http"
            ;;
        *)
            err "Invalid value '$protocol' for ${WHITE}protocol$NORMAL option (use one of: http, https, both, auto)."
            return 1
    esac
    echo -n ",$protocol,"
    #| tee "$cache_file"
}


## ssl_plugin_* and ssl_fallback should :
##  - do anything to ensure that
##  - issue config-add to add volumes if necessary
##  - output 3 vars of where to find the 3 files from within the docker apache

ssl_get_plugin_fun() {
    # from ssl conf, return the function that should manage SSL code creation
    local master_cfg="$1" cfg type keys
    cfg=$(_get_ssl_option_value "$master_cfg") || {
        err "No ssl options available."
        return 1
    }

    local cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$cfg")"
    if [ -e "$cache_file" ]; then
        cat "$cache_file"
        return 0
    fi
    [ "$cfg" ] || {
        touch "$cache_file"
        return 0
    }

    type="$(echo "$cfg" | shyaml -y get-type 2>/dev/null)" || return 1
    if [[ "$type" == "bool" ]] || [[ "$type" == "str" && "$cfg" =~ ^false|true$ ]]; then
        printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
        return 0
    fi

    if ! [[ "$type" == "str" || "$type" == "struct" ]]; then
        err "Invalid ${WHITE}ssl${NORMAL} value type '$type': please provide a string or a struct."
        return 1
    fi

    if [ -z "$NO_CERT_PROVIDER" ]; then
        if [[ "$type" == "str" ]]; then
            keys=("$cfg")
        else
            keys=($(echo "$cfg" | shyaml keys 2>/dev/null))
        fi
        for key in "${keys[@]}"; do
            target_relation="cert-provider"
            fun="ssl_plugin_${target_relation}"
            while read-0 relation_name target_service relation_config tech_dep; do
                [ "$relation_name" == "${target_relation}" ] || continue
                [ "$target_service" == "$key" ] || continue
                verb "Corresponding plugin ${DARKGREEN}found${NORMAL}" \
                     "in ${DARKBLUE}$relation_name${NORMAL}/${DARKYELLOW}$key${NORMAL}"
                ssl_cfg=$(printf "%s" "$cfg" | shyaml get-value "$key" 2>/dev/null) || true
                merged_config=$(merge_yaml_str "$relation_config" "$ssl_cfg") || return 1
                printf "%s\0" "$fun" "$key" "$merged_config" | tee "$cache_file"
                return 0
            done < <(get_service_relations "$SERVICE_NAME") || return 1
            case "$key" in
                cert|ca-cert|key)
                    :
                    ;;
                *)
                    err "Invalid key '$key' in ${WHITE}ssl${NORMAL}:" \
                        "no corresponding services declared in ${DARKBLUE}${target_relation}$NORMAL"
                    return 1
                    ;;
            esac
        done
    fi
    ## No key of the struct seem to be declared cert-provider, so fallback
    printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
}


ssl_fallback_vars() {
    local cfg="$1" ssl_cfg="$2" value="$3" domains="$4" cert key ca_cert
    domains=($domains)
    if __vhost_cfg_ssl_cert=$(echo "$ssl_cfg" | shyaml get-value cert 2>/dev/null); then
        __vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${domains[0]}.pem
    fi

    if __vhost_cfg_ssl_key=$(echo "$ssl_cfg" | shyaml get-value key 2>/dev/null); then
        __vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${domains[0]}.key
    fi

    if __vhost_cfg_ssl_ca_cert=$(echo "$ssl_cfg" | shyaml get-value ca-cert 2>/dev/null); then
        __vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${domains[0]}-ca.pem
    fi
}

ssl_fallback_prepare() {
    local cfg="$1" cert key ca_cert

    dst="$CONFIGSTORE/$BASE_SERVICE_NAME"
    volumes=""
    for label in cert key ca_cert; do
        content="$(eval echo "\"\$__vhost_cfg_ssl_$label\"")"
        if [ "$content" ]; then
            location="$(eval echo "\$__vhost_cfg_SSL_${label^^}_LOCATION")"
            echo "$content" | file_put "$dst$location"
            config_hash=$(printf "%s\0" "$config_hash" "$label" "$content" | md5_compat)
            volumes="$volumes
  - $dst$location:$location:ro"
        fi
    done

    if [ "$volumes" ]; then
        init-config-add "\
$MASTER_TARGET_SERVICE_NAME:
  volumes:
$volumes
"
    fi

}

ssl_plugin_cert-provider_vars() {
    local cfg="$1" ssl_cfg="$2" value="$3" domains="$4"
    domains=($domains)
    __vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${domains[0]}/cert.pem
    __vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${domains[0]}/privkey.pem
    __vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${domains[0]}/chain.pem
}

ssl_plugin_cert-provider_prepare() {
    local cfg="$1" ssl_cfg="$2" service="$3" domains="$4" options  server_aliases
    domains=($domains)
    options=$(yaml_key_val_str "options" "$ssl_cfg") || return 1
    service_config=$(yaml_key_val_str "$service" "$options")
    compose --debug --add-compose-content "$service_config" crt "$service" \
            create "${domains[@]}" || {
        err "Failed to launch letsencrypt for certificate creation."
        return 1
    }
    init-config-add "\
$SERVICE_NAME:
  volumes:
  - $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro
" || return 1

}


apache_passwd_file() {
    local cfg="$1" dest="$2" creds
    include parse || true

    ## XXXvlab: called twice... no better way to do this ?
    creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
    password_path=$(password-path-get "$dest")
    first=
    if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path" ]; then
        debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path, creating password file." || true
        first=c
    fi
    while read-0 login password; do
        debug "htpasswd -b$first '${password_path}' '$login' '$password'"
        echo "htpasswd -b$first '${password_path}' '$login' '$password'"
        if [ "$first" ]; then
            first=
        fi
    done < <(e "$creds" | shyaml key-values-0 2>/dev/null) |
        docker run -i --entrypoint "/bin/bash" \
               -v "$APACHE_CONFIG_LOCATION:/etc/apache2/sites-enabled" \
               "$DOCKER_BASE_IMAGE" || return 1
}

## Produce the full statements depending on relation-get informations
apache_vhost_statement() {
    local type="$1" protocols="$2" cfg="$3" domains="$4" \
          vhost_statement

    if is_protocol_enabled http "$protocols"; then
        __vhost_full_vhost_statement "$type" http "$cfg" "$domains" || return 1
    fi
    if is_protocol_enabled https "$protocols"; then
        read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
        "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domains" || return 1
        vhost_statement=$(__vhost_full_vhost_statement "$type" https "$cfg" "$domains") || return 1
        cat <<EOF

<IfModule mod_ssl.c>
$(echo "$vhost_statement" | prefix "  ")
</IfModule>
EOF
    fi
}
export -f apache_vhost_statement


apache_code_dir() {
    local cfg="$1" service_cfg="$2" www_data_gid local_path
    www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
        debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
        return 1
    }
    domains=($(get_domains "$cfg" "$service_cfg")) || return 1
    local_path="/var/www/${domains[0]}"
    host_path=$(e "$cfg" | cfg-get-value location 2>/dev/null) ||
        host_path="$DATASTORE/$BASE_SERVICE_NAME${local_path}"


    ## convert to docker host path
    case "$host_path" in
        "$DATASTORE"*)
            docker_host_path="$HOST_DATASTORE${host_path##$DATASTORE}"
            ;;
        "$CONFIGSTORE"*)
            docker_host_path="$HOST_CONFIGSTORE${host_path##$CONFIGSTORE}"
            ;;
        *)
            docker_host_path="$host_path"
            ;;
    esac

    mkdir -p "$host_path" || return 1

    init-config-add "
$SERVICE_NAME:
  volumes:
    - \"${docker_host_path}:$local_path\"
"
}

apache_data_dirs() {
    local cfg="$1" service_cfg="$2" data_dirs dst data fdir to_create
    data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
    if [ -z "$data_dirs" ]; then
        return 0
    fi
    domains=($(get_domains "$cfg" "$service_cfg")) || return 1
    local_path="/var/www/${domains[0]}"
    dst=$DATASTORE/$BASE_SERVICE_NAME$local_path
    data=()
    while IFS="," read -ra addr; do
        for dir in "${addr[@]}"; do
            data+=("$dir")
        done
    done <<< "$data_dirs"

    www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
        debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
        return 1
    }
    info "www-data gid from ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} is '$www_data_gid'"

    to_create=()
    volumes=""
    for d in "${data[@]}"; do
        fdir="$dst/$d"
        volumes+="    - $HOST_DATASTORE${dst##$DATASTORE}/$d:$local_path/$d"$'\n'
        if ! [ -d "$fdir" ]; then
            to_create+=("$fdir")
        fi
    done

    if [ "${#to_create[@]}" -gt 0 ]; then
        mkdir -p "${to_create[@]}" || return 1
        chgrp -v "${www_data_gid}" "${to_create[@]}" || return 1
        chmod g+rwx "${to_create[@]}" || return 1
    fi

    init-config-add "
$SERVICE_NAME:
  volumes:
$volumes"

}

deploy_files() {
    local src="$1" dst="$2"

    if ! [ -d "$dst" ]; then
        err "Destination '$dst' does not exist or is not a directory"
        return 1
    fi
    (
        cd "$dst" && info "In $dst:" &&
        get_file "$src" | tar xv
    )
}
export -f deploy_files


apache_core_rules_add() {
    local conf="$1" dst="/etc/apache2/conf-enabled/$SERVICE_NAME.conf"
    debug "Adding core rule."
    echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst"
    config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
    init-config-add "
$SERVICE_NAME:
  volumes:
  - $CONFIGSTORE/$SERVICE_NAME$dst:$dst:ro
"
}


__vhost_ssl_statement() {

    ## defaults

    __vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
    __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}

    cat <<EOF

##
## SSL Configuration
##

SSLEngine On

SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
$([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
$([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
SSLVerifyClient None

EOF

}


password-path-get() {
    local dest="$1"
    echo "/etc/apache2/sites-enabled/${dest}.passwd"
}

__vhost_creds_statement() {
    local cfg="$1" dest="$2" password_path
    password_path=$(password-path-get "$dest") || return 1
    if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then
        echo "Allow from all"
        return 0
    fi

    cat <<EOF
AuthType basic
AuthName "private"
AuthUserFile ${password_path}
Require valid-user
EOF

}


__vhost_head_statement() {
    local cfg="$1" protocol="$2" domains="$3" server_aliases admin_mail prefix
    domains=($domains)
    admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true
    if [ -z "$admin_mail" ]; then
        if [ "${#domains[@]}" == 0 ]; then
            admin_mail=webmaster@localhost
        else
            admin_mail=${admin_mail:-contact@${domains[0]}}
        fi
    fi

    if [ "$protocol" == "https" ]; then
        prefix="s-"
    else
        prefix=
    fi

    if [ "${#domains[@]}" != 0 ]; then
        log_prefix="${prefix}${domains[0]}_"
    else
        log_prefix=""
    fi

    cat <<EOF
$(
  echo "ServerAdmin ${admin_mail}"
  [ "${#domains[@]}" != 0 ] && echo "ServerName ${domains[0]}"
  for alias in "${domains[@]:1}"; do
      [ "$alias" ] || continue
      echo "ServerAlias $alias"
  done
)

ServerSignature Off
CustomLog /var/log/apache2/${log_prefix}access.log combined
ErrorLog /var/log/apache2/${log_prefix}error.log
ErrorLog syslog:local2
EOF

}


_get_custom_rules() {
    local cfg="$1" custom_rules
    custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true
    e "$custom_rules" | yaml_get_values
}


__vhost_custom_rules() {
    local cfg="$1" custom_rules
    custom_rules=$(_get_custom_rules "$cfg") || return 1
    if [ "$custom_rules" ]; then
        cat <<EOF


##
## Custom rules
##

$custom_rules

EOF
    fi
}


__vhost_content_statement() {
    local type="$1"
    shift
    case "$type" in
        "web_proxy")
            __vhost_proxy_statement "$@" || return 1
            ;;
        "publish_dir")
            __vhost_publish_dir_statement "$@" || return 1
            ;;
        "ssh_tunnel")
            __vhost_tunnel_ssh_statement "$@" || return 1
            ;;
    esac
}


target-get() {
    local cfg="$1" target first_exposed_port base_image
    target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true
    if [ -z "$target" ]; then
        ## First exposed port:
        base_image=$(service_ensure_image_ready "$BASE_SERVICE_NAME") || return 1
        if ! docker_has_image "$base_image"; then
            docker pull "$base_image" >&2
        fi
        first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
        if [ -z "$first_exposed_port" ]; then
            err "Failed to get first exposed port of image '$base_image'."
            return 1
        fi
        target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
        info "No target was specified, introspection found: $target"
    fi
    echo "$target"
}

__vhost_proxy_statement() {
    local protocol="$1" cfg="$2" domains="$3" proxy_pass_options target
    domains=($domains)
    target=$(target-get "$cfg") || return 1
    proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values))
    if [ "${#proxy_pass_options[@]}" == 0 ]; then
        proxy_pass_options=(${proxy_pass_options:-"retry=0"})
    fi
    dest=${domains[0]}
    dest=${dest:-html}
    cat <<EOF


##
## Proxy declaration towards $target
##

<IfModule mod_proxy.c>
    ProxyRequests Off
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyVia On
    ProxyPass / http://$target/ ${proxy_pass_options[*]}
    <Location / >
$(__vhost_creds_statement "$cfg" "$dest" | prefix "        ")
        ProxyPassReverse http://$target/
    </Location>
$([ "$protocol" == "https" ] && echo "    SSLProxyEngine On")
</IfModule>

SetEnvIf X-Forwarded-Proto "^$" forwarded_proto_not_set=true
RequestHeader set "X-Forwarded-Proto" "$protocol" env=forwarded_proto_not_set

## Fix IE problem (httpapache proxy dav error 408/409)
SetEnv proxy-nokeepalive 1
EOF

}

__vhost_full_vhost_statement() {
    local type="$1" protocol="$2" cfg="$3" domains="$4" head_statement custom_rules content_statement

    domains=($domains)
    head_statement=$(__vhost_head_statement "$cfg" "$protocol" "${domains[*]}") || return 1
    custom_rules=$(__vhost_custom_rules "$cfg") || return 1
    content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domains[*]}") || return 1

    case "$protocol" in
        https)
            PORT=443
            ;;
        http)
            PORT=80
            ;;
    esac

    cat <<EOF
<VirtualHost *:$PORT>

$(
{
    echo "$head_statement"
    [ "$custom_rules" ] && echo "$custom_rules"
    echo "$content_statement"
} | prefix "    ")

    ## Forbid any cache, this is only usefull on dev server.
    #Header set Cache-Control "no-cache"
    #Header set Access-Control-Allow-Origin "*"
    #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
    #Header set Access-Control-Allow-Headers "origin, content-type, accept"
$([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix "    " && echo )
</VirtualHost>
EOF

}

__vhost_publish_dir_statement() {
    local protocol="$1" cfg="$2" domains="$3" location
    domains=($domains)
    dest=${domains[0]}
    dest=${dest:-html}
    local_path="/var/www/${dest}"

    cat <<EOF


##
## Publish directory $local_path
##

DocumentRoot $local_path

<Directory />
    Options FollowSymLinks
    AllowOverride None
</Directory>

<Directory $local_path>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride all
$(__vhost_creds_statement "$cfg" "$dest" | prefix "    ")
</Directory>

EOF
}


__vhost_tunnel_ssh_statement() {
    local protocol="$1" cfg="$2" domains="$3" custom_rules content_statement
    domains=($domains)
    dest=${domains[0]}
    if [ "${#domains[@]}" == 0 ]; then
        err "You must specify a domain for ssh tunnel to work."
        return 1
    fi
    cat <<EOF


##
## SSH Tunnel
##

#HostnameLookups On
ProxyRequests On
AllowConnect 22
#ProxyVia on

### Deny everything by default

<Proxy *>
    Order deny,allow
    Deny from all
</proxy>

### Accept redirect only to same domain

<Proxy ${domains[0]}>
    Order deny,allow
$(__vhost_creds_statement "$cfg" "$dest" | prefix "    ")
</Proxy>

EOF
}


apache_config_hash() {
    debug "Adding config hash to enable recreating upon config change."
    config_hash=$({
                     printf "%s\0" "$config_hash"
                     find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
                          -name \*.conf -exec md5sum {} \;
                  } | md5_compat) || exit 1
    init-config-add "
$SERVICE_NAME:
  labels:
  - compose.config_hash=$config_hash
"
}