/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Channel.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.tool.generator.flag.router;

import java.util.ArrayList;
import java.util.List;

import com.sun.electric.tool.generator.flag.Utils;
import com.sun.electric.tool.Job;


/** rectangular region for routing */
public class Channel implements Comparable<Channel> {
	// A channel may be adjacent to a wide power or ground strap.
	// More spacing is required for wide metal. 
	private static final double METAL_WIDE_SPACE = 4;
	private static final double METAL_SPACE = 3;
	private static final double METAL_WIDTH = 3;
	private static final double METAL_PITCH = METAL_SPACE + METAL_WIDTH;
	private final boolean horizontal;
    private final List<Track> tracks = new ArrayList<Track>();
    private final double minL, maxL,   // dimensions along channel length    
                         minW, maxW;   // dimensions along channel width
    private final String description;
    
    private static void prln(String msg) {Utils.prln(msg);}

    public Channel(boolean horizontal, double l1, double l2, 
    		       double w1, double w2,
    		       String description) {
    	this.horizontal = horizontal;
    	this.minL = Math.min(l1, l2);
    	this.maxL = Math.max(l1, l2);
    	this.minW = Math.min(w1, w2);
    	this.maxW = Math.max(w1, w2);
    	this.description = description;
    	double trackCent=minW+METAL_WIDE_SPACE+METAL_WIDTH/2;
    	while (trackCent+METAL_WIDTH/2 + METAL_WIDE_SPACE <=maxW) {
    		int ndx = tracks.size();
    		tracks.add(new Track(trackCent, this, ndx));
    		trackCent += METAL_SPACE + METAL_WIDTH;
    	}
    }
    
    public int compareTo(Channel c) {
    	double delta = minW - c.minW;
    	return (int) Math.signum(delta);
    }
    /** Find an unused part of a track that can connect xy1 with xy2.
     * Any track whose center lies outside the bounds boundXY1 and boundXY2
     * increases the manhatten distance of the route.  
     * If we need to select from two tracks that both minimize the 
     * manhatten distance, then choose the track closest to the center
     * of the channel.  This will tend to pack the segments into the 
     * smallest number of channels.  */
    public Segment allocate(double xy1, double xy2,
    		                double boundXY1, double boundXY2) {
    	double min = Math.min(xy1, xy2) - METAL_PITCH;
    	double max = Math.max(xy1, xy2) + METAL_PITCH;
    	double minBound = Math.min(boundXY1, boundXY2);
    	double maxBound = Math.max(boundXY1, boundXY2);
    	
    	// Check if span is outside this Channel
    	if (max>this.maxL || min<this.minL) return null;
    	
    	// Try a greedy track allocation
    	double minCost = Double.MAX_VALUE;
    	Track bestTrack = null;
    	int nbTracks = tracks.size();
    	int midNdx = (nbTracks-1) / 2;
    	for (Track t : tracks) {
    		if (t.isAvailable(min, max)) {
    			double center = t.getCenter();
    			double cost = Math.abs(t.getIndex()-midNdx);
    			double c = center - maxBound;
    			if (c>0) cost+=c*1000;
    			c = minBound-center;
    			if (c>0) cost+=c*1000;
    			if (cost<minCost) {
    				minCost = cost;
    				bestTrack = t;
    			}
    		}
    	}
    	if (bestTrack==null) {
    		String lDesc = isHorizontal() ? "x=" : "y=";
    		String wDesc = isHorizontal() ? "y=" : "x=";
    		prln("Failed to allocate "+description+" segment from "+
    			 lDesc+min+" to "+lDesc+max+" between "+wDesc+minW+" and "+
    			 wDesc+maxW);
    		prln(this.toString());
    		return null;
    	}
    	Segment s = bestTrack.allocate(min, max);
    	Job.error(s==null,
    			        "Impossible: we already checked it's available");
    	return s;
    }
    /** Metal-2 only PortInst don't allow us to chose the track center.
     * Allocate the largest segment in the interval [min, max] that covers src */
    public Segment allocateBiggestFromTrack(double min, double src, double max, 
    		                                double trackCenter) {
    	for (Track t : tracks) {
    		double center = t.getCenter();
    		if (center==trackCenter) {
    			return t.allocateBiggest(min, src, max);
    		}
    	}
		return null;
    }
    public boolean isHorizontal() {return horizontal;}
    public boolean hasTracks() {return tracks.size()!=0;}
    public String toString() {
    	double xMin, xMax, yMin, yMax;
    	if (horizontal) {
    		xMin=minL; yMin=minW;
    		xMax=maxL; yMax =maxW;
    	} else {
    		xMin=minW; yMin=minL;
    		xMax=maxW; yMax=maxL;
    	}
    	
    	StringBuffer sb = new StringBuffer();
    	sb.append(horizontal ? "  Horizontal" : "  Vertical");
    	sb.append(" channel bounds: (");
    	sb.append(xMin+", "+yMin+") (");
    	sb.append(xMax+", "+yMax+") ");
    	sb.append(" Tracks:\n");
    	for (Track t : tracks)  sb.append(t.toString());

    	return sb.toString();
    }
//    public double getMinX() {return isHorizontal() ? minL : minW;}
//    public double getMaxX() {return isHorizontal() ? maxL : maxW;}
//    public double getMinY() {return isHorizontal() ? minW : minL;}
//    public double getMaxY() {return isHorizontal() ? maxW : maxL;}
    
    public double getMinTrackCenter() {
    	return tracks.get(0).getCenter(); 
    }
    public double getMaxTrackCenter() {
    	return tracks.get(tracks.size()-1).getCenter();
    }
    public double getMinTrackEnd() {return minL;}
    public double getMaxTrackEnd() {return maxL;}
}
