/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.cxf.ws.security.policy.model;

import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.cxf.ws.policy.PolicyBuilder;
import org.apache.cxf.ws.security.policy.SP12Constants;
import org.apache.cxf.ws.security.policy.SPConstants;
import org.apache.cxf.ws.security.policy.SPConstants.SupportTokenType;
import org.apache.neethi.All;
import org.apache.neethi.ExactlyOne;
import org.apache.neethi.Policy;

public class SupportingToken extends TokenWrapper implements AlgorithmWrapper {

    /**
     * Type of SupportingToken
     * 
     * @see SupportingToken#SUPPORTING
     * @see SupportingToken#ENDORSING
     * @see SupportingToken#SIGNED
     * @see SupportingToken#SIGNED_ENDORSING
     */
    private SupportTokenType type;

    private AlgorithmSuite algorithmSuite;

    private List<Token> tokens = new ArrayList<Token>();

    private SignedEncryptedElements signedElements;

    private SignedEncryptedElements encryptedElements;

    private SignedEncryptedParts signedParts;

    private SignedEncryptedParts encryptedParts;

    public SupportingToken(SupportTokenType type, SPConstants version, PolicyBuilder b) {
        super(version, b);
        this.type = type;
    }

    /**
     * @return Returns the algorithmSuite.
     */
    public AlgorithmSuite getAlgorithmSuite() {
        return algorithmSuite;
    }

    /**
     * @param algorithmSuite The algorithmSuite to set.
     */
    public void setAlgorithmSuite(AlgorithmSuite algorithmSuite) {
        this.algorithmSuite = algorithmSuite;
    }

    /**
     * @return Returns the token.
     */
    public List<Token> getTokens() {
        return tokens;
    }

    /**
     * @param token The token to set.
     */
    public void addToken(Token token) {
        this.tokens.add(token);
    }

    /**
     * @return Returns the type.
     */
    public SupportTokenType getTokenType() {
        return type;
    }

    /**
     * @param type The type to set.
     */
    public void setTokenType(SupportTokenType t) {
        this.type = t;
    }

    /**
     * @return Returns the encryptedElements.
     */
    public SignedEncryptedElements getEncryptedElements() {
        return encryptedElements;
    }

    /**
     * @param encryptedElements The encryptedElements to set.
     */
    public void setEncryptedElements(SignedEncryptedElements encryptedElements) {
        this.encryptedElements = encryptedElements;
    }

    /**
     * @return Returns the encryptedParts.
     */
    public SignedEncryptedParts getEncryptedParts() {
        return encryptedParts;
    }

    /**
     * @param encryptedParts The encryptedParts to set.
     */
    public void setEncryptedParts(SignedEncryptedParts encryptedParts) {
        this.encryptedParts = encryptedParts;
    }

    /**
     * @return Returns the signedElements.
     */
    public SignedEncryptedElements getSignedElements() {
        return signedElements;
    }

    /**
     * @param signedElements The signedElements to set.
     */
    public void setSignedElements(SignedEncryptedElements signedElements) {
        this.signedElements = signedElements;
    }

    /**
     * @return Returns the signedParts.
     */
    public SignedEncryptedParts getSignedParts() {
        return signedParts;
    }

    /**
     * @param signedParts The signedParts to set.
     */
    public void setSignedParts(SignedEncryptedParts signedParts) {
        this.signedParts = signedParts;
    }

    /*
     * (non-Javadoc)
     * @see org.apache.ws.security.policy.TokenWrapper#setToken(org.apache.ws.security.policy.Token)
     */
    public void setToken(Token tok) {
        this.addToken(tok);
    }
    public Token getToken() {
        if (tokens.size() == 1) { 
            return tokens.get(0);
        }
        return null;
    }


    public QName getRealName() {
        QName ret = null;
        
        switch (type) {
        case SUPPORTING_TOKEN_SUPPORTING:
            ret = constants.getSupportingTokens();
            break;
        case SUPPORTING_TOKEN_SIGNED:
            ret = constants.getSignedSupportingTokens();
            break;
        case SUPPORTING_TOKEN_ENDORSING:
            ret = constants.getEndorsingSupportingTokens();
            break;
        case SUPPORTING_TOKEN_SIGNED_ENDORSING:
            ret = constants.getSignedEndorsingSupportingTokens();
            break;
        case SUPPORTING_TOKEN_ENCRYPTED:
            ret = SP12Constants.ENCRYPTED_SUPPORTING_TOKENS;
            break;
        case SUPPORTING_TOKEN_SIGNED_ENCRYPTED:
            ret = SP12Constants.SIGNED_ENCRYPTED_SUPPORTING_TOKENS;
            break;
        case SUPPORTING_TOKEN_ENDORSING_ENCRYPTED:
            ret = SP12Constants.ENDORSING_ENCRYPTED_SUPPORTING_TOKENS;
            break;
        case SUPPORTING_TOKEN_SIGNED_ENDORSING_ENCRYPTED:
            ret = SP12Constants.SIGNED_ENDORSING_ENCRYPTED_SUPPORTING_TOKENS;
            break;
        default:
            ret = null;
            break;
        }
        return ret;
    }
    public QName getName() {
        QName ret = null;
        switch (type) {
        case SUPPORTING_TOKEN_SUPPORTING:
            ret = SP12Constants.INSTANCE.getSupportingTokens();
            break;
        case SUPPORTING_TOKEN_SIGNED:
            ret = SP12Constants.INSTANCE.getSignedSupportingTokens();
            break;
        case SUPPORTING_TOKEN_ENDORSING:
            ret = SP12Constants.INSTANCE.getEndorsingSupportingTokens();
            break;
        case SUPPORTING_TOKEN_SIGNED_ENDORSING:
            ret = SP12Constants.INSTANCE.getSignedEndorsingSupportingTokens();
            break;
        case SUPPORTING_TOKEN_ENCRYPTED:
            ret = SP12Constants.ENCRYPTED_SUPPORTING_TOKENS;
            break;
        case SUPPORTING_TOKEN_SIGNED_ENCRYPTED:
            ret = SP12Constants.SIGNED_ENCRYPTED_SUPPORTING_TOKENS;
            break;
        case SUPPORTING_TOKEN_ENDORSING_ENCRYPTED:
            ret = SP12Constants.ENDORSING_ENCRYPTED_SUPPORTING_TOKENS;
            break;
        case SUPPORTING_TOKEN_SIGNED_ENDORSING_ENCRYPTED:
            ret = SP12Constants.SIGNED_ENDORSING_ENCRYPTED_SUPPORTING_TOKENS;
            break;
        default:
            ret = null;
            break;
        }
        return ret;
    }

    /**
     * @return true if the supporting token should be encrypted
     */

    public boolean isEncryptedToken() {
        return type == SPConstants.SupportTokenType.SUPPORTING_TOKEN_ENCRYPTED
            || type == SPConstants.SupportTokenType.SUPPORTING_TOKEN_SIGNED_ENCRYPTED
            || type == SPConstants.SupportTokenType.SUPPORTING_TOKEN_ENDORSING_ENCRYPTED
            || type == SPConstants.SupportTokenType.SUPPORTING_TOKEN_SIGNED_ENDORSING_ENCRYPTED;
    }

    public short getType() {
        return org.apache.neethi.Constants.TYPE_ASSERTION;
    }

    public void serialize(XMLStreamWriter writer) throws XMLStreamException {
        String namespaceURI = getRealName().getNamespaceURI();

        String prefix = writer.getPrefix(namespaceURI);
        if (prefix == null) {
            prefix = getRealName().getPrefix();
            writer.setPrefix(prefix, namespaceURI);
        }

        String localname = getRealName().getLocalPart();

        // <sp:SupportingToken>
        writer.writeStartElement(prefix, localname, namespaceURI);

        // xmlns:sp=".."
        writer.writeNamespace(prefix, namespaceURI);

        String pPrefix = writer.getPrefix(SPConstants.POLICY.getNamespaceURI());
        if (pPrefix == null) {
            pPrefix = SPConstants.POLICY.getPrefix();
            writer.setPrefix(pPrefix, SPConstants.POLICY.getNamespaceURI());
        }
        // <wsp:Policy>
        writer.writeStartElement(pPrefix, SPConstants.POLICY.getLocalPart(), SPConstants.POLICY
            .getNamespaceURI());

        for (Token token : getTokens()) {
            // [Token Assertion] +
            token.serialize(writer);
        }

        if (signedParts != null) {
            signedParts.serialize(writer);
        } else if (signedElements != null) {
            signedElements.serialize(writer);
        } else if (encryptedParts != null) {
            encryptedParts.serialize(writer);
        } else if (encryptedElements != null) {
            encryptedElements.serialize(writer);
        }
        // </wsp:Policy>
        writer.writeEndElement();

        writer.writeEndElement();
        // </sp:SupportingToken>
    }
    
    
    public Policy getPolicy() {
        Policy p = new Policy();
        ExactlyOne ea = new ExactlyOne();
        p.addPolicyComponent(ea);
        All all = new All();

        for (Token token : getTokens()) {
            all.addPolicyComponent(token);
        }
        
        if (signedParts != null) {
            all.addPolicyComponent(signedParts);
        } else if (signedElements != null) {
            all.addPolicyComponent(signedElements);
        } else if (encryptedParts != null) {
            all.addPolicyComponent(encryptedParts);
        } else if (encryptedElements != null) {
            all.addPolicyComponent(encryptedElements);
        }        
        
        ea.addPolicyComponent(all);
        Policy pc = p.normalize(builder.getPolicyRegistry(), true);
        if (pc != null) {
            return pc;
        } else {
            return new Policy();
        }
    }

}
