/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Quick.java
 *
 * Copyright (c) 2004, 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.tool.drc;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.technology.*;
import com.sun.electric.tool.Consumer;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.MultiTaskJob;
import com.sun.electric.util.ElapseTimer;

import java.util.*;

/**
 * User: Gilda Garreton
 * Date: Dec 12, 2007
 */
public abstract class MTDRCTool extends MultiTaskJob<Layer, MTDRCTool.MTDRCResult, MTDRCTool.MTDRCResult>
{
    protected DRC.DRCPreferences dp;
    protected Cell topCell;
    protected ElapseTimer globalStartTime = ElapseTimer.createInstance();
    protected CellLayersContainer cellLayersCon = new CellLayersContainer();
    protected final boolean printLog = Job.getDebug();
    protected XMLRules rules;

    protected MTDRCTool(String jobName, DRC.DRCPreferences dp, Cell c, Consumer<MTDRCResult> consumer)
    {
        super(jobName, DRC.getDRCTool(), consumer);
        this.dp = dp;
        this.topCell= c;
        // Rules set must be local to avoid concurrency issues with other tasks
        this.rules = topCell.getTechnology().getFactoryDesignRules();
    }

    @Override
    public void prepareTasks()
    {
//        Technology tech = topCell.getTechnology();
        cellLayersCon = new CellLayersContainer();
        CheckCellLayerEnumerator layerCellCheck = new CheckCellLayerEnumerator(cellLayersCon);
        HierarchyEnumerator.enumerateCell(topCell, VarContext.globalContext, layerCellCheck);
        Collection<Layer> layers = cellLayersCon.getLayersSet(topCell);
        globalStartTime.start();
        for (Layer layer : layers)
        {
            assert (layer != null); // it should always be a valid layer
            if (rules.hasLayerRules(layer))
                startTask(layer.getName(), layer);
        }
        if (!checkArea())
            startTask("Node Min Size.", null);
    }

    @Override
    public MTDRCResult mergeTaskResults(Map<Layer,MTDRCResult> taskResults)
    {
        int numTE = 0, numTW = 0;
        Set<Cell> goodSpacingSet = new HashSet<Cell>();
        Set<Cell> goodAreaSet = new HashSet<Cell>();
        Set<Cell> cleanSpacingSet = new HashSet<Cell>();
        Set<Cell> cleanAreaSet = new HashSet<Cell>();
        boolean runFine = true;

        for (Map.Entry<Layer, MTDRCResult> e : taskResults.entrySet())
        {
            MTDRCResult p = e.getValue();
            numTE += p.numErrors;
            numTW += p.numWarns;
            if (!p.runfine)
                runFine = false;

            // Collect all cells that must be clear
            cleanSpacingSet.addAll(p.cleanSpacingDRCDate);
            cleanAreaSet.addAll(p.cleanAreaDRCDate);
        }
        // Now that all the cells to be clean are collected, then good cells can be stored.
        for (Map.Entry<Layer, MTDRCResult> e : taskResults.entrySet())
        {
            MTDRCResult p = e.getValue();
            for (Cell c : p.goodSpacingDRCDate)
            {
                if (!cleanSpacingSet.contains(c))
                    goodSpacingSet.add(c);
            }
            for (Cell c : p.goodAreaDRCDate)
            {
                if (!cleanAreaSet.contains(c))
                    goodAreaSet.add(c);
            }
        }
        System.out.println("Finished " + ((runFine)?"without":"with") + " problems.");

        System.out.println("Total DRC Errors: " + numTE);
        System.out.println("Total DRC Warnings: " + numTW);
        globalStartTime.end();
        System.out.println("Total Time: " + globalStartTime);

        if (runFine)
        {
            int activeSpacingBits = DRC.getActiveBits(topCell.getTechnology(), dp);
             DRC.addDRCUpdate(activeSpacingBits, goodSpacingSet, cleanSpacingSet,
                goodAreaSet, cleanAreaSet, null, dp);
        }

        return new MTDRCResult(numTE, numTW, runFine, null, null, null, null, null);
    }

    @Override
    public MTDRCResult runTask(Layer taskKey)
    {
        if (skipLayer(taskKey))
            return null;
        return runTaskInternal(taskKey);
    }

    abstract MTDRCResult runTaskInternal(Layer taskKey);

    abstract boolean checkArea();

    static boolean skipLayerInvalidForMinArea(Layer theLayer)
    {
        // via*, polyCut, activeCut if theLayer is not null
        // theLayer is null if it is checking node min sizes
        return theLayer == null || theLayer.getFunction().isContact(); // via*, polyCut, activeCut
    }

    boolean skipLayer(Layer theLayer)
    {
        if (theLayer == null) return false;

        if (theLayer.getFunction().isDiff() && theLayer.getName().toLowerCase().equals("p-active-well"))
            return true; // dirty way to skip the MoCMOS p-active well
        else if (theLayer.getFunction().isGatePoly())
            return true; // check transistor-poly together with polysilicon-1
        else if (checkArea() && skipLayerInvalidForMinArea(theLayer))  // via*, polyCut, activeCut
            return true;
        return false;
    }

    public static class MTDRCResult
    {
        private int numErrors, numWarns;
        private boolean runfine;
        private HashSet<Cell> goodSpacingDRCDate, goodAreaDRCDate;
        private HashSet<Cell> cleanSpacingDRCDate, cleanAreaDRCDate;

        MTDRCResult(int numE, int numW, boolean notAborted,
                    HashSet<Cell> goodSpacingDRCD, HashSet<Cell> cleanSpacingDRCD,
                    HashSet<Cell> goodAreaDRCD, HashSet<Cell> cleanAreaDRCD,
                    HashMap<Geometric, List<Variable>> newVars)
        {
            this.numErrors = numE;
            this.numWarns = numW;
            this.runfine = notAborted;
            this.goodSpacingDRCDate = goodSpacingDRCD;
            this.cleanSpacingDRCDate = cleanSpacingDRCD;
            this.goodAreaDRCDate = goodAreaDRCD;
            this.cleanAreaDRCDate = cleanAreaDRCD;
            assert(newVars == null); // not implemented for Schematics DRC
        }

        public int getNumErrors()
        {
            return numErrors;
        }

        public int getNumWarnings()
        {
            return numWarns;
        }
    }
}
