/*
 *  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.directory.shared.kerberos.components;


import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.apache.directory.api.asn1.EncoderException;
import org.apache.directory.api.asn1.ber.tlv.BerValue;
import org.apache.directory.api.asn1.ber.tlv.TLV;
import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.kerberos.KerberosConstants;
import org.apache.directory.shared.kerberos.KerberosMessageType;
import org.apache.directory.shared.kerberos.messages.KerberosMessage;
import org.apache.directory.shared.kerberos.messages.Ticket;


/**
 * The KDC-REP data structure. It will store the object described by the ASN.1 grammar :
 * <pre>
 * KDC-REP         ::= SEQUENCE {
 *         pvno            [0] INTEGER (5),
 *         msg-type        [1] INTEGER (11 -- AS -- | 13 -- TGS --),
 *         padata          [2] SEQUENCE OF PA-DATA OPTIONAL
 *                                 -- NOTE: not empty --,
 *         crealm          [3] Realm,
 *         cname           [4] <PrincipalName>,
 *         ticket          [5] <Ticket>,
 *         enc-part        [6] <EncryptedData>
 *                                 -- EncASRepPart or EncTGSRepPart,
 *                                 -- as appropriate
 * }
 * </pre>
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class KdcRep extends KerberosMessage
{
    /** The PA-DATAs */
    private List<PaData> paData;

    /** The client realm */
    private String crealm;

    /** A storage for a byte array representation of the realm */
    private byte[] crealmBytes;

    /** The client principal name */
    private PrincipalName cname;

    /** The ticket tickets */
    private Ticket ticket;

    /** Encoded part */
    private EncryptedData encPart;

    /** The decoded KDC-REP part */
    protected EncKdcRepPart encKdcRepPart;

    // Storage for computed lengths
    private int pvnoLength;
    private int msgTypeLength;
    private int paDataLength;
    private int paDataSeqLength;
    private int[] paDataLengths;
    private int cnameLength;
    private int crealmLength;
    private int ticketLength;
    private int encPartLength;
    private int kdcRepSeqLength;
    private int kdcRepLength;


    /**
     * Creates a new instance of KDC-REP.
     */
    public KdcRep( KerberosMessageType msgType )
    {
        super( msgType );
        paData = new ArrayList<PaData>();
    }


    /**
     * @return the pvno
     */
    public int getPvno()
    {
        return getProtocolVersionNumber();
    }


    /**
     * @param pvno the pvno to set
     */
    public void setPvno( int pvno )
    {
        setProtocolVersionNumber( pvno );
    }


    /**
     * @return the paData
     */
    public List<PaData> getPaData()
    {
        return paData;
    }


    /**
     * @param paData the paData to set
     */
    public void addPaData( PaData paData )
    {
        this.paData.add( paData );
    }


    /**
     * Returns the client realm.
     *
     * @return The client realm.
     */
    public String getCRealm()
    {
        return crealm;
    }


    /**
     * Set the client realm
     * @param crealm the client realm
     */
    public void setCRealm( String crealm )
    {
        this.crealm = crealm;
    }


    /**
     * Returns the client {@link PrincipalName}.
     *
     * @return The client {@link PrincipalName}.
     */
    public PrincipalName getCName()
    {
        return cname;
    }


    /**
     * Set the client principalName
     * @param cname the client principalName
     */
    public void setCName( PrincipalName cname )
    {
        this.cname = cname;
    }


    /**
     * Returns the {@link Ticket}
     *
     * @return The {@link Ticket}
     */
    public Ticket getTicket()
    {
        return ticket;
    }


    /**
     * Set the Ticket
     * @param ticket the ticket to set
     */
    public void setTicket( Ticket ticket )
    {
        this.ticket = ticket;
    }


    /**
     * Returns the encrypted part as {@link EncryptedData}.
     *
     * @return The encrypted part as {@link EncryptedData}.
     */
    public EncryptedData getEncPart()
    {
        return encPart;
    }


    /**
     * @param encPart the encPart to set
     */
    public void setEncPart( EncryptedData encPart )
    {
        this.encPart = encPart;
    }


    /**
     * @return the encKdcRepPart
     */
    public EncKdcRepPart getEncKdcRepPart()
    {
        return encKdcRepPart;
    }


    /**
     * @param encKdcRepPart the encKdcRepPart to set
     */
    public void setEncKdcRepPart( EncKdcRepPart encKdcRepPart )
    {
        this.encKdcRepPart = encKdcRepPart;
    }


    /**
     * Compute the KDC-REP length
     * <pre>
     * KDC-REP :
     * 
     * 0x30 L1 KDC-REP sequence
     *  |
     *  +--> 0xA0 0x03 pvno tag
     *  |     |
     *  |     +--> 0x02 0x01 0x05 pvno (5)
     *  |
     *  +--> 0xA1 0x03 msg-type tag
     *  |     |
     *  |     +--> 0x02 0x01 0x0B/0x0D msg-type : either AS-REP (0x0B) or TGS-REP (0x0D)
     *  |     
     *  +--> 0xA2 L2 pa-data tag
     *  |     |
     *  |     +--> 0x30 L2-1 pa-data SEQ
     *  |           |
     *  |           +--> 0x30 L2-1-1 pa-data
     *  |           |
     *  |           +--> 0x30 L2-1-2 pa-data
     *  |           :
     *  |     
     *  +--> 0xA3 L3 crealm tag
     *  |     |
     *  |     +--> 0x1B L3-1 crealm
     *  |
     *  +--> 0xA4 L4 cname tag
     *  |     |
     *  |     +--> 0x30 L4-1 cname
     *  |
     *  +--> 0xA5 L5 ticket tag
     *  |     |
     *  |     +--> 0x61 L5-1 ticket
     *  |
     *  +--> 0xA6 L6 enc-part tag
     *        |
     *        +--> 0x30 L6-1 enc-part
     *  
     * </pre>       
     */
    public int computeLength()
    {
        // The pvno length
        pvnoLength = 1 + 1 + 1;
        kdcRepSeqLength = 1 + TLV.getNbBytes( pvnoLength ) + pvnoLength;

        // The msg-type length
        msgTypeLength = 1 + 1 + 1;
        kdcRepSeqLength += 1 + TLV.getNbBytes( msgTypeLength ) + msgTypeLength;

        // Compute the pa-data length.
        if ( paData.size() != 0 )
        {
            paDataLengths = new int[paData.size()];
            int pos = 0;
            paDataSeqLength = 0;

            for ( PaData paDataElem : paData )
            {
                paDataLengths[pos] = paDataElem.computeLength();
                paDataSeqLength += paDataLengths[pos];
                pos++;
            }

            paDataLength = 1 + TLV.getNbBytes( paDataSeqLength ) + paDataSeqLength;
            kdcRepSeqLength += 1 + TLV.getNbBytes( paDataLength ) + paDataLength;
        }

        // The crealm length
        crealmBytes = Strings.getBytesUtf8( crealm );
        crealmLength = 1 + TLV.getNbBytes( crealmBytes.length ) + crealmBytes.length;
        kdcRepSeqLength += 1 + TLV.getNbBytes( crealmLength ) + crealmLength;

        // Compute the client principalName length
        cnameLength = cname.computeLength();
        kdcRepSeqLength += 1 + TLV.getNbBytes( cnameLength ) + cnameLength;

        // Compute the ticket length
        ticketLength = ticket.computeLength();
        kdcRepSeqLength += 1 + TLV.getNbBytes( ticketLength ) + ticketLength;

        // Compute the encrypted part
        encPartLength = encPart.computeLength();
        kdcRepSeqLength += 1 + TLV.getNbBytes( encPartLength ) + encPartLength;

        // compute the global size
        kdcRepLength = 1 + TLV.getNbBytes( kdcRepSeqLength ) + kdcRepSeqLength;

        return kdcRepLength;
    }


    /**
     * Encode the KDC-REP component
     * 
     * @param buffer The buffer containing the encoded result
     * @return The encoded component
     * @throws EncoderException If the encoding failed
     */
    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
    {
        if ( buffer == null )
        {
            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
        }

        // The KDC-REP SEQ Tag
        buffer.put( UniversalTag.SEQUENCE.getValue() );
        buffer.put( TLV.getBytes( kdcRepSeqLength ) );

        // The PVNO -----------------------------------------------------------
        // The tag
        buffer.put( ( byte ) KerberosConstants.KDC_REP_PVNO_TAG );
        buffer.put( TLV.getBytes( pvnoLength ) );

        // The value
        BerValue.encode( buffer, getProtocolVersionNumber() );

        // The MSG-TYPE if any ------------------------------------------------
        // The tag
        buffer.put( ( byte ) KerberosConstants.KDC_REP_MSG_TYPE_TAG );
        buffer.put( TLV.getBytes( msgTypeLength ) );

        // The value
        BerValue.encode( buffer, getMessageType().getValue() );

        // The PD-DATA if any -------------------------------------------------
        if ( paData.size() != 0 )
        {
            // The tag
            buffer.put( ( byte ) KerberosConstants.KDC_REP_PA_DATA_TAG );
            buffer.put( TLV.getBytes( paDataLength ) );

            // The sequence
            buffer.put( UniversalTag.SEQUENCE.getValue() );
            buffer.put( TLV.getBytes( paDataSeqLength ) );

            // The values
            for ( PaData paDataElem : paData )
            {
                paDataElem.encode( buffer );
            }
        }

        // The CREALM ---------------------------------------------------------
        // The tag
        buffer.put( ( byte ) KerberosConstants.KDC_REP_CREALM_TAG );
        buffer.put( TLV.getBytes( crealmLength ) );

        // The value
        buffer.put( UniversalTag.GENERAL_STRING.getValue() );
        buffer.put( TLV.getBytes( crealmBytes.length ) );
        buffer.put( crealmBytes );

        // The CNAME ----------------------------------------------------------
        // The tag
        buffer.put( ( byte ) KerberosConstants.KDC_REP_CNAME_TAG );
        buffer.put( TLV.getBytes( cnameLength ) );

        // The value
        cname.encode( buffer );

        // The TICKET ---------------------------------------------------------
        // The tag
        buffer.put( ( byte ) KerberosConstants.KDC_REP_TICKET_TAG );
        buffer.put( TLV.getBytes( ticketLength ) );

        // The value
        ticket.encode( buffer );

        // The ENC-PART -------------------------------------------------------
        // The tag
        buffer.put( ( byte ) KerberosConstants.KDC_REP_ENC_PART_TAG );
        buffer.put( TLV.getBytes( encPartLength ) );

        // The value
        encPart.encode( buffer );

        return buffer;
    }


    /**
     * Pretty print the instance
     */
    public String toString( String tabs )
    {
        StringBuilder sb = new StringBuilder();

        sb.append( tabs ).append(
            "\n>-------------------------------------------------------------------------------\n" );
        sb.append( tabs ).append( "KdcRep : " );

        if ( getMessageType() == KerberosMessageType.AS_REP )
        {
            sb.append( "AS-REP" ).append( '\n' );
        }
        else if ( getMessageType() == KerberosMessageType.TGS_REP )
        {
            sb.append( "TGS-REP" ).append( '\n' );
        }
        else
        {
            sb.append( "Unknown" ).append( '\n' );
        }

        sb.append( tabs ).append( "pvno : " ).append( getProtocolVersionNumber() ).append( '\n' );

        sb.append( tabs ).append( "msg-type : " ).append( getMessageType() ).append( '\n' );

        for ( PaData paDataElem : paData )
        {
            sb.append( tabs ).append( "padata : " ).append( paDataElem.toString( tabs + "    " ) ).append( '\n' );
        }

        sb.append( tabs ).append( "crealm : " ).append( crealm ).append( '\n' );
        sb.append( tabs ).append( "cname : " ).append( cname ).append( '\n' );
        sb.append( ticket.toString( tabs ) );
        sb.append( encPart.toString( tabs ) );
        sb.append( tabs ).append(
            "\n-------------------------------------------------------------------------------<\n" );

        return sb.toString();
    }


    /**
     * @see Object#toString()
     */
    public String toString()
    {
        return toString( "" );
    }
}
