/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.keys;

import java.security.MessageDigest;
import java.util.Arrays;

import freenet.crypt.SHA256;
import freenet.support.Fields;

/**
 * @author amphibian
 * 
 * CHK plus data. When fed a ClientCHK, can decode into the original
 * data for a client.
 */
public class CHKBlock implements KeyBlock {

    final byte[] data;
    final byte[] headers;
    final short hashIdentifier;
    final NodeCHK chk;
    final int hashCode;
    public static final int MAX_LENGTH_BEFORE_COMPRESSION = Integer.MAX_VALUE;
    public static final int TOTAL_HEADERS_LENGTH = 36;
    public static final int DATA_LENGTH = 32768;
    /* Maximum length of compressed payload */
	public static final int MAX_COMPRESSED_DATA_LENGTH = DATA_LENGTH - 4;
    
    @Override
	public String toString() {
        return super.toString()+": chk="+chk;
    }
    
    /**
     * @return The header for this key. DO NOT MODIFY THIS DATA!
     */
    public byte[] getHeaders() {
        return headers;
    }

    /**
     * @return The actual data for this key. DO NOT MODIFY THIS DATA!
     */
    public byte[] getData() {
        return data;
    }
    
    public static CHKBlock construct(byte[] data, byte[] header, byte cryptoAlgorithm) throws CHKVerifyException {
    	return new CHKBlock(data, header, null, true, cryptoAlgorithm);
     }
    
    public CHKBlock(byte[] data2, byte[] header2, NodeCHK key) throws CHKVerifyException {
    	this(data2, header2, key, key.cryptoAlgorithm);
    }

    public CHKBlock(byte[] data2, byte[] header2, NodeCHK key, byte cryptoAlgorithm) throws CHKVerifyException {
        this(data2, header2, key, true, cryptoAlgorithm);
    }
    
    public CHKBlock(byte[] data2, byte[] header2, NodeCHK key, boolean verify, byte cryptoAlgorithm) throws CHKVerifyException {
        data = data2;
        headers = header2;
        if(headers.length != TOTAL_HEADERS_LENGTH)
        	throw new IllegalArgumentException("Wrong length: "+headers.length+" should be "+TOTAL_HEADERS_LENGTH);
        hashIdentifier = (short)(((headers[0] & 0xff) << 8) + (headers[1] & 0xff));
//        Logger.debug(CHKBlock.class, "Data length: "+data.length+", header length: "+header.length);
        if((key != null) && !verify) {
        	this.chk = key;
            hashCode = key.hashCode() ^ Fields.hashCode(data) ^ Fields.hashCode(headers) ^ cryptoAlgorithm;
        	return;
        }
        
        // Minimal verification
        // Check the hash
        if(hashIdentifier != HASH_SHA256)
            throw new CHKVerifyException("Hash not SHA-256");
        MessageDigest md = SHA256.getMessageDigest();
        
        md.update(headers);
        md.update(data);
        byte[] hash = md.digest();
        SHA256.returnMessageDigest(md);
        if(key == null) {
        	chk = new NodeCHK(hash, cryptoAlgorithm);
        } else {
        	chk = key;
            byte[] check = chk.routingKey;
            if(!java.util.Arrays.equals(hash, check)) {
                throw new CHKVerifyException("Hash does not verify");
            }
            // Otherwise it checks out
        }
        hashCode = chk.hashCode() ^ Fields.hashCode(data) ^ Fields.hashCode(headers) ^ cryptoAlgorithm;
    }

	@Override
	public NodeCHK getKey() {
        return chk;
    }

	@Override
	public byte[] getRawHeaders() {
		return headers;
	}

	@Override
	public byte[] getRawData() {
		return data;
	}

	@Override
	public byte[] getPubkeyBytes() {
		return null;
	}

	@Override
	public byte[] getFullKey() {
		return getKey().getFullKey();
	}

	@Override
	public byte[] getRoutingKey() {
		return getKey().getRoutingKey();
	}
	
	@Override
	public int hashCode() {
		return hashCode;
	}
	
	@Override
	public boolean equals(Object o) {
		if(!(o instanceof CHKBlock)) return false;
		CHKBlock block = (CHKBlock) o;
		if(!chk.equals(block.chk)) return false;
		if(!Arrays.equals(data, block.data)) return false;
		if(!Arrays.equals(headers, block.headers)) return false;
		if(hashIdentifier != block.hashIdentifier) return false;
		return true;
	}
	
}
