/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: NetlistShorted.java
 * Written by: Dmitry Nadezhin, Sun Microsystems.
 *
 * Copyright (c) 2003, Oracle and/or its affiliates. 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.network;

import com.sun.electric.util.TextUtils;
import com.sun.electric.util.collections.ArrayIterator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * This class represents Netlist obtained from base Netlist by shortening some Networks.
 */
public class NetlistShorted extends Netlist {

    private Netlist baseNetlist;
    private int[] thisNetHead;
    private int[] baseNetNext;
    private BitSet isUsernamed = new BitSet();
    private BitSet isExported = new BitSet();
    private String[] firstNames;

    NetlistShorted(Netlist baseNetlist, Netlist.ShortResistors shortResistors, int[] netMap) {
        super(baseNetlist.netCell, shortResistors, baseNetlist.numExternalEntries, netMap);
        this.baseNetlist = baseNetlist;
        expectedSnapshot = baseNetlist.expectedSnapshot;
        expectedCellTree = baseNetlist.expectedCellTree;

        assert nm_net.length == baseNetlist.nm_net.length;
        int[] baseNetToThisNet = new int[baseNetlist.getNumNetworks()];
        Arrays.fill(baseNetToThisNet, -1);
        for (int mapOffset = 0; mapOffset < nm_net.length; mapOffset++) {
            int baseNetIndex = baseNetlist.nm_net[mapOffset];
            int thisNetIndex = nm_net[mapOffset];
            if (baseNetToThisNet[baseNetIndex] < 0) {
                baseNetToThisNet[baseNetIndex] = thisNetIndex;
            } else {
                assert baseNetToThisNet[baseNetIndex] == thisNetIndex;
            }
        }
        for (int thisNet : baseNetToThisNet) {
            assert thisNet >= 0;
        }
        thisNetHead = new int[getNumNetworks()];
        Arrays.fill(thisNetHead, -1);
        baseNetNext = new int[baseNetlist.getNumNetworks()];
        Arrays.fill(baseNetNext, -1);
        for (int baseNetIndex = baseNetlist.getNumNetworks() - 1; baseNetIndex >= 0; baseNetIndex--) {
            int thisNetIndex = baseNetToThisNet[baseNetIndex];
            baseNetNext[baseNetIndex] = thisNetHead[thisNetIndex];
            thisNetHead[thisNetIndex] = baseNetIndex;
            if (baseNetlist.isUsernamed(baseNetIndex)) {
                isUsernamed.set(thisNetIndex);
            }
        }
        for (int thisHead : thisNetHead) {
            assert thisHead >= 0;
        }

        firstNames = new String[getNumNetworks()];
        for (int thisNetIndex = 0; thisNetIndex < getNumNetworks(); thisNetIndex++) {
            makeName(thisNetIndex);
        }
    }

    /**
     * Returns most appropriate name of the net.
     * Intitialized net has at least one name - user-defiend or temporary.
     */
    @Override
    String getName(int netIndex) {
        String name = firstNames[netIndex];
        if (name != null) {
            return name;
        } else {
            return makeName(netIndex);
        }
    }

    private String makeName(int thisNetIndex) {
        int baseIndexLimit = isExported(thisNetIndex) ? baseNetlist.getNumExternalNetworks() : baseNetlist.getNumNetworks();
        String firstName = null;
        if (isUsernamed(thisNetIndex)) {
            for (int baseNetIndex = thisNetHead[thisNetIndex]; baseNetIndex >= 0 && baseNetIndex < baseIndexLimit; baseNetIndex = baseNetNext[baseNetIndex]) {
                if (!baseNetlist.isUsernamed(baseNetIndex)) {
                    continue;
                }
                String name = baseNetlist.getName(baseNetIndex);
                if (firstName == null || TextUtils.STRING_NUMBER_ORDER.compare(name, firstName) < 0) {
                    firstName = name;
                }
            }
        } else {
            firstName = baseNetlist.getName(thisNetHead[thisNetIndex]);
        }
        firstNames[thisNetIndex] = firstName;
        return firstName;
    }

    @Override
    Iterator<String> getNames(int netIndex) {
        if (isUsernamed(netIndex)) {
            TreeSet<String> exportedNames = new TreeSet<String>(TextUtils.STRING_NUMBER_ORDER);
            TreeSet<String> privateNames = new TreeSet<String>(TextUtils.STRING_NUMBER_ORDER);
            fillNames(netIndex, exportedNames, privateNames);
            ArrayList<String> allNames = new ArrayList<String>(exportedNames);
            for (String name : privateNames) {
                if (exportedNames.contains(name)) {
                    continue;
                }
                allNames.add(name);
            }
            return allNames.iterator();
        } else {
            return Collections.singleton(getName(netIndex)).iterator();
        }
    }

    @Override
    Iterator<String> getExportedNames(int netIndex) {
        if (isExported(netIndex)) {
            TreeSet<String> exportedNames = new TreeSet<String>(TextUtils.STRING_NUMBER_ORDER);
            fillNames(netIndex, exportedNames, null);
            return exportedNames.iterator();
        } else {
            return ArrayIterator.<String>emptyIterator();
        }
    }

    /** Returns true if nm is one of Network's names */
    @Override
    boolean hasName(int netIndex, String nm) {
        if (isUsernamed(netIndex)) {
            for (int baseNetIndex = thisNetHead[netIndex]; baseNetIndex >= 0; baseNetIndex = baseNetNext[baseNetIndex]) {
                if (baseNetlist.hasName(baseNetIndex, nm)) {
                    return true;
                }
            }
            return false;
        } else {
            return nm.equals(getName(netIndex));
        }
    }

    /**
     * Add names of this net to two Collections. One for exported, and other for unexported names.
     * @param exportedNames Collection for exported names.
     * @param privateNames Collection for unexported names.
     */
    @Override
    void fillNames(int netIndex, Collection<String> exportedNames, Collection<String> privateNames) {
        if (!isUsernamed(netIndex)) {
            return;
        }
        for (int baseNetIndex = thisNetHead[netIndex]; baseNetIndex >= 0; baseNetIndex = baseNetNext[baseNetIndex]) {
            baseNetlist.fillNames(baseNetIndex, exportedNames, privateNames);
        }
    }

    /**
     * Method to tell whether this network has user-defined name.
     * @return true if this Network has user-defined name.
     */
    @Override
    boolean isUsernamed(int netIndex) {
        return isUsernamed.get(netIndex);
    }

    @Override
    int getEquivPortIndexByNetIndex(int netIndex) {
        return baseNetlist.getEquivPortIndexByNetIndex(thisNetHead[netIndex]);
    }
//    void checkNames() {
//        TreeSet<String> exportedNames = new TreeSet<String>(TextUtils.STRING_NUMBER_ORDER);
//        TreeSet<String> privateNames = new TreeSet<String>(TextUtils.STRING_NUMBER_ORDER);
//        for (int thisNetIndex = 0; thisNetIndex < getNumNetworks(); thisNetIndex++) {
//            exportedNames.clear();
//            privateNames.clear();
//            for (int baseNetIndex = thisNetHead[thisNetIndex]; baseNetIndex >= 0; baseNetIndex = baseNetNext[baseNetIndex]) {
//                baseNetlist.fillNames(baseNetIndex, exportedNames, privateNames);
//            }
//            if (exportedNames.size() > 0 || privateNames.size() > 0) {
//                assert isUsernamed(thisNetIndex);
//                assert getName(thisNetIndex) == (exportedNames.size() > 0 ? exportedNames : privateNames).iterator().next();
//                assert isExported(thisNetIndex) == (exportedNames.size() > 0);
//                Iterator<String> eIt = getExportedNames(thisNetIndex);
//                Iterator<String> pIt = getNames(thisNetIndex);
//                for (String name : exportedNames) {
//                    assert eIt.next().equals(name);
//                    assert pIt.next().equals(name);
//                }
//                assert !eIt.hasNext();
//                for (String name : privateNames) {
//                    if (exportedNames.contains(name)) {
//                        continue;
//                    }
//                    String pName = pIt.next();
//                    if (!pName.equals(name)) {
//                        int x = 0;
//                    }
//                    assert pName.equals(name);
//                }
//                assert !pIt.hasNext();
//            } else {
//                assert !isUsernamed(thisNetIndex);
//                assert !isExported(thisNetIndex);
//                boolean hasTempName = false;
//                String tempName = getName(thisNetIndex);
//                for (int baseNetIndex = thisNetHead[thisNetIndex]; baseNetIndex >= 0; baseNetIndex = baseNetNext[baseNetIndex]) {
//                    assert !baseNetlist.isUsernamed(baseNetIndex);
//                    if (baseNetlist.getName(baseNetIndex).equals(tempName)) {
//                        hasTempName = true;
//                    }
//                }
//                assert hasTempName;
//            }
//        }
//    }
}
