/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: IdWriter.java
 * Written by: Dmitry Nadezhin.
 *
 * Copyright (c) 2003, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.database.id;

import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.tool.Tool;
import com.sun.electric.util.math.Orientation;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;

/**
 * Class to write trace of Snapshots to DataOutput byte sequence.
 */
public class IdWriter {

    /**
     * Constant to bypass limitations of java.io.DataOutputStream#writeUTF(String) .
     * DataOutputStream can't deal with 64kB String limit
     */
    public static final int MAX_STR_LENGTH = 16000;
    public final IdManager idManager;
    private final DataOutputStream out;
    private TechCounts[] techCounts = {}; // One entry is for Generic technology
    private int libCount;
    private int[] exportCounts = {};
    private HashMap<Variable.Key, Integer> varKeys = new HashMap<Variable.Key, Integer>();
    private HashMap<TextDescriptor, Integer> textDescriptors = new HashMap<TextDescriptor, Integer>();
    private HashMap<Tool, Integer> tools = new HashMap<Tool, Integer>();
    private HashMap<Orientation, Integer> orients = new HashMap<Orientation, Integer>();

    private static class TechCounts {

        int modCount;
        int layerCount;
        int arcCount;
        int[] portCounts = {};
    }

    /** Creates a new instance of SnapshotWriter */
    public IdWriter(IdManager idManager, DataOutputStream out) {
        this.idManager = idManager;
        this.out = out;
    }

    public void writeDiffs() throws IOException {
        TechId[] techIdsArray;
        LibId[] libIdsArray;
        CellId[] cellIdsArray;
        synchronized (idManager) {
            techIdsArray = idManager.techIds.toArray(TechId.NULL_ARRAY);
            libIdsArray = idManager.libIds.toArray(LibId.NULL_ARRAY);
            cellIdsArray = idManager.cellIds.toArray(CellId.NULL_ARRAY);
        }

        writeInt(techIdsArray.length);
        if (techIdsArray.length != techCounts.length) {
            TechCounts[] newTechCounts = new TechCounts[techIdsArray.length];
            System.arraycopy(techCounts, 0, newTechCounts, 0, techCounts.length);
            for (int techIndex = techCounts.length; techIndex < techIdsArray.length; techIndex++) {
                TechId techId = techIdsArray[techIndex];
                writeString(techId.techName);
                newTechCounts[techIndex] = new TechCounts();
            }
            techCounts = newTechCounts;
        }

        writeInt(libIdsArray.length);
        for (int libIndex = libCount; libIndex < libIdsArray.length; libIndex++) {
            LibId libId = libIdsArray[libIndex];
            writeString(libId.libName);
        }
        libCount = libIdsArray.length;

        writeInt(cellIdsArray.length);
        if (cellIdsArray.length != exportCounts.length) {
            int[] newExportCounts = new int[cellIdsArray.length];
            System.arraycopy(exportCounts, 0, newExportCounts, 0, exportCounts.length);
            for (int cellIndex = exportCounts.length; cellIndex < cellIdsArray.length; cellIndex++) {
                CellId cellId = cellIdsArray[cellIndex];
                writeLibId(cellId.libId);
                writeString(cellId.cellName.toString());
            }
            exportCounts = newExportCounts;
        }

        for (int techIndex = 0; techIndex < techIdsArray.length; techIndex++) {
            TechId techId = techIdsArray[techIndex];
            TechCounts techCount = techCounts[techIndex];
            int modCount = techId.modCount;
            if (modCount == techCount.modCount) {
                continue;
            }

            writeInt(techIndex);

            int numLayerIds = techId.numLayerIds();
            int numNewLayerIds = numLayerIds - techCount.layerCount;
            assert numNewLayerIds >= 0;
            writeInt(numNewLayerIds);
            for (int i = 0; i < numNewLayerIds; i++) {
                writeString(techId.getLayerId(techCount.layerCount + i).name);
            }
            techCount.layerCount = numLayerIds;

            int numArcProtoIds = techId.numArcProtoIds();
            int numNewArcProtoIds = numArcProtoIds - techCount.arcCount;
            assert numNewArcProtoIds >= 0;
            writeInt(numNewArcProtoIds);
            for (int i = 0; i < numNewArcProtoIds; i++) {
                writeString(techId.getArcProtoId(techCount.arcCount + i).name);
            }
            techCount.arcCount = numArcProtoIds;

            int numPrimitiveNodeIds = techId.numPrimitiveNodeIds();
            int numNewPrimitiveNodeIds = numPrimitiveNodeIds - techCount.portCounts.length;
            assert numNewPrimitiveNodeIds >= 0;
            writeInt(numNewPrimitiveNodeIds);
            if (numNewPrimitiveNodeIds > 0) {
                for (int i = 0; i < numNewPrimitiveNodeIds; i++) {
                    writeString(techId.getPrimitiveNodeId(techCount.portCounts.length + i).name);
                }
                int[] newPortCounts = new int[numPrimitiveNodeIds];
                System.arraycopy(techCount.portCounts, 0, newPortCounts, 0, techCount.portCounts.length);
                techCount.portCounts = newPortCounts;
            }

            for (int primIndex = 0; primIndex < numPrimitiveNodeIds; primIndex++) {
                PrimitiveNodeId primitiveNodeId = techId.getPrimitiveNodeId(primIndex);
                int numPrimitivePortIds = primitiveNodeId.numPrimitivePortIds();
                int numNewPrimitivePortIds = numPrimitivePortIds - techCount.portCounts[primIndex];
                assert numNewPrimitivePortIds >= 0;
                if (numNewPrimitivePortIds == 0) {
                    continue;
                }

                writeInt(primIndex);
                writeInt(numNewPrimitivePortIds);
                for (int i = 0; i < numNewPrimitivePortIds; i++) {
                    writeString(primitiveNodeId.getPortId(techCount.portCounts[primIndex] + i).externalId);
                }
                techCount.portCounts[primIndex] = numPrimitivePortIds;
            }
            writeInt(-1);

            techCount.modCount = modCount;
        }
        writeInt(-1);

        for (int cellIndex = 0; cellIndex < cellIdsArray.length; cellIndex++) {
            CellId cellId = cellIdsArray[cellIndex];
            int numExportIds = cellId.numExportIds();
            int exportCount = exportCounts[cellIndex];
            if (numExportIds != exportCount) {
                writeInt(cellIndex);
                int numNewExportIds = numExportIds - exportCount;
                assert numNewExportIds > 0;
                writeInt(numNewExportIds);
                for (int i = 0; i < numNewExportIds; i++) {
                    writeString(cellId.getPortId(exportCount + i).externalId);
                }
                exportCounts[cellIndex] = numExportIds;
            }
        }
        writeInt(-1);
    }

    /** Flushes this IdWriter */
    public void flush() throws IOException {
        out.flush();
    }

    /** Flushes this IdWriter */
    public void close() throws IOException {
        out.close();
    }

    /**
     * Writes boolean.
     * @param v boolean to write.
     */
    public void writeBoolean(boolean v) throws IOException {
        out.writeBoolean(v);
    }

    /**
     * Writes byte.
     * @param v byte to write.
     */
    public void writeByte(byte v) throws IOException {
        out.writeByte(v);
    }

    /**
     * Writes short.
     * @param v short to write.
     */
    public void writeShort(short v) throws IOException {
        out.writeShort(v);
    }

    /**
     * Writes integer.
     * @param v integer to write.
     */
    public void writeInt(int v) throws IOException {
        out.writeInt(v);
    }

    /**
     * Writes long.
     * @param v long to write.
     */
    public void writeLong(long v) throws IOException {
        out.writeLong(v);
    }

    /**
     * Writes float.
     * @param v float to write.
     */
    public void writeFloat(float v) throws IOException {
        out.writeFloat(v);
    }

    /**
     * Writes double.
     * @param v double to write.
     */
    public void writeDouble(double v) throws IOException {
        out.writeDouble(v);
    }

    /**
     * Writes bytes.
     * @param v bytes to write.
     */
    public void writeBytes(byte[] v) throws IOException {
        out.writeInt(v.length);
        out.write(v);
    }

    /**
     * Writes string.
     * @param s string to write.
     */
    public void writeString(String s) throws IOException {
    	// DataOutputStream can't deal with 64kB String limit
//    	byte[] buf = s.getBytes("utf-8");
//    	out.write(buf.length);
//    	out.write(buf);
		out.writeUTF(s);
    }

    /**
     * Writes variable key.
     * @param key variable key to write.
     */
    public void writeVariableKey(Variable.Key key) throws IOException {
        Integer i = varKeys.get(key);
        if (i != null) {
            out.writeInt(i.intValue());
        } else {
            i = varKeys.size();
            varKeys.put(key, i);
            out.writeInt(i.intValue());

            out.writeUTF((key.toString()));
        }
    }

    /**
     * Writes TextDescriptor.
     * @param td TextDescriptor to write.
     */
    public void writeTextDescriptor(TextDescriptor td) throws IOException {
        if (td == null) {
            out.writeInt(-1);
            return;
        }
        Integer i = textDescriptors.get(td);
        if (i != null) {
            out.writeInt(i.intValue());
        } else {
            i = textDescriptors.size();
            textDescriptors.put(td, i);
            out.writeInt(i.intValue());

            out.writeLong(td.lowLevelGet());
            out.writeInt(td.getColorIndex());
            out.writeBoolean(td.isDisplay());
            int face = td.getFace();
            String fontName = face != 0 ? TextDescriptor.ActiveFont.findActiveFont(face).getName() : "";
            out.writeUTF(fontName);
        }
    }

    /**
     * Writes Tool.
     * @param tool Tool to write.
     */
    public void writeTool(Tool tool) throws IOException {
        Integer i = tools.get(tool);
        if (i != null) {
            out.writeInt(i.intValue());
        } else {
            i = tools.size();
            tools.put(tool, i);
            out.writeInt(i.intValue());
            out.writeUTF(tool.getName());
        }
    }

    /**
     * Writes TechId.
     * @param techId TechId to write.
     */
    public void writeTechId(TechId techId) throws IOException {
        assert techId.idManager == idManager;
        out.writeInt(techId.techIndex);
    }

    /**
     * Writes ArcProtoId.
     * @param arcProtoId ArcProtoId to write.
     */
    public void writeArcProtoId(ArcProtoId arcProtoId) throws IOException {
        writeTechId(arcProtoId.techId);
        writeInt(arcProtoId.chronIndex);
    }

    /**
     * Writes LibId.
     * @param libId LibId to write.
     */
    public void writeLibId(LibId libId) throws IOException {
        out.writeInt(libId.libIndex);
    }

    /**
     * Writes NodeProtoId.
     * @param nodeProtoId NodeProtoId to write.
     */
    public void writeNodeProtoId(NodeProtoId nodeProtoId) throws IOException {
        if (nodeProtoId instanceof CellId) {
            CellId cellId = (CellId) nodeProtoId;
            assert cellId.idManager == idManager;
            out.writeInt(cellId.cellIndex);
            return;
        } else {
            PrimitiveNodeId pnId = (PrimitiveNodeId) nodeProtoId;
            out.writeInt(~pnId.chronIndex);
            writeTechId(pnId.techId);
        }
    }

    /**
     * Writes PortProtoId.
     * @param portProtoId PortProtoId to write.
     */
    public void writePortProtoId(PortProtoId portProtoId) throws IOException {
        writeNodeProtoId(portProtoId.getParentId());
        out.writeInt(portProtoId.getChronIndex());
    }

    /**
     * Writes node id.
     * @param nodeId node id to write.
     */
    public void writeNodeId(int nodeId) throws IOException {
        out.writeInt(nodeId);
    }

    /**
     * Writes arc id.
     * @param arcId arc id to write.
     */
    public void writeArcId(int arcId) throws IOException {
        out.writeInt(arcId);
    }

    /**
     * Writes Name key.
     * @param nameKey name key to write.
     */
    public void writeNameKey(Name nameKey) throws IOException {
        out.writeUTF(nameKey.toString());
    }

    /**
     * Writes Orientation.
     * @param orient Orientation.
     */
    public void writeOrientation(Orientation orient) throws IOException {
        Integer i = orients.get(orient);
        if (i != null) {
            out.writeInt(i.intValue());
        } else {
            i = orients.size();
            orients.put(orient, i);
            out.writeInt(i.intValue());

            out.writeShort(orient.getAngle());
            out.writeBoolean(orient.isXMirrored());
            out.writeBoolean(orient.isYMirrored());
        }
    }

    /**
     * Writes coordiante.
     * @param v gridCooridnate.
     */
    public void writeCoord(long v) throws IOException {
        out.writeLong(v);
    }

    /**
     * Writes EPoint.
     * @param p EPoint.
     */
    public void writePoint(EPoint p) throws IOException {
        writeCoord(p.getGridX());
        writeCoord(p.getGridY());
    }

    /**
     * Writes ERectangle.
     * @param r ERectangle.
     */
    public void writeRectangle(ERectangle r) throws IOException {
        writeCoord(r.getGridX());
        writeCoord(r.getGridY());
        writeCoord(r.getGridWidth());
        writeCoord(r.getGridHeight());
    }
}
