/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Mos.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.ncc.netlist;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.PrimitiveNode.Function;
import com.sun.electric.tool.ncc.NccOptions;
import com.sun.electric.tool.ncc.basic.NccUtils;
import com.sun.electric.tool.ncc.basic.Primes;
import com.sun.electric.tool.ncc.netlist.NccNameProxy.PartNameProxy;
import com.sun.electric.tool.ncc.trees.Circuit;
import com.sun.electric.tool.Job;

/** One or more MOS transistors in series. All gates have the same width
 * and length. 
 * If MOS has body port then last entry in pins array is body wire */
public class Mos extends Part {
	private static class GateType implements PinType {
		private final int numSeries;
		private final Function np;
		private final int gateHeight;
		private final boolean cap;
		private final boolean hasBody;

		public String description() {
			String t = np.getShortName();
			String c = cap ? "_CAP" : "";
			String h = numSeries==1 ? "" : ("_"+numSeries+"stack");
			String s = hasBody ? "_withBody" : "";
			int hiGate = numSeries+1 - gateHeight;
			String g = "";
			if (numSeries>2) {
				g = gateHeight + (gateHeight==hiGate ? "" : ("/"+hiGate)); 
			} 
			return t+c+h+s+" gate"+g;
		}
		public GateType(Function np, int numSeries, int gateHeight, boolean cap,
				        boolean hasBody) {
			Job.error(np==null, "null type?");
			Job.error(numSeries<1, "bad numSeries");
			int highestGateInLowerHalfOfStack = (numSeries+1)/2;
			Job.error(gateHeight>highestGateInLowerHalfOfStack, "bad gate Height");
			this.np = np;
			this.numSeries = numSeries;
			this.gateHeight = gateHeight;
			this.cap = cap;
			this.hasBody = hasBody;
		}
	}
	private static class DiffType implements PinType {
		private final int numSeries;
		private final Function np;
		private final boolean cap;
		private final boolean hasBody;

		public String description() {
			String t = np.getShortName();
			String c = cap ? "_CAP" : "";
			String h = numSeries==1 ? "" : ("_"+numSeries+"stack");
			String s = hasBody ? "_withBody" : "";
			return t+c+h+s+" diffusion";
		}
		public DiffType(Function np, int numSeries, boolean cap, 
				        boolean hasBody) {
			Job.error(np==null, "null type?");
			Job.error(numSeries<1, "bad numSeries");
			this.np = np;
			this.numSeries = numSeries;
			this.cap = cap;
			this.hasBody = hasBody;
		}
	}
	private static class BodyType implements PinType {
		private final int numSeries;
		private final Function np;
		private final boolean cap;
		public String description() {
			String t = np.getShortName();
			String c = cap ? "_CAP" : "";
			String h = numSeries==1 ? "" : ("_"+numSeries+"stack");
			return t+c+h+"_withBody body";
		}
		public BodyType(Function np, int numSeries, boolean cap) {
			Job.error(np==null, "null type?");
			Job.error(numSeries<1, "bad numSeries");
			this.np = np;
			this.numSeries = numSeries;
			this.cap = cap;
		}
	}
	/** Set of all the pins for a particular Transistor */ 
	private static class PinTypeSetKey {
		private Function type;
		private boolean isCapacitor;
		private int numSeries;
		private boolean hasBody;
		public PinTypeSetKey(Function type, boolean isCapacitor, int numSeries,
				             boolean hasBody) {
			this.type = type;
			this.isCapacitor = isCapacitor;
			this.numSeries = numSeries;
			this.hasBody = hasBody;
		}
	    @Override
		public boolean equals(Object o) {
			if (!(o instanceof PinTypeSetKey)) return false;
			PinTypeSetKey p = (PinTypeSetKey) o;
			return type==p.type && isCapacitor==p.isCapacitor && numSeries==p.numSeries
			       && hasBody==p.hasBody;
		}
	    @Override
		public int hashCode() {
			return type.hashCode() + (isCapacitor?1:0) + (numSeries<<1);
		}
	}
	private static final Map<PinTypeSetKey,PinType[]> TYPE_TO_PINTYPE_ARRAY = new HashMap<PinTypeSetKey,PinType[]>();
	
	private synchronized PinType[] getPinTypeArray() {
		PinTypeSetKey key = new PinTypeSetKey(type(), isCapacitor(), numSeries(),
				                              hasBody);
		PinType[] pinTypeArray = TYPE_TO_PINTYPE_ARRAY.get(key);
		if (pinTypeArray==null) {
			pinTypeArray = new PinType[pins.length];
			TYPE_TO_PINTYPE_ARRAY.put(key, pinTypeArray);
			
			pinTypeArray[0] = pinTypeArray[nbGateDiffPins()-1] =
				new DiffType(type(), numSeries(), isCapacitor(), hasBody);

			int maxHeight = (numSeries()+1) / 2;
			for (int gateHeight=1; gateHeight<=maxHeight; gateHeight++) {
				pinTypeArray[gateHeight] = 
					pinTypeArray[nbGateDiffPins()-1-gateHeight] = 
					new GateType(type(), numSeries(), gateHeight, isCapacitor(), hasBody);
			}
			if (hasBody) {
				pinTypeArray[pinTypeArray.length-1] = new BodyType(type(), numSeries(), isCapacitor());
			}
		}
		return pinTypeArray;
	}
    @Override
	public synchronized PinType getPinTypeOfNthPin(int n) {
		return getPinTypeArray()[n];
	}
	
	/** Generate arrays of pin coefficients on demand. Share these arrays
	 * between identically sized Transistors */
	private static class CoeffGen {
		private static ArrayList<int[]> coeffArraysNoBody = new ArrayList<int[]>();
		private static ArrayList<int[]> coeffArraysBody = new ArrayList<int[]>();
		private static void ensureListEntry(ArrayList<int[]> coeffArrays, int numPins) {
				while (coeffArrays.size()-1<numPins)  coeffArrays.add(null);
		}
		public static int[] getCoeffArray(int nbGateDiff, boolean withBody) {
			ArrayList<int[]> coeffArrays = withBody ? coeffArraysBody : coeffArraysNoBody;
			ensureListEntry(coeffArrays, nbGateDiff);
			int[] coeffArray = coeffArrays.get(nbGateDiff);
			if (coeffArray==null) {
				coeffArray = new int[nbGateDiff+(withBody?1:0)];
				for (int i=0, j=nbGateDiff-1; i<(nbGateDiff+1)/2; i++,j--) {
					int nthPrime = 30 + i + nbGateDiff;
					coeffArray[i] = coeffArray[j] = Primes.get(nthPrime);
				}
				if (withBody) {
					coeffArray[coeffArray.length-1] = Primes.get(30);
				}
				coeffArrays.set(nbGateDiff, coeffArray);
			}
			return coeffArray;
		}
	}

    // ---------- private data -------------
    /** array of pin coefficients */ private final int[] pin_coeffs;
    /** channel width */ private double width;
    /** channel length */ private final double length;
    /** true if transistor has a body connection */ private final boolean hasBody;
    
    // ---------- private methods ----------
	/** Stack of series transistors. If Mos has body connection then 
	 * body is last entry of pins */
	private Mos(Function np, PartNameProxy name, VarContext cont, double width, double length,
				boolean hasBody, Wire[] pins) {
		super(name, cont, np, pins);
		this.width = width;
		this.length = length;
		this.hasBody = hasBody;
		Job.error(np==null, "null type?");
		
		int nbGateDiff = pins.length - (hasBody?1:0);
		
		pin_coeffs = CoeffGen.getCoeffArray(nbGateDiff, hasBody);
		Job.error(pins.length!=pin_coeffs.length, "wrong number of pin coeffs");
	}
	
	private int nbGateDiffPins() {
		return hasBody ? pins.length-1 : pins.length;
	}
	
	private int bodyNdx() {return hasBody ? pins.length-1 : -1;}
	
	private boolean bodyMatches(Mos t) {
		if (hasBody!=t.hasBody) return false;
		if (!hasBody) return true;
		if (pins[bodyNdx()]!=t.pins[t.bodyNdx()]) return false;
		
		return true;
	}

	private boolean matchForward(Mos t) {
		for (int i=0; i<nbGateDiffPins(); i++) {
			if (pins[i]!=t.pins[i]) return false;
		}
		return true;
	}
	private boolean matchReverse(Mos t) {
		int nbGateDiff = nbGateDiffPins();
		for (int i=0, j=nbGateDiff-1; i<nbGateDiff; i++, j--) {
			if (pins[i]!=t.pins[j]) return false;
		}
		return true;
	}
	private boolean samePinsAs(Mos t) {
		if (pins.length!=t.pins.length) return false;
		if (hasBody!=t.hasBody) return false;
		if (!bodyMatches(t)) return false;
		return matchForward(t) || matchReverse(t);
	}
	private void flip() {
		int nbGateDiff = nbGateDiffPins();
		for (int i=0, j=nbGateDiff-1; i<nbGateDiff/2; i++,j--) {
			Wire w = pins[i];
			pins[i] = pins[j];
			pins[j] = w;
		}
	}
	private Wire hiDiff() {return pins[nbGateDiffPins()-1];}
	private Wire loDiff() {return pins[0];}

    // ---------- public methods ----------

	/** Transistor without body port. */
	public Mos(Function np, PartNameProxy name, VarContext cont, double width, double length,
			   Wire src, Wire gate, Wire drn) {
		this(np, name, cont, width, length, false, new Wire[] {src, gate, drn});
	}
	/** Transistor with body port. */
	public Mos(Function np, PartNameProxy name, VarContext cont, double width, double length,
			   Wire src, Wire gate, Wire drn, Wire body) {
		this(np, name, cont, width, length, true, new Wire[] {src, gate, drn, body});
	}
    @Override
    public double getLength() {return length;}
    @Override
    public double getWidth() {return width;}
	public int numSeries() {return nbGateDiffPins()-2;}
    @Override
	public int[] getPinCoeffs() {return pin_coeffs;}

    /** @return true if Wire w is connected to one or more gates */
	private boolean touchesSomeGate(Wire w){
		for (int i=1; i<nbGateDiffPins()-1; i++)  if (w==pins[i]) return true;
		return false;
	}

	/** @return true if Wire w connects to exactly one diffusion and w
	 *  doesn't connect to any gate
	 */
	public boolean touchesOnlyOneDiffAndNoGate(Wire w) {
		return (w==loDiff() ^ w==hiDiff()) &&
			   !touchesSomeGate(w);
	}

	/** @return true if Mos is a capacitor. That is both diffusions are connected */
	public boolean isCapacitor() {return pins[0]==pins[nbGateDiffPins()-1];}
//	public boolean isCapacitor()			// TODO: debug
//	{
//		boolean answer = pins[0]==pins[nbGateDiffPins()-1];
//		if (answer)
//		{
//			String msg = "A CAPACITOR WITH " + pins.length + " PINS:";
//			for(int i=0; i<pins.length; i++) msg += " "+pins[i].getName();
//			System.out.println(msg);
//		}
//		return answer;
//	}

	@Override
	public Integer hashCodeForParallelMerge() {
		// include how many Wires may be connected
		int hc = pins.length;
		// include what's connected
		for (int i=0; i<pins.length; i++)  
			hc += pins[i].hashCode() * pin_coeffs[i];
		// include the class
		hc += getClass().hashCode();
		// include whether its NMOS or PMOS
		hc += type().hashCode();
		return Integer.valueOf(hc);
	}

	// merge into this transistor
	@Override
	public boolean parallelMerge(Part p, NccOptions nccOpt){
		if(!(p instanceof Mos)) return false;
		Mos t= (Mos) p;
		if (this==t)return false; //same transistor

		if (!this.isLike(t, nccOpt)) return false; //different type

		// a different individual same type and length
		if (!samePinsAs(t)) return false;
		
		width += t.width;
		t.setDeleted();
		return true;		    	
	}
	@Override
	public int typeCode() {
		final int tw = Part.TYPE_FIELD_WIDTH;
		return type().ordinal() +
			   ((isCapacitor()?1:0) << tw) +
			   ((hasBody?1:0) << tw+1) +
			   (numSeries() << tw+2);
	}

	// ---------- printing methods ----------
	@Override
	public String typeString() {
		String t = type().getShortName();
		String c = isCapacitor() ? "_CAP" : "";
		String h = nbGateDiffPins()==3 ? "" : ("_"+(nbGateDiffPins()-2)+"stack");
		String s = hasBody ? "_withBody" : "";
		return t+c+h+s;
	}
	@Override
	public String valueDescription(){
		return "W=" + NccUtils.round(width,2) + " L=" + NccUtils.round(length, 2);
	}
	@Override
	public String connectionDescription(int n) {
		StringBuffer msg = new StringBuffer();
		for (int i=0; i<pins.length; i++) {
			if (i==0) {
				msg.append("S=");
			} else if (i==nbGateDiffPins()-1) {
				msg.append(" D=");
			} else if (hasBody && i==bodyNdx()) {
				msg.append(" B=");
			} else {
				if (nbGateDiffPins()==3) {
					msg.append(" G="); 
				} else {
					msg.append(" G"+i+"=");
				}
			}
			msg.append(pins[i].getName());
		}
		return msg.toString();
	}
	@Override
	public String connectionDescription(Wire w) {
		StringBuffer s = new StringBuffer();
		for (int i=0; i<pins.length; i++) {
			if (pins[i]!=w)  continue;
			if (s.length()!=0) s.append(",");
			if (i==0) {
				s.append("S");
			} else if (i==nbGateDiffPins()-1) {
				s.append("D"); 
			} else if (hasBody && i==bodyNdx()) {
				s.append("B");
			} else {
				if (nbGateDiffPins()==3) {
					s.append("G"); 
				} else {
					s.append("G"+i);
				}
			}
		}
		return s.toString();
	}

	/** Compare the type (N vs P) and the gate length
	 * @param t Transistor to compare to
	 * @return true if type and gate length match */
	public boolean isLike(Mos t, NccOptions nccOpt){
		return type()==t.type() && NccUtils.sizesMatch(length, t.length, nccOpt);
    }
	
	/** Merge two series Transistors into a single Transistor. 
	 * Tricky: Parts on wire may be deleted or replicated. 
	 * @param w wire joining diffusions of two transistors
	 * @return true if merge has taken place */
	public static boolean joinOnWire(Wire w, NccOptions nccOpt) {
		if (w.isDeleted()) return false;

		// make sure there are no Ports on wire
		if (w.getPort()!=null)  return false;
		
		// Use Set to remove duplicate Parts
		Set<Mos> trans = new HashSet<Mos>();
		for (Iterator<Part> it=w.getParts(); it.hasNext();) {
			Part p = it.next();
			if (p.isDeleted()) continue;
			if (!(p instanceof Mos)) return false;
			Mos t = (Mos) p;
			if (!t.touchesOnlyOneDiffAndNoGate(w)) return false;
			trans.add(t);
			if (trans.size()>2) return false;
		}
		if (trans.size()!=2) return false;

		Iterator<Mos> it = trans.iterator();
		Mos ta = it.next();
		Mos tb = it.next();
		error(ta.getParent()!=tb.getParent(), "mismatched parents?");
		if (!ta.isLike(tb, nccOpt))  return false;
		if (!NccUtils.sizesMatch(ta.width, tb.width, nccOpt))  return false;
		if (!ta.bodyMatches(tb)) return false;
		
		// it's a match - merge them into a stack
		if (ta.hiDiff()!=w) ta.flip();
		if (tb.loDiff()!=w) tb.flip();
		error(ta.hiDiff()!=w || tb.loDiff()!=w, "joinOnWire: diffusion connections corrupted");
		
		boolean hasBody = ta.hasBody;
		Wire[] mergedPins = new Wire[ta.nbGateDiffPins() + tb.nbGateDiffPins() - 2 + (hasBody?1:0)];
		int aNdx = 0;
		for (; aNdx<ta.nbGateDiffPins()-1; aNdx++) {
			mergedPins[aNdx] = ta.pins[aNdx];
		}
		for (int bNdx=1; bNdx<tb.nbGateDiffPins(); bNdx++){
			mergedPins[aNdx++] = tb.pins[bNdx];
		}
		if (hasBody) mergedPins[mergedPins.length-1] = ta.pins[ta.bodyNdx()];
		Mos stack = new Mos(ta.type(), ta.getNameProxy(), ta.getContext(),
							ta.getWidth(), ta.getLength(), 
							hasBody, mergedPins);

		Circuit parent = tb.getParent();
		parent.adopt(stack);
		ta.setDeleted();
		tb.setDeleted();
		//error(w.numParts()!=0, "wire not empty?");
		w.setDeleted();
		return true;
	}
    @Override
	public Integer computeHashCode(){
		// the function is symmetric: ABCD = DCBA
    	int nbGateDiff = nbGateDiffPins();
		int sumLo=0, sumHi=0;
		for (int i=0, j=nbGateDiff-1; i<(nbGateDiff+1)/2; i++,j--){
			sumLo += pins[i].getCode() * pin_coeffs[i];
			sumHi += pins[j].getCode() * pin_coeffs[j];
		}
		int sum = sumLo * sumHi;
		if (hasBody) sum += pins[bodyNdx()].getCode() * pin_coeffs[bodyNdx()];
		return Integer.valueOf(sum);
	}

}
