/*
 *******************************************************************************
 * Copyright (C) 1996-2012, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.dev.util;

import java.io.DataOutput;
import java.io.IOException;
import java.io.ObjectOutput;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;

import com.ibm.icu.text.UTF16;

/**
 * Simple data output compressor. Nothing fancy, but much smaller footprint for ints and many strings.
 */
public final class DataOutputCompressor implements ObjectOutput {
    static final boolean SHOW = false;

    private ObjectOutput dataOutput;

    public DataOutputCompressor(ObjectOutput dataOutput) {
        this.dataOutput = dataOutput;
    }

    public DataOutput getDataOutput() {
        return dataOutput;
    }

    public void setDataOutput(ObjectOutput dataOutput) {
        this.dataOutput = dataOutput;
    }

    public void write(byte[] b) throws IOException {
        dataOutput.write(b);
    }

    public void write(byte[] b, int off, int len) throws IOException {
        dataOutput.write(b, off, len);
    }

    public void write(int b) throws IOException {
        dataOutput.write(b);
    }

    public void writeBoolean(boolean v) throws IOException {
        dataOutput.writeBoolean(v);
    }

    public void writeByte(int v) throws IOException {
        dataOutput.writeByte(v);
    }

    public void writeBytes(String s) throws IOException {
        dataOutput.writeBytes(s);
    }

    public void writeDouble(double v) throws IOException {
        dataOutput.writeDouble(v);
    }

    public void writeFloat(float v) throws IOException {
        dataOutput.writeFloat(v);
    }

    public void close() throws IOException {
        dataOutput.close();
    }
    public void flush() throws IOException {
        dataOutput.flush();
    }
    public String toString() {
        return dataOutput.toString();
    }
    public void writeObject(Object obj) throws IOException {
        dataOutput.writeObject(obj);
    }
    // ==== New Routines ====

    public void writeChar(int v) throws IOException {
        writeULong(v);
    }

    public void writeShort(int v) throws IOException {
        writeLong(v);
    }

    public void writeUShort(int v) throws IOException {
        writeULong(v);
    }

    public void writeInt(int v) throws IOException {
        writeLong(v);
    }

    public void writeUInt(int v) throws IOException {
        writeULong(v);
    }

    public void writeUTF(String str) throws IOException {
        writeULong(UTF16.countCodePoint(str));
        writeChars(str);
    }

    public void writeChars(String s) throws IOException {
        int cp = 0;
        for (int i = 0; i < s.length(); i += UTF16.getCharCount(cp)) {
            cp = UTF16.charAt(s, i);
            writeULong(cp);
        }
    }

    public void writeLong(long v) throws IOException {
        long flag = 0; // put sign bit at the bottom, and invert
        if (v < 0) {
            v = ~v;
            flag = 1;
        }
        v <<= 1;
        v |= flag;
        while (true) {
            if ((v & ~0x7FL) == 0) {
                dataOutput.writeByte((byte) v);
                break;
            }
            dataOutput.writeByte((byte) (0x80L | v));
            v >>>= 7;
        }
    }

    public void writeULong(long v) throws IOException {
        while (true) { // write sequence of 7 bits, with top bit = 1 for continuation
            if ((v & ~0x7FL) == 0) {
                dataOutput.writeByte((byte) v);
                break;
            }
            dataOutput.writeByte((byte) (0x80L | v));
            v >>>= 7;
        }
    }

    /**
     * 
     */
    public void writeStringSet(SortedSet c, Map object_index) throws IOException {
        if (SHOW) System.out.println("writeStringSet");
        writeUInt(c.size());
        int i = 0;
        object_index.put(null, new Integer(i++));
        WritePool trailingPool = new WritePool();
        String lastString = "";
        for (Iterator it = c.iterator(); it.hasNext();) {
            String s = (String) it.next();
            object_index.put(s, new Integer(i++));
            int common = UnicodeMap.findCommonPrefix(lastString, s); // runlength encode
            lastString = s;
            String piece = s.substring(common);
            if (SHOW) System.out.println(common);
            common <<= 1;
            int inPool = trailingPool.getIndex(piece);
            if (inPool < 0) {
                writeUInt(common);
                writeUTF(piece);
                trailingPool.put(piece);
            } else {
                writeUInt(common | 1);
                writeUInt(inPool);
                if (SHOW) System.out.println("\t" + inPool);
            }
            if (SHOW) System.out.println("\t\t" + lastString);
        }
    }
    
    public static class WritePool {
        private Map trailingPool = new HashMap();
        private int poolCount = 0;
        public int getIndex(Object o) {
            Integer inPool = (Integer) trailingPool.get(o);
            if (inPool == null) return -1;
            return inPool.intValue();
        }
        public void put(Object o) {
            trailingPool.put(o, new Integer(poolCount++));
        }
    }

    /**
     * @throws IOException
     * 
     */
    public void writeCollection(Collection c, Map object_index) throws IOException {
        writeUInt(c.size());
        int i = 0;
        object_index.put(null, new Integer(i++));
        for (Iterator it = c.iterator(); it.hasNext();) {
            Object s = it.next();
            dataOutput.writeObject(s);
            if (object_index != null) object_index.put(s, new Integer(i++));
        }
    }
}
