/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Library.java
 *
 * 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.hierarchy;

import com.sun.electric.database.CellTree;
import com.sun.electric.database.EObjectInputStream;
import com.sun.electric.database.EObjectOutputStream;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.IdMapper;
import com.sun.electric.database.ImmutableLibrary;
import com.sun.electric.database.LibraryBackup;
import com.sun.electric.database.Snapshot;
import com.sun.electric.database.constraint.Constraints;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.id.LibId;
import com.sun.electric.database.text.CellName;
import com.sun.electric.database.text.Pref;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.util.TextUtils;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.NotSerializableException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.prefs.Preferences;

/**
 * A Library represents a collection of Cells.
 * To find of a Library, you use Electric.getLibrary(String name).
 * Use Electric.newLibrary(String name) to create a new library, or
 * Electric.getCurrent() to get the current Library.
 * <p>
 * Once you have a Library, you can create a new Cell in it, find an existing
 * Cell, get an Enumeration of all Cells, or find the Cell that the user
 * is currently editing.
 */
public class Library extends ElectricObject implements Comparable<Library> {

    /** key of Variable holding font associations. */
    public static final Variable.Key FONT_ASSOCIATIONS = Variable.newKey("LIB_font_associations");
    // ------------------------ private data ------------------------------
//	/** library has changed significantly */				private static final int LIBCHANGEDMAJOR =           01;
//	/** set to see library in explorer */					private static final int OPENINEXPLORER =            02;
    /** set if library came from disk */
    private static final int READFROMDISK = 04;
//	/** internal units in library (see INTERNALUNITS) */	private static final int LIBUNITS =                 070;
//	/** right shift for LIBUNITS */							private static final int LIBUNITSSH =                 3;
//	/** library has changed insignificantly */				private static final int LIBCHANGEDMINOR =         0100;
    /** library is "hidden" (clipboard library) */
    public static final int HIDDENLIBRARY = 0200;
//	/** library is unwanted (used during input) */			private static final int UNWANTEDLIB =             0400;
    /** Database to which this Library belongs. */
    private final EDatabase database;
    /** persistent data of this Library. */
    private ImmutableLibrary d;
    /** list of referenced libs */
    private final List<Library> referencedLibs = new ArrayList<Library>();
    /** Last backup of this Library */
    LibraryBackup backup;
    /** list of Cells in this library */
    final TreeMap<CellName, Cell> cells = new TreeMap<CellName, Cell>();

    // ----------------- private and protected methods --------------------
    /**
     * The constructor is never called.  Use the factor method "newInst" instead.
     */
    Library(EDatabase database, ImmutableLibrary d) {
        if (database == null) {
            throw new NullPointerException();
        }
        this.database = database;
        this.d = d;
        backup = new LibraryBackup(d, true, LibId.NULL_ARRAY);
    }

    /**
     * This method is a factory to create new libraries.
     * A Library has both a name and a file.
     * @param libName the name of the library (for example, "gates").
     * Library names must be unique, and they must not contain spaces or colons.
     * @param libFile the URL to the disk file (for example "/home/strubin/gates.elib").
     * If the Library is being created, the libFile can be null.
     * If the Library file is given and it points to an existing file, then the I/O system
     * can be told to read that file and populate the Library.
     * @return the Library object.
     */
    public static Library newInst(String libName, URL libFile) {
        // make sure the name is legal
        String legalName = LibId.legalLibraryName(libName);
        if (legalName == null) {
            return null;
        }
        if (legalName != libName) {
            System.out.println("Warning: library '" + libName + "' renamed to '" + legalName + "'");
        }

        // see if the library name already exists
        EDatabase database = EDatabase.currentDatabase();
        Library existingLibrary = database.findLibrary(legalName);
        if (existingLibrary != null) {
            System.out.println("Error: library '" + legalName + "' already exists");
            return existingLibrary;
        }

        // create the library
        return newInst(database, database.getIdManager().newLibId(legalName), libFile);
    }

    /**
     * This method is a factory to create new libraries.
     * A Library has both a name and a file.
     * @param libId ID of new Library.
     * @param libFile the URL to the disk file (for example "/home/strubin/gates.elib").
     * If the Library is being created, the libFile can be null.
     * If the Library file is given and it points to an existing file, then the I/O system
     * can be told to read that file and populate the Library.
     * @return the Library object.
     * @throws NullPointerException if libId or legalName is null.
     */
    private static Library newInst(EDatabase edb, LibId libId, URL libFile) {
        // create the library
        ImmutableLibrary d = ImmutableLibrary.newInst(libId, libFile, null);
        Library lib = new Library(edb, d);

        // add the library to the global list
        edb.addLib(lib);

        // always broadcast library changes
//        Undo.setNextChangeQuiet(false);
        edb.unfreshSnapshot();
        Constraints.getCurrent().newObject(lib);
        return lib;

    }

    /**
     * Method for serialization.
     */
    private Object writeReplace() {
        return new LibraryKey(this);
    }

    private static class LibraryKey extends EObjectInputStream.Key<Library> {

        public LibraryKey() {
        }

        private LibraryKey(Library lib) {
            super(lib);
        }

        @Override
        public void writeExternal(EObjectOutputStream out, Library lib) throws IOException {
            if (lib.getDatabase() != out.getDatabase() || !lib.isLinked()) {
                throw new NotSerializableException(lib + " not linked");
            }
            out.writeObject(lib.getId());
        }

        @Override
        public Library readExternal(EObjectInputStream in) throws IOException, ClassNotFoundException {
            LibId libId = (LibId) in.readObject();
            Library lib = libId.inDatabase(in.getDatabase());
            if (lib == null) {
                throw new InvalidObjectException(libId + " not linked");
            }
            return lib;
        }
    }

    /**
     * Method to delete this Library.
     * @param reason the reason for deleting this library (replacement or deletion).
     * @return true if the library was deleted.
     * Returns false on error.
     */
    public boolean kill(String reason) {
        if (!isLinked()) {
            System.out.println("Library already killed");
            return false;
        }
        // cannot delete the current library
        Library newCurLib = null;
        if (getCurrent() == this) {
            // find another library
            for (Library lib : database.libraries.values()) {
                if (lib == this) {
                    continue;
                }
                if (lib.isHidden()) {
                    continue;
                }
                newCurLib = lib;
                break;
            }
//			if (newCurLib == null)
//			{
//				System.out.println("Cannot delete the last library");
//				Job.getUserInterface().showInformationMessage("Cannot delete the last "+toString(),
//					"Close library");
//				return false;
//			}
        }

        // make sure it is in the list of libraries
        if (database.libraries.get(getName()) != this) {
            System.out.println("Cannot delete library " + this);
            Job.getUserInterface().showErrorMessage("Cannot delete " + toString(),
                    "Close library");
            return false;
        }

        // make sure none of these cells are referenced by other libraries
        boolean referenced = false;
        for (Library lib : database.libraries.values()) {
            if (lib == this) {
                continue;
            }
            for (Iterator<Cell> cIt = lib.getCells(); cIt.hasNext();) {
                Cell cell = cIt.next();
                for (Iterator<NodeInst> nIt = cell.getNodes(); nIt.hasNext();) {
                    NodeInst ni = nIt.next();
                    if (ni.isCellInstance()) {
                        Cell subCell = (Cell) ni.getProto();
                        if (subCell.getLibrary() == this) {
                            Job.getUserInterface().showErrorMessage("Library close failed. Cannot " + reason + " " + toString()
                                    + " because one of its cells (" + subCell.noLibDescribe() + ") is being used (by " + cell.libDescribe() + ")",
                                    "Close library");
                            referenced = true;
                            break;
                        }
                    }
                }
                if (referenced) {
                    break;
                }
            }
            if (referenced) {
                break;
            }
        }
        if (referenced) {
            return false;
        }

        // remove all cells in the library
        erase();
        for (Library lib : database.libraries.values()) {
            if (lib == this) {
                continue;
            }
            lib.removeReferencedLib(this);
        }

        // remove it from the list of libraries
        database.removeLib(getId());

        // set the new current library if appropriate
        if (newCurLib != null) {
            Job.setCurrentLibraryInJob(newCurLib);
        }

        // always broadcast library changes
//		Undo.setNextChangeQuiet(false);
        database.unfreshSnapshot();
        Constraints.getCurrent().killObject(this);
        return true;
    }

    /**
     * Method to remove all contents from this Library.
     */
    public void erase() {
        // remove all cells in the library
        for (Iterator<Cell> it = getCells(); it.hasNext();) {
            Cell c = it.next();
            c.kill();
        }
        cells.clear();
    }

    /**
     * Method to add a Cell to this Library.
     * @param c the Cell to add.
     */
    void addCell(Cell c) {
        CellName cn = c.getCellName();
        // sanity check: make sure Cell isn't already in the list
        synchronized (cells) {
            if (cells.containsKey(cn)) {
                System.out.println("Tried to re-add a cell to a library: " + c);
                return;
            }
            cells.put(cn, c);
            updateNewestVersions();
        }
        setChanged();
    }

    /**
     * Method to remove a Cell from this Library.
     * @param c the Cell to remove.
     */
    void removeCell(Cell c) {
        CellName cn = c.getCellName();
        // sanity check: make sure Cell is in the list
        synchronized (cells) {
            if (cells.get(cn) != c) {
                System.out.println("Tried to remove a non-existant Cell from a library: " + c);
                return;
            }
            cells.remove(cn);
            c.newestVersion = null;
            updateNewestVersions();
        }
        setChanged();
    }

    /**
     * Collect cells from database snapshot into cells list of this Library.
     */
    void collectCells() {
        synchronized (cells) {
            cells.clear();
            for (int cellIndex = 0; cellIndex < database.linkedCells.size(); cellIndex++) {
                Cell cell = database.getCell(cellIndex);
                if (cell == null || cell.getLibrary() != this) {
                    continue;
                }
                cells.put(cell.getCellName(), cell);
            }
            updateNewestVersions();
        }
    }

    /**
     * Update newestVersion fields of cells in this Library.
     */
    private void updateNewestVersions() {
        Cell newestVersion = null;
        for (Cell cell : cells.values()) {
            if (newestVersion == null || !newestVersion.getName().equals(cell.getName()) || newestVersion.getView() != cell.getView()) {
                newestVersion = cell;
            }
            cell.newestVersion = newestVersion;
        }
    }

    /**
     * Adds lib as a referenced library. This also checks the dependency
     * would create a circular dependency, in which case the reference is
     * not logged, and the method returns a LibraryDependency object.
     * If everything is ok, the method returns null.
     * @param lib the library to be added as a referenced lib
     * @return null if ok, a LibraryDependency object if this would create circular dependency
     */
    LibraryDependency addReferencedLib(Library lib) {
        synchronized (referencedLibs) {
            if (referencedLibs.contains(lib)) {
                return null;          // already a referenced lib, is ok
            }
        }
        // check recursively if there is a circular dependency
        List<Library> libDependencies = new ArrayList<Library>();
        if (lib.isReferencedLib(this, libDependencies)) {
            // there is a dependency

            // trace the dependency (this is an expensive operation)
            LibraryDependency d = new LibraryDependency();
            d.startLib = lib;
            d.finalRefLib = this;
            Library startLib = lib;
            for (Library refLib : libDependencies) {

                // startLib references refLib. Find out why
                boolean found = false;
                // find a cell instance that creates dependency
                for (Iterator<Cell> itCell = startLib.getCells(); itCell.hasNext();) {
                    Cell c = itCell.next();
                    for (Iterator<NodeInst> it = c.getNodes(); it.hasNext();) {
                        NodeInst ni = it.next();
                        if (ni.isCellInstance()) {
                            Cell cc = (Cell) ni.getProto();
                            if (cc.getLibrary() == refLib) {
                                d.dependencies.add(c);
                                d.dependencies.add(cc);
                                found = true;
                                break;
                            }
                        }
                    }
                    if (found) {
                        break;
                    }
                }
                if (!found) {
                    System.out.println("ERROR: Library.addReferencedLib dependency trace failed inexplicably");
                }
                startLib = refLib;
            }

            return d;
        }

        // would not create circular dependency, add and return
        synchronized (referencedLibs) {
            referencedLibs.add(lib);
            updateBackup(d, backup.modified, backupReferencedLibs(backup.referencedLibs));
        }
        return null;
    }

    /**
     * Try to remove lib as a referenced library. If it is no longer referenced,
     * it is removed as a reference library.
     * @param lib the reference library that may no longer be referenced
     */
    void removeReferencedLib(Library lib) {
        if (lib == this) {
            return;            // we don't store references to self
        }
        synchronized (referencedLibs) {
            if (!referencedLibs.contains(lib)) {
                return;
            }
//            assert(referencedLibs.contains(lib));
        }
        boolean refFound = false;
        for (Iterator<Cell> itCell = getCells(); itCell.hasNext();) {
            Cell c = itCell.next();
            for (Iterator<NodeInst> it = c.getNodes(); it.hasNext();) {
                NodeInst ni = it.next();
                if (ni.isCellInstance()) {
                    Cell cc = (Cell) ni.getProto();
                    if (cc.getLibrary() == lib) {
                        refFound = true;
                        break;
                    }
                }
            }
            if (refFound) {
                break;
            }
        }
        if (!refFound) {
            // no instance that would create reference found, ok to remove reference
            synchronized (referencedLibs) {
                referencedLibs.remove(lib);
                updateBackup(d, backup.modified, backupReferencedLibs(backup.referencedLibs));
            }
        }
    }

    /**
     * Returns true if this Library directly references the specified Library 'lib'.
     * It does not return true if the library is referenced through another library.
     * @param lib the possible referenced library
     * @return true if the library is directly referenced by this library, false otherwise
     */
    public boolean referencesLib(Library lib) {
        synchronized (referencedLibs) {
            if (referencedLibs.contains(lib)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks to see if lib is referenced by this library, or by
     * any libraries this library references, recursively.
     * @param lib the lib to check if it is a referenced lib
     * @return true if it is through any number of references, false otherwise
     */
    private boolean isReferencedLib(Library lib, List<Library> libDepedencies) {
        List<Library> reflibsCopy = new ArrayList<Library>();
        synchronized (referencedLibs) {
            if (referencedLibs.contains(lib)) {
                libDepedencies.add(lib);
                return true;
            }
            reflibsCopy.addAll(referencedLibs);
        }
        for (Library reflib : reflibsCopy) {
            // if reflib already in dependency list, ignore
            if (libDepedencies.contains(reflib)) {
                continue;
            }

            // check recursively
            libDepedencies.add(reflib);
            if (reflib.isReferencedLib(lib, libDepedencies)) {
                return true;
            }
            // remove reflib in accumulated list, try again
            libDepedencies.remove(reflib);
        }
        return false;
    }

    static class LibraryDependency {

        private List<Cell> dependencies;
        private Library startLib;
        private Library finalRefLib;

        private LibraryDependency() {
            dependencies = new ArrayList<Cell>();
        }

        public String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append(startLib + " depends on " + finalRefLib + " through the following references:\n");
            for (Iterator<Cell> it = dependencies.iterator(); it.hasNext();) {
                Cell libCell = it.next();
                Cell instance = it.next();
                buf.append("   " + libCell.libDescribe() + " instantiates " + instance.libDescribe() + "\n");
            }
            return buf.toString();
        }
    }

    // ----------------- public interface --------------------
    /**
     * Returns persistent data of this Library.
     * @return persistent data of this Library.
     */
    @Override
    public ImmutableLibrary getD() {
        return d;
    }

    /**
     * Modifies persistend data of this Library.
     * @param newD new persistent data.
     * @return true if persistent data was modified.
     */
    private boolean setD(ImmutableLibrary newD) {
        checkChanging();
        ImmutableLibrary oldD = d;
        if (newD == oldD) {
            return false;
        }
        d = newD;
//        setChanged();
        assert isLinked();
        updateBackup(d, true, backup.referencedLibs);
        Constraints.getCurrent().modifyLibrary(this, oldD);
        return true;
    }

    /**
     * Method to add a Variable on this Library.
     * It may add repaired copy of this Variable in some cases.
     * @param var Variable to add.
     */
    public void addVar(Variable var) {
        setD(getD().withVariable(var));
    }

    /**
     * Method to delete a Variable from this Library.
     * @param key the key of the Variable to delete.
     */
    public void delVar(Variable.Key key) {
        setD(getD().withoutVariable(key));
    }

    /**
     * Method to return LibId of this Library.
     * LibId identifies Library independently of threads.
     * @return LibId of this Library.
     */
    public LibId getId() {
        return d.libId;
    }

    /**
     * Returns a Library by LibId.
     * Returns null if the Library is not linked to the database.
     * @param libId LibId to find.
     * @return Library or null.
     */
    public static Library inCurrentThread(LibId libId) {
        return EDatabase.currentDatabase().getLib(libId);
    }

    /**
     * Returns true if this Library is linked into database.
     * @return true if this Library is linked into database.
     */
    public boolean isLinked() {
        return inCurrentThread(getId()) == this;
    }

    /**
     * Returns database to which this Library belongs.
     * @return database to which this Library belongs.
     */
    public EDatabase getDatabase() {
        return database;
    }

    /*
     * Low-level method to backup this Library to LibraryBackup.
     * @return LibraryBackup which is the backup of this Library.
     */
    public LibraryBackup backup() {
        return backup;
    }

    void recover(LibraryBackup recoverBackup) {
        checkUndoing();
        backup = recoverBackup;
        this.d = recoverBackup.d;
        referencedLibs.clear();
        for (LibId libId : recoverBackup.referencedLibs) {
            referencedLibs.add(database.getLib(libId));
        }
    }

    void checkFresh(LibraryBackup libBackup) {
        assert d == libBackup.d;
        assert backup == libBackup;
        assert libBackup.referencedLibs.length == referencedLibs.size();
        for (int i = 0; i < libBackup.referencedLibs.length; i++) {
            assert libBackup.referencedLibs[i] == referencedLibs.get(i).getId();
        }
    }

    private LibId[] backupReferencedLibs(LibId[] oldReferencedLibs) {
        int numRefs = Math.min(oldReferencedLibs.length, referencedLibs.size());
        int matchedRefs = 0;
        while (matchedRefs < numRefs && oldReferencedLibs[matchedRefs] == referencedLibs.get(matchedRefs).getId()) {
            matchedRefs++;
        }
        if (matchedRefs == oldReferencedLibs.length && matchedRefs == referencedLibs.size()) {
            return oldReferencedLibs;
        }
        LibId[] newRefs = new LibId[referencedLibs.size()];
        System.arraycopy(oldReferencedLibs, 0, newRefs, 0, matchedRefs);
        for (int i = matchedRefs; i < referencedLibs.size(); i++) {
            newRefs[i] = referencedLibs.get(i).getId();
        }
        return newRefs;
    }
    
    public static void repairAllLibraries(EditingPreferences ep) {
        ErrorLogger errorLogger = ErrorLogger.newInst("Repair Libraries");
        for (Iterator<Library> it = Library.getLibraries(); it.hasNext();) {
            Library l = it.next();
            l.checkAndRepair(true, errorLogger, ep);
        }
        System.out.println("Repair Libraries: " + errorLogger.getNumErrors() + " errors, "
                + errorLogger.getNumWarnings() + " warnings");
    }

    /**
     * Method to check and repair data structure errors in this Library.
     */
    public int checkAndRepair(boolean repair, ErrorLogger errorLogger, EditingPreferences ep) {
        int errorCount = 0;
        boolean verbose = !isHidden();
        if (verbose) {
            System.out.print("Checking " + this);
            if (repair) {
                System.out.print(" for repair");
            }
            System.out.println();
        }

        for (Iterator<Cell> it = getCells(); it.hasNext();) {
            Cell cell = it.next();
            errorCount += cell.checkAndRepair(repair, errorLogger, ep);
        }
        if (errorCount != 0) {
            if (repair) {
                if (verbose) {
                    System.out.println("... library repaired");
                }
//			    setChanged();
            } else {
                if (verbose) {
                    System.out.println("... library has to be repaired");
                }
            }
        } else {
            if (verbose) {
                System.out.println("... library checked");
            }
        }
        return errorCount;
    }

    /**
     * Method to check invariants in this Library.
     * @exception AssertionError if invariants are not valid
     */
    protected void check() {
        assert getD() == backup.d;
        assert backup.referencedLibs.length == referencedLibs.size();
        super.check();
        String libName = d.libId.libName;
        assert libName != null;
        assert libName.length() > 0;
        assert libName.indexOf(' ') == -1 && libName.indexOf(':') == -1 : libName;
        for (int i = 0; i < referencedLibs.size(); i++) {
            Library rLib = referencedLibs.get(i);
            assert rLib.isLinked() && rLib.database == database;
            assert backup.referencedLibs[i] == referencedLibs.get(i).getId();
        }
        HashSet<Cell.CellGroup> cellGroups = new HashSet<Cell.CellGroup>();
        String protoName = null;
        Cell.CellGroup cellGroup = null;
        Cell newestVersion = null;
        for (Map.Entry<CellName, Cell> e : cells.entrySet()) {
            CellName cn = e.getKey();
            Cell cell = e.getValue();
            assert cell.isLinked() && cell.getDatabase() == database;
            assert cell.getCellName() == cn;
            assert cell.getLibrary() == this;
            if (protoName == null || !cell.getName().equals(protoName)) {
                protoName = cell.getName();
                cellGroup = cell.getCellGroup();
                assert cellGroup != null : cell;
                cellGroups.add(cellGroup);
                newestVersion = cell;
            }
            if (cell.getView() != newestVersion.getView()) {
                newestVersion = cell;
            }
            assert cell.getCellGroup() == cellGroup : cell;
            assert cell.newestVersion == newestVersion;
        }
        for (Iterator<Cell.CellGroup> it = cellGroups.iterator(); it.hasNext();) {
            cellGroup = it.next();
            cellGroup.check();
        }
    }

    private void setFlag(int mask, boolean value) {
        setD(d.withFlags(value ? d.flags | mask : d.flags & ~mask));
    }

    private boolean isFlag(int mask) {
        return (d.flags & mask) != 0;
    }

    /**
     * Method to indicate that this Library has changed.
     */
    public void setChanged() {
        checkChanging();
        if (!isChanged()) {
            updateBackup(d, true, backup.referencedLibs);
        }
    }

    /**
     * Method to indicate that this Library has not changed.
     */
    public void clearChanged() {
        clearChanged(Collections.<Cell>emptySet());
    }

    /**
     * Method to indicate that this Library has not changed.
     */
    public void clearChanged(Set<Cell> exceptCells) {
        checkChanging();
        boolean hasExceptions = false;
        for (Cell cell : cells.values()) {
            if (exceptCells.contains(cell)) {
                hasExceptions = true;
                continue;
            }
            cell.clearModified();
        }
        if (isChanged() && !hasExceptions) {
            updateBackup(d, false, backup.referencedLibs);
        }
    }

    private void updateBackup(ImmutableLibrary d, boolean modified, LibId[] referencedLibs) {
        backup = new LibraryBackup(d, modified, referencedLibs);
        database.unfreshSnapshot();
    }

    /**
     * Method to return true if this Library has changed.
     * @return true if this Library has changed.
     */
    public boolean isChanged() {
        return backup.modified;
    }

    /**
     * Method to indicate that this Library came from disk.
     * Libraries that come from disk are saved without a file-selection dialog.
     */
    public void setFromDisk() {
        setFlag(READFROMDISK, true);
    }

    /**
     * Method to indicate that this Library did not come from disk.
     * Libraries that come from disk are saved without a file-selection dialog.
     */
    public void clearFromDisk() {
        setFlag(READFROMDISK, false);
    }

    /**
     * Method to return true if this Library came from disk.
     * Libraries that come from disk are saved without a file-selection dialog.
     * @return true if this Library came from disk.
     */
    public boolean isFromDisk() {
        return isFlag(READFROMDISK);
    }

    /**
     * Method to indicate that this Library is hidden.
     * Hidden libraries are not seen by the user.
     * For example, the "clipboard" library is hidden because it is only used
     * internally for copying and pasting circuitry.
     */
    public void setHidden() {
        setFlag(HIDDENLIBRARY, true);
    }

    /**
     * Method to indicate that this Library is not hidden.
     * Hidden libraries are not seen by the user.
     * For example, the "clipboard" library is hidden because it is only used
     * internally for copying and pasting circuitry.
     */
    public void clearHidden() {
        setFlag(HIDDENLIBRARY, false);
    }

    /**
     * Method to return true if this Library is hidden.
     * Hidden libraries are not seen by the user.
     * For example, the "clipboard" library is hidden because it is only used
     * internally for copying and pasting circuitry.
     * @return true if this Library is hidden.
     */
    public boolean isHidden() {
        return isFlag(HIDDENLIBRARY);
    }

    /**
     * Method to return the current Library.
     * @return the current Library.
     */
    public static Library getCurrent() {
        return Job.getUserInterface().getCurrentLibrary();
    }

    /**
     * Low-level method to get the user bits.
     * The "user bits" are a collection of flags that are more sensibly accessed
     * through special methods.
     * This general access to the bits is required because the ELIB
     * file format stores it as a full integer.
     * This should not normally be called by any other part of the system.
     * @return the "user bits".
     */
    public int lowLevelGetUserBits() {
        return d.flags;
    }

    /**
     * Low-level method to set the user bits.
     * The "user bits" are a collection of flags that are more sensibly accessed
     * through special methods.
     * This general access to the bits is required because the ELIB
     * file format stores it as a full integer.
     * This should not normally be called by any other part of the system.
     * @param userBits the new "user bits".
     */
    public void lowLevelSetUserBits(int userBits) {
        setD(d.withFlags(userBits));
    }

    /**
     * Get list of cells contained in other libraries
     * that refer to cells contained in this library
     * @param elib to search for
     * @return list of cells refering to elements in this library
     */
    public static Set<Cell> findReferenceInCell(Library elib) {
        return EDatabase.currentDatabase().findReferenceInCell(elib);
    }

    /**
     * Method to find a Library with the specified name.
     * @param libName the name of the Library.
     * Note that this is the Library name, and not the Library file.
     * @return the Library, or null if there is no known Library by that name.
     */
    public static Library findLibrary(String libName) {
        return EDatabase.currentDatabase().findLibrary(libName);
    }

    /**
     * Method to return an iterator over all libraries.
     * @return an iterator over all libraries.
     */
    public static Iterator<Library> getLibraries() {
        return EDatabase.currentDatabase().getLibraries();
    }

    /**
     * Method to return the number of libraries.
     * @return the number of libraries.
     */
    public static int getNumLibraries() {
        return EDatabase.currentDatabase().getNumLibraries();
    }

    /**
     * Method to return an iterator over all visible libraries.
     * @return an iterator over all visible libraries.
     */
    public static List<Library> getVisibleLibraries() {
        return EDatabase.currentDatabase().getVisibleLibraries();
    }

    /**
     * Method to return the name of this Library.
     * @return the name of this Library.
     */
    public String getName() {
        return d.libId.libName;
    }

    /**
     * Method to set the name of this Library.
     * @param libName the new name of this Library.
     * @return mapping of Library/Cell/Export ids, null if the library was renamed.
     */
    public IdMapper setName(String libName) {
        if (d.libId.libName.equals(libName)) {
            return null;
        }

        // make sure the name is legal
        if (LibId.legalLibraryName(libName) != libName) {
            System.out.println("Error: '" + libName + "' is not a valid name");
            return null;
        }
        Library already = findLibrary(libName);
        if (already != null) {
            System.out.println("Already a library called " + already.getName());
            return null;
        }

        Snapshot oldSnapshot = database.backup();
        LibId newLibId = oldSnapshot.idManager.newLibId(libName);
        IdMapper idMapper = IdMapper.renameLibrary(oldSnapshot, d.libId, newLibId);
        Snapshot newSnapshot = oldSnapshot.withRenamedIds(idMapper, null, null);
        LibraryBackup[] libBackups = newSnapshot.libBackups.toArray(new LibraryBackup[newSnapshot.libBackups.size()]);
        LibraryBackup libBackup = libBackups[newLibId.libIndex];

        String newLibFile = TextUtils.getFilePath(d.libFile) + libName;
        String extension = TextUtils.getExtension(d.libFile);
        if (extension.length() > 0) {
            newLibFile += "." + extension;
        }
        URL libFile = TextUtils.makeURLToFile(newLibFile);
        libBackups[newLibId.libIndex] = new LibraryBackup(libBackup.d.withLibFile(libFile), true, libBackup.referencedLibs);
        newSnapshot = newSnapshot.with(null, null, (CellTree[]) null, libBackups);
        checkChanging();
        boolean isCurrent = getCurrent() == this;
        database.lowLevelSetCanUndoing(true);
        database.undo(newSnapshot);
        database.lowLevelSetCanUndoing(false);
        Constraints.getCurrent().renameIds(idMapper);
        Library newLib = database.getLib(newLibId);
        if (isCurrent) {
            Job.setCurrentLibraryInJob(newLib);
        }
        return idMapper;

//		String oldName = d.libId.libName;
//		lowLevelRename(libName);
//		Constraints.getCurrent().renameObject(this, oldName);
////        setChanged();
//        assert isLinked();
//        database.unfreshSnapshot();
//        for (Cell cell: cells.values())
//            cell.notifyRename();
//		return this;
    }

//	/**
//	 * Method to rename this Library.
//	 * This method is for low-level use by the database, and should not be called elsewhere.
//	 * @param libName the new name of the Library.
//	 */
//	private void lowLevelRename(String libName)
//	{
//		String newLibFile = TextUtils.getFilePath(d.libFile) + libName;
//		String extension = TextUtils.getExtension(d.libFile);
//		if (extension.length() > 0) newLibFile += "." + extension;
//		URL libFile = TextUtils.makeURLToFile(newLibFile);
//
//        database.libraries.remove(d.libName);
//        setD(d.withName(libName, libFile));
//		database.libraries.put(libName, this);
//        assert isLinked();
//        database.unfreshSnapshot();
//
//        Cell curCell = getCurCell();
//        prefs = allPrefs.node(libName);
//        prefs.put("LIB", libName);
//        curCellPref = null;
//        setCurCell(curCell);
//        for (Iterator<Cell> it = getCells(); it.hasNext(); ) {
//            Cell cell = it.next();
//            cell.expandStatusChanged();
//        }
//	}
    /**
     * Method to return the URL of this Library.
     * @return the URL of this Library.
     */
    public URL getLibFile() {
        return d.libFile;
    }

    /**
     * Method to set the URL of this Library.
     * @param libFile the new URL of this Library.
     */
    public void setLibFile(URL libFile) {
        setD(d.withLibFile(libFile));
    }

    /**
     * Compares two <code>Library</code> objects.
     * @param that the Library to be compared.
     * @return the result of comparison.
     */
    public int compareTo(Library that) {
        return TextUtils.STRING_NUMBER_ORDER.compare(getName(), that.getName());
    }

    /**
     * Returns a printable version of this Library.
     * @return a printable version of this Library.
     */
    public String toString() {
        return "library '" + getName() + "'";
    }

    // ----------------- cells --------------------
    /**
     * Method to get the current Cell in this Library.
     * @return the current Cell in this Library.
     * Returns NULL if there is no current Cell.
     */
    public Cell getCurCell() {
        Preferences libPrefs = Pref.getLibraryPreferences(getId());
        String key = "CurrentCell";
        String cellName = libPrefs.get(key, "");
        if (cellName.length() == 0) {
            return null;
        }
        Cell cell = this.findNodeProto(cellName);
        if (cell == null) {
            libPrefs.remove(key);
        }
        return cell;
    }

    /**
     * Method to set the current Cell in this Library.
     * @param curCell the new current Cell in this Library.
     */
    public void setCurCell(Cell curCell) {
        Preferences libPrefs = Pref.getLibraryPreferences(getId());
        String key = "CurrentCell";
        if (curCell == null) {
            libPrefs.remove(key);
        } else {
            libPrefs.put(key, curCell.noLibDescribe());
        }
    }

    public static Cell findCellInLibraries(String cellName, View view, String libraryName) {
        if (libraryName != null) {
            Library lib = findLibrary(libraryName);
            if (lib != null) {
                Cell cell = lib.findNodeProto(cellName);
                // Either first match in name or check that view matches
                if (cell != null && (view == null || cell.getView() == view)) {
                    return cell;
                }
            }
            // search in other libraries if no library is found with given name
        }

        for (Iterator<Library> it = Library.getLibraries(); it.hasNext();) {
            Library lib = it.next();
            Cell cell = lib.findNodeProto(cellName);
            // Either first match in name or check that view matches
            if (cell != null && (view == null || cell.getView() == view)) {
                return cell;
            }
        }
        return null;
    }

    /**
     * Method to find the Cell with the given name in this Library.
     * @param name the name of the desired Cell. It must have the view included in the name.
     * @return the Cell with the given name in this Library.
     */
    public Cell findNodeProto(String name) {
        if (name == null) {
            return null;
        }
        CellName n = CellName.parseName(name);
        if (n == null) {
            return null;
        }
        synchronized (cells) {
            Cell cell = cells.get(n);
            if (cell != null) {
                return cell;
            }

            Cell onlyWithName = null;
            for (Cell c : cells.values()) {
                if (!n.getName().equals(c.getName())) {
                    continue;
                }
//                if (!n.getName().equalsIgnoreCase(c.getName())) continue;
                if (onlyWithName == null || onlyWithName.getVersion() < c.getVersion()) {
                    onlyWithName = c;
                }
                if (n.getView() != c.getView()) {
                    continue;
                }
                if (n.getVersion() > 0 && n.getVersion() != c.getVersion()) {
                    continue;
                }
                if (n.getVersion() == 0 && c.getNewestVersion() != c) {
                    continue;
                }
                return c;
            }
            if (n.getView() == View.UNKNOWN && onlyWithName != null) {
                return onlyWithName;
            }
        }
        return null;
    }

    public int getNumCells() {
        synchronized (cells) {
            return cells.size();
        }
    }

    /**
     * Method to return an Iterator over all Cells in this Library.
     * @return an Iterator over all Cells in this Library.
     */
    public Iterator<Cell> getCells() {
        synchronized (cells) {
            ArrayList<Cell> cellsCopy = new ArrayList<Cell>(cells.values());
            return cellsCopy.iterator();
        }
    }

    /**
     * Method to return an Iterator over all Cells in this Library after given CellName.
     * @param cn starting CellName
     * @return an Iterator over all Cells in this Library after given CellName.
     */
    Iterator<Cell> getCellsTail(CellName cn) {
        synchronized (cells) {
            return cells.tailMap(cn).values().iterator();
        }
    }

    /**
     * Returns version of Electric which wrote this library.
     * Returns null for ReadableDumps, for new libraries and for dummy libraries.
     * @return version
     */
    public Version getVersion() {
        return d.version;
    }

    /**
     * Method to set library version found in header.
     * @param version
     */
    public void setVersion(Version version) {
        setD(d.withVersion(version));
    }

    /**
     * Returns DELIB cells.
     * @return DELIB cells.
     */
    public Set<CellId> getDelibCells() {
        return d.delibCells;
    }

    /**
     * Sets DELIB cells.
     */
    public void setDelibCells() {
        HashSet<CellId> delibCells = new HashSet<CellId>();
        for (Iterator<Cell> it = getCells(); it.hasNext();) {
            delibCells.add(it.next().getId());
        }
        setD(d.withDelibCells(delibCells));
    }
}
