/**
 * LhaWithEntropyOutputStream.java
 *
 * Copyright (c) 2006 Ying-Chun Liu (PaulLiu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  1. Redistributions of source code must retain the copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND
 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package org.jlhafrontend;

import java.io.*;
import java.util.*;
import jp.gr.java_conf.dangan.util.lha.*;

/**
 * This class adds the function to get entropy when output
 *
 */

public class LhaWithEntropyOutputStream extends jp.gr.java_conf.dangan.util.lha.LhaOutputStream {

    /**
     *  Total Data Size
     */
    private long[] distributionTable = null;

    /**
     * Creates an LhaOutputStream, with getEntropy() support
     *
     * @param out The OutputStream of the output archived file
     */
    public LhaWithEntropyOutputStream(OutputStream out) {
	super(out);
    }
    /**
     * Creates an LhaOutputStream, with getEntropy() support
     *
     * @param out The OutputStream of the output archived file
     * @param property The properties of the new archived file
     */
    public LhaWithEntropyOutputStream(OutputStream out,Properties property) {
	super(out,property);
    }
    /**
     * Creates an LhaOutputStream, with getEntropy() support
     *
     * @param out The OutputStream of the output archived file
     * @param file The RandomAccessFile can be read out
     */
    public LhaWithEntropyOutputStream(OutputStream out, RandomAccessFile file) {
	super(out,file);
    }
    /**
     * Creates an LhaOutputStream, with getEntropy() support
     *
     * @param out The OutputStream of the output archived file
     * @param file The RandomAccessFile can be read out
     * @param property The properties of the new archived file
     */
    public LhaWithEntropyOutputStream(OutputStream out, RandomAccessFile file, Properties property) {
	super(out,file,property);
    }


    /**
     * Put a new entry into the archive file.
     * It will use some information from header to see if it needs compresson.
     *
     * @param header The LhaHeader contained the file information
     */
    public void putNextEntry(LhaHeader header) throws IOException {
        resetDistributionTable();
	super.putNextEntry(header);
    }

    /**
     * Put a new entry into the archive file.
     * The data you provided is already compressed, 
     * so it will not be compressed again
     *
     * @param header The LhaHeader contained the file information
     */
    public void putNextEntryAlreadyCompressed(LhaHeader header) throws IOException {
        resetDistributionTable();
	super.putNextEntryAlreadyCompressed(header);
    }

    /**
     * Put a new entry into the archive file.
     * The data you provided is not compressed, 
     * so it will be compressed when you write().
     *
     * @param header The LhaHeader contained the file information
     */
    public void putNextEntryNotYetCompressed(LhaHeader header) throws IOException {
        resetDistributionTable();
	super.putNextEntryNotYetCompressed(header);
    }

    /**
     *
     * Calculate entropy length
     *
     * @param ent The data distribution table
     * @return the entropy data length
     */
    private long calEntropy(long[] ent) {
        long N=0,ret=0;
        double Hs=0.0,dN,dA,dRet;
        int i;
	if (ent==null) {
	    return 0;
	}
        for (i=0 ; i<ent.length ; i++) {
            N += ent[i];
        }
        if (N<=0) {
            return 0;
        }
        dN = (double)N;
        for (i=0 ; i<ent.length ; i++) {
          if (ent[i]==0) {
              continue;
          }
          dA = ((double)(ent[i]));
          Hs += (dA/dN)*(Math.log(dN)-Math.log(dA))/(Math.log(256));
        }
        dRet = N*(Hs);
        ret = (long)dRet;
        return ret;
    }

    /**
     *
     * Reset distributionTable
     *
     */
    public void resetDistributionTable() {
        int i;
        distributionTable = new long[256];
        for (i=0 ; i<distributionTable.length; i++) {
          distributionTable[i]=0;
        }
    }

    /**
     *
     * Update distributionTable by the input data
     *
     * @param data The input data 
     */
    public void updateDistributionTable(int data) {
        int c;
	c = data;
	while (c<0) {
	  c+=256;
	}
	c = c%256;
	if (c < distributionTable.length) {
	  distributionTable[c]++;
	}
    }

    /**
     *
     * Get current entropy length
     *
     * @return the entropy data length
     */
    public long getEntropyLength() {
	if (distributionTable!=null) {
	    return calEntropy(distributionTable);
	} 
	return 0;
    }

    /**
     *
     * Write the data into file.
     *
     * @param buffer the data
     */
    public void write(byte[] buffer) throws IOException {
	int i;
	for (i=0 ; i<buffer.length ; i++) {
	    updateDistributionTable(buffer[i]);
	}
	super.write(buffer);
    }

    /**
     *
     * Write the data into file. Begins at index and write length bytes.
     *
     * @param buffer the data
     * @param index the start position
     * @param length the length to write
     */
    public void write(byte[] buffer,int index,int length) throws IOException {
	int i;
	for (i=index ; i<index+length && i<buffer.length ; i++) {
	    updateDistributionTable(buffer[i]);
	}
	super.write(buffer,index,length);
    }

    /**
     *
     * Write a byte into file.
     *
     * @param data the byte
     */
    public void write(int data) throws IOException {
	updateDistributionTable(data);
	super.write(data);
    }

}
