/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LibId.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.EObjectInputStream;
import com.sun.electric.database.EObjectOutputStream;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.text.CellName;

import java.io.IOException;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * The LibId immutable class identifies library independently of threads.
 * It differs from Library objects, which will be owned by threads in transactional database.
 * This class is thread-safe except inCurrentThread method.
 */
public final class LibId implements Serializable {

    /** Empty LibId array for initialization. */
    public static final LibId[] NULL_ARRAY = {};
    /** IdManager which owns this LibId. */
    public final IdManager idManager;
    /** Library name */
    public final String libName;
    /** Unique index of this lib in the database. */
    public final int libIndex;
    /** HashMap of CellIds in this library by their cell name. */
    private final HashMap<CellName, CellId> cellIdsByCellName = new HashMap<CellName, CellId>();

    /**
     * LibId constructor.
     */
    LibId(IdManager idManager, String libName, int libIndex) {
        if (libName == null) {
            throw new NullPointerException();
        }
        if (legalLibraryName(libName) != libName) {
            throw new IllegalArgumentException(libName);
        }
        this.idManager = idManager;
        this.libName = libName;
        this.libIndex = libIndex;
    }

    private Object writeReplace() {
        return new LibIdKey(this);
    }

    private static class LibIdKey extends EObjectInputStream.Key<LibId> {

        public LibIdKey() {
        }

        private LibIdKey(LibId libId) {
            super(libId);
        }

        @Override
        public void writeExternal(EObjectOutputStream out, LibId libId) throws IOException {
            if (libId.idManager != out.getIdManager()) {
                throw new NotSerializableException(libId + " from other IdManager");
            }
            out.writeInt(libId.libIndex);
        }

        @Override
        public LibId readExternal(EObjectInputStream in) throws IOException, ClassNotFoundException {
            int libIndex = in.readInt();
            return in.getIdManager().getLibId(libIndex);
        }
    }

    /**
     * Returns new CellId with cellIndex unique in this IdManager.
     * @return new CellId.
     */
    public CellId newCellId(CellName cellName) {
        return idManager.newCellId(this, cellName);
    }

    /**
     * Method to return the Library representing LibId in the specified EDatabase.
     * @param database EDatabase where to get from.
     * @return the Library representing LibId in the specified database.
     * This method is not properly synchronized.
     */
    public Library inDatabase(EDatabase database) {
        return database.getLib(this);
    }

    /**
     * Returns a printable version of this LibId.
     * @return a printable version of this LibId.
     */
    public String toString() {
        return libName;
    }

    CellId getCellId(CellName cellName) {
        return cellIdsByCellName.get(cellName);
    }

    void putCellId(CellId cellId) {
        cellIdsByCellName.put(cellId.cellName, cellId);
    }

    /**
     * Checks invariants in this LibId.
     * @exception AssertionError if invariants are not valid
     */
    void check() {
        assert this == idManager.getLibId(libIndex);
        assert libName != null && legalLibraryName(libName) == libName;
        for (Map.Entry<CellName, CellId> e : cellIdsByCellName.entrySet()) {
            CellId cellId = e.getValue();
            assert cellId.libId == this;
            assert cellId.cellName == e.getKey();
            assert idManager.getCellId(cellId.cellIndex) == cellId;
        }
    }

    /**
     * Checks if character is not valid as part of a library name
     * @param ch character to check. Invalid characters are replaced by '-'.
     * @return true if character is invalid.
     */
    private static boolean isInvalidLibraryCharacter(char ch)
    {
    	return Character.isWhitespace(ch) || ch == ':' || ch == '/';
    }
    
    /**
     * Checks that string is legal library name.
     * If not, tries to convert string to legal library name.
     * @param libName specified library name.
     * @return legal library name obtained from specified library name,
     *   or null if specified name is null or empty.
     */
    public static String legalLibraryName(String libName) {
        if (libName == null || libName.length() == 0) {
            return null;
        }
        int i = 0;
        for (; i < libName.length(); i++) {
            if (isInvalidLibraryCharacter(libName.charAt(i))) {
                break;
            }
        }
        if (i == libName.length()) {
            return libName;
        }

        char[] chars = libName.toCharArray();
        for (; i < libName.length(); i++) {
            char ch = chars[i];
            chars[i] = isInvalidLibraryCharacter(ch) ? '-' : ch;
        }
        return new String(chars);
    }
}
