package net.i2p.data.i2np;
/*
 * free (adj.): unencumbered; not under the control of others
 * Written by jrandom in 2003 and released into the public domain
 * with no warranty of any kind, either expressed or implied.
 * It probably won't make your computer catch on fire, or eat
 * your children, but it might.  Use at your own risk.
 *
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

import net.i2p.I2PAppContext;
import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.util.Log;

/**
 * Contains one deliverable message encrypted to a router along with instructions
 * and a certificate 'paying for' the delivery.
 *
 * Note that certificates are always the null certificate at this time, others are unimplemented.
 *
 * @author jrandom
 */
public class GarlicClove extends DataStructureImpl {

    //private final Log _log;
    private static final long serialVersionUID = 1L;
    private transient final I2PAppContext _context;
    private DeliveryInstructions _instructions;
    private I2NPMessage _msg;
    private long _cloveId;
    private Date _expiration;
    private Certificate _certificate;
    
    public GarlicClove(I2PAppContext context) {
        _context = context;
        //_log = context.logManager().getLog(GarlicClove.class);
        _cloveId = -1;
    }
    
    public DeliveryInstructions getInstructions() { return _instructions; }
    public void setInstructions(DeliveryInstructions instr) { _instructions = instr; }
    public I2NPMessage getData() { return _msg; }
    public void setData(I2NPMessage msg) { _msg = msg; }
    public long getCloveId() { return _cloveId; }
    public void setCloveId(long id) { _cloveId = id; }
    public Date getExpiration() { return _expiration; }
    public void setExpiration(Date exp) { _expiration = exp; }
    public Certificate getCertificate() { return _certificate; }
    public void setCertificate(Certificate cert) { _certificate = cert; }
    
    /**
     *  @deprecated unused, use byte array method to avoid copying
     *  @throws UnsupportedOperationException always
     */
    public void readBytes(InputStream in) throws DataFormatException, IOException {
        throw new UnsupportedOperationException();
/****
        _instructions = new DeliveryInstructions();
        _instructions.readBytes(in);
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("Read instructions: " + _instructions);
        try {
            _msg = _handler.readMessage(in);
        } catch (I2NPMessageException ime) {
            throw new DataFormatException("Unable to read the message from a garlic clove", ime);
        }
        _cloveId = DataHelper.readLong(in, 4);
        _expiration = DataHelper.readDate(in);
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("CloveID read: " + _cloveId + " expiration read: " + _expiration);
        //_certificate = new Certificate();
        //_certificate.readBytes(in);
        _certificate = Certificate.create(in);
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("Read cert: " + _certificate);
****/
    }

    /**
     *
     */
    public int readBytes(byte source[], int offset) throws DataFormatException {
        int cur = offset;
        _instructions = new DeliveryInstructions();
        cur += _instructions.readBytes(source, cur);
        //if (_log.shouldLog(Log.DEBUG))
        //    _log.debug("Read instructions: " + _instructions);
        try {
            I2NPMessageHandler handler = new I2NPMessageHandler(_context);
            cur += handler.readMessage(source, cur);
            _msg = handler.lastRead();
        } catch (I2NPMessageException ime) {
            throw new DataFormatException("Unable to read the message from a garlic clove", ime);
        }
        _cloveId = DataHelper.fromLong(source, cur, 4);
        cur += 4;
        _expiration = DataHelper.fromDate(source, cur);
        cur += DataHelper.DATE_LENGTH;
        //if (_log.shouldLog(Log.DEBUG))
        //    _log.debug("CloveID read: " + _cloveId + " expiration read: " + _expiration);
        //_certificate = new Certificate();
        //cur += _certificate.readBytes(source, cur);
        _certificate = Certificate.create(source, cur);
        cur += _certificate.size();
        //if (_log.shouldLog(Log.DEBUG))
        //    _log.debug("Read cert: " + _certificate);
        return cur - offset;
    }

    /**
     *  @deprecated unused, use byte array method to avoid copying
     *  @throws UnsupportedOperationException always
     */
    public void writeBytes(OutputStream out) throws DataFormatException, IOException {
        throw new UnsupportedOperationException();
/****
        StringBuilder error = null; 
        if (_instructions == null) {
            if (error == null) error = new StringBuilder();
            error.append("No instructions ");
        }
        if (_msg == null) {
            if (error == null) error = new StringBuilder();
            error.append("No message ");
        }
        if (_cloveId < 0) {
            if (error == null) error = new StringBuilder();
            error.append("CloveID < 0 [").append(_cloveId).append("] ");
        }
        if (_expiration == null) {
            if (error == null) error = new StringBuilder();
            error.append("Expiration is null ");
        }
        if (_certificate == null) {
            if (error == null) error = new StringBuilder();
            error.append("Certificate is null ");
        }
        
        if ( (error != null) && (error.length() > 0) )
            throw new DataFormatException(error.toString());

        _instructions.writeBytes(out);

        if (_log.shouldLog(Log.DEBUG))
            _log.debug("Wrote instructions: " + _instructions);
        try {
            byte m[] = _msg.toByteArray();
            if (m == null)
                throw new RuntimeException("foo, returned null");
            if (m.length <= 0)
                throw new RuntimeException("foo, returned 0 length");
            out.write(m);
        } catch (Exception e) {
            throw new DataFormatException("Unable to write the clove: " + _msg + " to " + out, e);
        }
        DataHelper.writeLong(out, 4, _cloveId);
        DataHelper.writeDate(out, _expiration);
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("CloveID written: " + _cloveId + " expiration written: " 
                       + _expiration);
        _certificate.writeBytes(out);
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("Written cert: " + _certificate);
****/
    }

    /**
     *
     */
    @Override
    public byte[] toByteArray() {
        byte rv[] = new byte[estimateSize()];
        int offset = 0;
        offset += _instructions.writeBytes(rv, offset);
        //if (_log.shouldLog(Log.DEBUG))
        //    _log.debug("Wrote instructions: " + _instructions);
        //offset += _msg.toByteArray(rv);
        try {
            byte m[] = _msg.toByteArray();
            System.arraycopy(m, 0, rv, offset, m.length);
            offset += m.length;
        } catch (Exception e) { throw new RuntimeException("Unable to write: " + _msg + ": " + e.getMessage()); }
        DataHelper.toLong(rv, offset, 4, _cloveId);
        offset += 4;
        DataHelper.toDate(rv, offset, _expiration.getTime());
        offset += DataHelper.DATE_LENGTH;
        offset += _certificate.writeBytes(rv, offset);
        if (offset != rv.length) {
            Log log = I2PAppContext.getGlobalContext().logManager().getLog(GarlicClove.class);
            log.error("Clove offset: " + offset + " but estimated length: " + rv.length);
        }
        return rv;
    }
    
    public int estimateSize() {
        return _instructions.getSize()
               + _msg.getMessageSize() 
               + 4 // cloveId
               + DataHelper.DATE_LENGTH
               + _certificate.size(); // certificate
    }
    
    @Override
    public boolean equals(Object obj) {
        if ( (obj == null) || !(obj instanceof GarlicClove))
            return false;
        GarlicClove clove = (GarlicClove)obj;
        return DataHelper.eq(_certificate, clove._certificate) &&
               _cloveId == clove._cloveId &&
               DataHelper.eq(_msg, clove._msg) &&
               DataHelper.eq(_expiration, clove._expiration) &&
               DataHelper.eq(_instructions,  clove._instructions);
    }
    
    @Override
    public int hashCode() {
        return DataHelper.hashCode(_certificate) ^
               (int) _cloveId ^
               DataHelper.hashCode(_msg) ^
               DataHelper.hashCode(_expiration) ^
               DataHelper.hashCode(_instructions);
    }
    
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(128);
        buf.append("[GarlicClove: ");
        buf.append("\n\tInstructions: ").append(_instructions);
        buf.append("\n\tCertificate: ").append(_certificate);
        buf.append("\n\tClove ID: ").append(_cloveId);
        buf.append("\n\tExpiration: ").append(_expiration);
        buf.append("\n\tData: ").append(_msg);
        buf.append("]");
        return buf.toString();
    }
}
