/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2015 ForgeRock AS.
 */

package org.forgerock.json.jose.jws;

import static org.forgerock.json.jose.jws.JwsHeaderKey.CRIT;
import static org.forgerock.json.jose.jws.JwsHeaderKey.CTY;
import static org.forgerock.json.jose.jws.JwsHeaderKey.JKU;
import static org.forgerock.json.jose.jws.JwsHeaderKey.JWK;
import static org.forgerock.json.jose.jws.JwsHeaderKey.KID;
import static org.forgerock.json.jose.jws.JwsHeaderKey.X5C;
import static org.forgerock.json.jose.jws.JwsHeaderKey.X5T;
import static org.forgerock.json.jose.jws.JwsHeaderKey.X5U;
import static org.forgerock.json.jose.jws.JwsHeaderKey.getHeaderKey;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.forgerock.json.jose.exceptions.JwtRuntimeException;
import org.forgerock.json.jose.jwk.JWK;
import org.forgerock.json.jose.jwt.JwtHeader;
import org.forgerock.json.jose.utils.Utils;
import org.forgerock.util.encode.Base64;

/**
 * A base implementation for the common security header parameters shared by the JWS and JWE headers.
 *
 * @since 2.0.0
 */
public abstract class JwtSecureHeader extends JwtHeader {

    /**
     * Constructs a new, empty JwtSecureHeader.
     */
    public JwtSecureHeader() {
    }

    /**
     * Constructs a new JwtSecureHeader, with its parameters set to the contents of the given Map.
     *
     * @param headers A Map containing the parameters to be set in the header.
     */
    public JwtSecureHeader(Map<String, Object> headers) {
        setParameters(headers);
    }

    /**
     * Sets the JWK Set URL header parameter for this JWS.
     * <p>
     * A URI that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used
     * to digitally sign the JWS.
     * <p>
     * The keys MUST be encoded as a JSON Web Key Set (JWK Set).
     * <p>
     * The protocol used to acquire the resource MUST provide integrity protection and the identity of the server MUST
     * be validated.
     *
     * @param jwkSetUrl The JWK Set URL.
     */
    public void setJwkSetUrl(URL jwkSetUrl) {
        put(JKU.value(), new String(jwkSetUrl.toString()));
    }

    /**
     * Gets the JWK Set URL header parameter for this JWS.
     *
     * @return The JWK Set URL.
     */
    public URL getJwkSetUrl() {
        try {
            return get(JKU.value()).asURI().toURL();
        } catch (MalformedURLException e) {
            throw new JwtRuntimeException(e);
        }
    }

    /**
     * Sets the JSON Web Key header parameter for this JWS.
     * <p>
     * The public key that corresponds to the key used to digitally sign the JWS. This key is represented as a JSON Web
     * Key (JWK).
     *
     * @param jsonWebKey The JSON Web Key.
     */
    public void setJsonWebKey(JWK jsonWebKey) {
        put(JWK.value(), jsonWebKey);
    }

    /**
     * Gets the JSON Web Key header parameter for this JWS.
     *
     * @return The JSON Web Key.
     */
    public JWK getJsonWebKey() {
        return (JWK) get(JWK.value()).getObject();
    }

    /**
     * Sets the X.509 URL header parameter for this JWS.
     * <p>
     * A URI that refers to a resource for the X.509 public key certificate or certificate chain corresponding to the
     * key used to digitally sign the JWS.
     * <p>
     * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the
     * first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the
     * one used to certify the previous one.
     * <p>
     * The protocol used to acquire the resource MUST provide integrity protection and the identity of the server MUST
     * be validated.
     *
     * @param x509Url The X.509 URL.
     */
    public void setX509Url(URL x509Url) {
        put(X5U.value(), new String(x509Url.toString()));
    }

    /**
     * Gets the X.509 URL header parameter for this JWS.
     *
     * @return The X.509 URL.
     */
    public URL getX509Url() {
        try {
            return get(X5U.value()).asURI().toURL();
        } catch (MalformedURLException e) {
            throw new JwtRuntimeException(e);
        }
    }

    /**
     * Sets the X.509 Certificate Thumbprint header parameter for this JWS.
     * <p>
     * A base64url encoded SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the X.509 certificate corresponding
     * to the key used to digitally sign the JWS.
     * <p>
     * This method will perform the base64url encoding so the x509CertificateThumbprint must be the SHA-1 digest.
     *
     * @param x509CertificateThumbprint The X.509 Certificate Thumbprint.
     */
    public void setX509CertificateThumbprint(String x509CertificateThumbprint) {
        put(X5T.value(), Utils.base64urlEncode(x509CertificateThumbprint));
    }

    /**
     * Gets the X.509 Certificate Thumbprint header parameter for this JWS.
     *
     * @return The X.509 Certificate Thumbprint.
     */
    public String getX509CertificateThumbprint() {
        return get(X5T.value()).asString();
    }

    /**
     * Sets the X.509 Certificate Chain header parameter for this JWS.
     * <p>
     * Contains the list of X.509 public key certificate or certificate chain corresponding to the key used to
     * digitally sign the JWS.
     * Each entry in the list is a base64 encoded DER PKIX certificate value.
     * This method will perform the base64 encoding of each entry so the entries in the list must be the DER PKIX
     * certificate values.
     * <p>
     * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the
     * first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the
     * one used to certify the previous one.
     * <p>
     *
     * @param x509CertificateChain The X.509 Certificate Chain.
     */
    public void setX509CertificateChain(List<String> x509CertificateChain) {
        List<String> encodedCertChain = new ArrayList<>();
        for (String x509Cert : x509CertificateChain) {
            encodedCertChain.add(Base64.encode(x509Cert.getBytes(Utils.CHARSET)));
        }
        put(X5C.value(), encodedCertChain);
    }

    /**
     * Gets the X.509 Certificate Chain header parameter for this JWS.
     *
     * @return The X.509 Certificate Chain.
     */
    public List<String> getX509CertificateChain() {
        return get(X5C.value()).asList(String.class);
    }

    /**
     * Sets the Key ID header parameter for this JWS.
     * <p>
     * Indicates which key was used to secure the JWS, allowing originators to explicitly signal a change of key to
     * recipients.
     *
     * @param keyId The Key ID.
     */
    public void setKeyId(String keyId) {
        put(KID.value(), keyId);
    }

    /**
     * Gets the Key ID header parameter for this JWS.
     *
     * @return The Key ID.
     */
    public String getKeyId() {
        return get(KID.value()).asString();
    }

    /**
     * Sets the content type header parameter for this JWS.
     * <p>
     * Declares the type of the secured content (the Payload).
     *
     * @param contentType The content type of this JWS' payload.
     */
    public void setContentType(String contentType) {
        put(CTY.value(), contentType);
    }

    /**
     * Gets the content type header parameter for this JWS.
     *
     * @return The content type of this JWS' payload.
     */
    public String getContentType() {
        return get(CTY.value()).asString();
    }

    /**
     * Sets the critical header parameters for this JWS.
     * <p>
     * This header parameter indicates that extensions to the JWS specification are being used that MUST be understood
     * and processed.
     * <p>
     * The criticalHeaders parameter cannot be an empty list.
     *
     * @param criticalHeaders A List of the critical parameters.
     */
    public void setCriticalHeaders(List<String> criticalHeaders) {
        if (criticalHeaders != null && criticalHeaders.isEmpty()) {
            throw new JwtRuntimeException("Critical Headers parameter cannot be an empty list");
        }
        put(CRIT.value(), criticalHeaders);
    }

    /**
     * Gets the critical header parameters for this JWS.
     *
     * @return A List of the critical parameters.
     */
    public List<String> getCriticalHeaders() {
        return get(CRIT.value()).asList(String.class);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setParameter(String key, Object value) {
        JwsHeaderKey headerKey = getHeaderKey(key.toUpperCase());

        switch (headerKey) {
        case JKU: {
            checkValueIsOfType(value, URL.class);
            setJwkSetUrl((URL) value);
            break;
        }
        case JWK: {
            checkValueIsOfType(value, JWK.class);
            setJsonWebKey((JWK) value);
            break;
        }
        case X5U: {
            checkValueIsOfType(value, URL.class);
            setX509Url((URL) value);
            break;
        }
        case X5T: {
            checkValueIsOfType(value, String.class);
            setX509CertificateThumbprint((String) value);
            break;
        }
        case X5C: {
            checkValueIsOfType(value, List.class);
            checkListValuesAreOfType((List<?>) value, String.class);
            setX509CertificateChain((List<String>) value);
            break;
        }
        case KID: {
            checkValueIsOfType(value, String.class);
            setKeyId((String) value);
            break;
        }
        case CTY: {
            checkValueIsOfType(value, String.class);
            setContentType((String) value);
            break;
        }
        case CRIT: {
            checkValueIsOfType(value, List.class);
            checkListValuesAreOfType((List<?>) value, String.class);
            setCriticalHeaders((List<String>) value);
            break;
        }
        default: {
            super.setParameter(key, value);
        }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getParameter(String key) {
        JwsHeaderKey headerKey = getHeaderKey(key.toUpperCase());

        Object value;

        switch (headerKey) {
        case JKU: {
            value = getJwkSetUrl();
            break;
        }
        case JWK: {
            value = getJsonWebKey();
            break;
        }
        case X5U: {
            value = getX509Url();
            break;
        }
        case X5T: {
            value = getX509CertificateThumbprint();
            break;
        }
        case X5C: {
            value = getX509CertificateChain();
            break;
        }
        case KID: {
            value = getKeyId();
            break;
        }
        case CTY: {
            value = getContentType();
            break;
        }
        case CRIT: {
            value = getCriticalHeaders();
            break;
        }
        default: {
            value = super.getParameter(key);
        }
        }

        return value;
    }
}
