package org.bouncycastle.openpgp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.bouncycastle.bcpg.SignaturePacket;
import org.bouncycastle.bcpg.UserAttributeSubpacket;

abstract class PGPDefaultSignatureGenerator
{
    protected byte lastb;
    protected OutputStream sigOut;
    protected int sigType;
    protected final int version;

    public PGPDefaultSignatureGenerator(int version)
    {
        this.version = version;
    }
    
    public void update(
        byte b)
    {
        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
        {
            if (b == '\r')
            {
                byteUpdate((byte)'\r');
                byteUpdate((byte)'\n');
            }
            else if (b == '\n')
            {
                if (lastb != '\r')
                {
                    byteUpdate((byte)'\r');
                    byteUpdate((byte)'\n');
                }
            }
            else
            {
                byteUpdate(b);
            }

            lastb = b;
        }
        else
        {
            byteUpdate(b);
        }
    }

    public void update(
        byte[] b)
    {
        this.update(b, 0, b.length);
    }

    public void update(
        byte[]  b,
        int     off,
        int     len)
    {
        if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
        {
            int finish = off + len;

            for (int i = off; i != finish; i++)
            {
                this.update(b[i]);
            }
        }
        else
        {
            blockUpdate(b, off, len);
        }
    }

    private void byteUpdate(byte b)
    {
        try
        {
            sigOut.write(b);
        }
        catch (IOException e)
        {
            throw new PGPRuntimeOperationException(e.getMessage(), e);
        }
    }

    protected void blockUpdate(byte[] block, int off, int len)
    {
        try
        {
            sigOut.write(block, off, len);
        }
        catch (IOException e)
        {
            throw new PGPRuntimeOperationException("unable to update signature: " + e.getMessage(), e);
        }
    }

    protected void updateWithIdData(int header, byte[] idBytes)
    {
        this.update((byte)header);
        this.update((byte)(idBytes.length >> 24));
        this.update((byte)(idBytes.length >> 16));
        this.update((byte)(idBytes.length >> 8));
        this.update((byte)(idBytes.length));
        this.update(idBytes);
    }

    protected void updateWithPublicKey(PGPPublicKey key)
        throws PGPException
    {
        byte[] keyBytes = getEncodedPublicKey(key);

        if (version == SignaturePacket.VERSION_4)
        {
            this.update((byte) 0x99);
            this.update((byte) (keyBytes.length >> 8));
            this.update((byte) (keyBytes.length));
        }
        else if (version == SignaturePacket.VERSION_5)
        {
            this.update((byte) 0x9A);
            this.update((byte) (keyBytes.length >> 24));
            this.update((byte) (keyBytes.length >> 16));
            this.update((byte) (keyBytes.length >> 8));
            this.update((byte) (keyBytes.length));
        }
        else if (version == SignaturePacket.VERSION_6)
        {
            this.update((byte) 0x9B);
            this.update((byte) (keyBytes.length >> 24));
            this.update((byte) (keyBytes.length >> 16));
            this.update((byte) (keyBytes.length >> 8));
            this.update((byte) (keyBytes.length));
        }
        this.update(keyBytes);
    }

    private byte[] getEncodedPublicKey(
        PGPPublicKey pubKey)
        throws PGPException
    {
        byte[] keyBytes;

        try
        {
            keyBytes = pubKey.publicPk.getEncodedContents();
        }
        catch (IOException e)
        {
            throw new PGPException("exception preparing key.", e);
        }

        return keyBytes;
    }

    protected void getAttributesHash(PGPUserAttributeSubpacketVector userAttributes)
        throws PGPException
    {
        //
        // hash in the attributes
        //
        try
        {
            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
            UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
            for (int i = 0; i != packets.length; i++)
            {
                packets[i].encode(bOut);
            }
            updateWithIdData(0xd1, bOut.toByteArray());
        }
        catch (IOException e)
        {
            throw new PGPException("cannot encode subpacket array", e);
        }
    }
}
