/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: TechEditWizardData.java
 * Create an Electric XML Technology from a simple numeric description of design rules
 * Written in Perl by Andrew Wewist, translated to Java by Steven Rubin.
 *
 * Copyright (c) 2008, 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.user.tecEditWizard2;

import com.sun.electric.database.geometry.*;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.io.IOTool;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.electric.tool.user.User;
import com.sun.electric.technology.*;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

/**
 * Class to handle the "Technology Creation Wizard" dialog.
 */
public class TechEditWizardData
{
    private static final boolean POLY_CONTACT_SAME_AS_DIFF_CONTACT = false;
    
	/************************************** THE DATA **************************************/

	private String tech_name;
	private String tech_description;
	private int num_metal_layers;
	private int stepsize;   // value in nm
    private double resolution; // technology resolution for delta values (not in real scale)
    private boolean pSubstrateProcess = false; // to control if process is a pwell or psubstrate process or not. If true, Tech Creation Wizard will not create pwell layers
    private boolean horizontalFlag = true; // to control if transistor gates are aligned horizontally. True by default . If transistors are horizontal -> M1 is horizontal?
    // 0=Basic is only with metal info, 0=Standard is with info for mocmos, 1=Complex with extra elements like resistors
    private int caseFlag = 0; // to control what information is stored: -1=basic, 0=standard, 1=complex
    private boolean analogElementsFlag = false; // to control if analog elements are generated. False by default
    private boolean secondPolyFlag = false; // to control if second poly. False by default

    // DIFFUSION RULES
	private WizardField diff_width = new WizardField();
	private WizardField diff_poly_overhang = new WizardField();		// min. diff overhang from gate edge
	private WizardField diff_contact_overhang = new WizardField();	// min. diff overhang contact
	private WizardField diff_contact_overhang_min_short = new WizardField();				// diff overhang contact. It should hold the min short value
	private WizardField diff_contact_overhang_min_long = new WizardField();				// diff overhang contact. It should hold the min long value
	private WizardField diff_spacing = new WizardField();

	// POLY RULES
	private WizardField poly_width = new WizardField();
	private WizardField poly_endcap = new WizardField();			// min. poly gate extension from edge of diffusion
	private WizardField poly_spacing = new WizardField();
	private WizardField poly_diff_spacing = new WizardField();		// min. spacing between poly and diffusion
    private WizardField poly_protection_spacing = new WizardField();		// min. spacing between poly and dummy poly

    // GATE RULES
	private WizardField gate_length = new WizardField();			// min. transistor gate length
	private WizardField gate_width = new WizardField();				// min. transistor gate width
	private WizardField gate_spacing = new WizardField();			// min. gate to gate spacing on diffusion
	private WizardField gate_contact_spacing = new WizardField();	// min. spacing from gate edge to contact inside diffusion

    // Special rules for OD18 transistors if specified.
    private WizardField gate_od18_length = new WizardField();       // transistor gate length for OD18 transistors
    private WizardField gate_od18_width = new WizardField();        // transistor gate width for OD18 transistors
    private WizardField[] od18_diff_overhang = new WizardField[]{new WizardField(), new WizardField()};             // OD18 X and Y overhang

    // Special rules for native transistors if specified
    private WizardField gate_nt_length  = new WizardField();        // transistor gate length for native transistors
    private WizardField gate_nt_width = new WizardField();          // transistor gate width for OD18 transistors
    private WizardField poly_nt_endcap = new WizardField();         // gate extension from edge of diffusion for native transistors
    private WizardField nt_diff_overhang = new WizardField();       // extension from OD

    // Special rules for vth/vtl transistors if specified.
    private WizardField vthl_diff_overhang  = new WizardField();    // Overhang of VTH/VTL with respecto to OD
    private WizardField vthl_poly_overhang = new WizardField();     // Overhang of VTH/VTL with respecto to the gate

    // CONTACT RULES
	private WizardField contact_size = new WizardField();
	private WizardField contact_spacing = new WizardField();
    private WizardField contact_array_spacing = new WizardField();
    private WizardField contact_metal_overhang_inline_only = new WizardField();	// metal overhang when overhanging contact from two sides only
	private WizardField contact_metal_overhang_all_sides = new WizardField();	// metal overhang when surrounding contact
	private WizardField contact_poly_overhang = new WizardField();				// poly overhang contact. It should hold the recommended value
	private WizardField polycon_diff_spacing = new WizardField();				// spacing between poly-metal contact edge and diffusion

	// WELL AND IMPLANT RULES
	private WizardField nplus_width = new WizardField();
	private WizardField nplus_overhang_diff = new WizardField();
	private WizardField nplus_overhang_strap = new WizardField();               // for well/substrate contact
	private WizardField nplus_overhang_poly = new WizardField();
	private WizardField nplus_spacing = new WizardField();

	private WizardField pplus_width = new WizardField();
	private WizardField pplus_overhang_diff = new WizardField();
	private WizardField pplus_overhang_strap = new WizardField();               // for well/substrate contact
	private WizardField pplus_overhang_poly = new WizardField();
	private WizardField pplus_spacing = new WizardField();

	private WizardField nwell_width = new WizardField();
	private WizardField nwell_overhang_diff_p = new WizardField();
    private WizardField nwell_overhang_diff_n = new WizardField();
    private WizardField nwell_spacing = new WizardField();

	// METAL RULES
	private WizardField [] metal_width;
	private WizardField [] metal_spacing;
    private List<WideWizardField> wide_metal_spacing = new ArrayList<WideWizardField>(); // For all wide spacing rules not displayed in graphcs

    // VIA RULES
	private WizardField [] via_size;
	private WizardField [] via_inline_spacing;
	private WizardField [] via_array_spacing;
	private WizardField [] via_overhang;

    // generic cross contacts
    private static class ContactNode
    {
        String layer;
        WizardField valueX; // overhang or size X value. Size value for cuts
        WizardField valueY; // overhang or size Y value. Size value for cuts

        ContactNode(String l, double valX, String nameX, double valY, String nameY)
        {
            layer = l;
            valueX = new WizardField(valX, nameX);
            valueY = new WizardField(valY, nameY);
        }
    }
    private static class Contact
    {
        // some primitives might not have prefix. "-" should not be in the prefix to avoid
        // being displayed in the palette
        String prefix;
        List<ContactNode> layers;

        // odd metals go vertical
        Contact (String p)
        {
            prefix = p;
            layers = new ArrayList<ContactNode>();
        }
    }

    private Map<String,List<Contact>> metalContacts = new HashMap<String,List<Contact>>();
    private Map<String,List<Contact>> otherContacts = new HashMap<String,List<Contact>>(); 
    private Map<String,List<Contact>> genericContacts = new HashMap<String,List<Contact>>();
    private Map<String,List<Contact>> capacitors = new HashMap<String,List<Contact>>();

    private static class PaletteGroup
    {
//        String name;
        List<Xml.ArcProto> arcs;
        List<Xml.MenuNodeInst> pins;
        List<Xml.MenuNodeInst> elements; // contact or transistor

        void addArc(Xml.ArcProto arc)
        {
            if (arcs == null)
            {
                arcs = new ArrayList<Xml.ArcProto>();
            }
            arcs.add(arc);
        }
        private void add(List<Xml.MenuNodeInst> list, Xml.PrimitiveNodeGroup element, String shortName)
        {
            assert element.isSingleton;
            Xml.PrimitiveNode pn = element.nodes.get(0);
            Xml.MenuNodeInst n = new Xml.MenuNodeInst();
            n.protoName = pn.name;
            n.function = pn.function;
            if (shortName != null)
            {
                n.text = shortName;
            }
            list.add(n);
        }
        void addPinOrResistor(Xml.PrimitiveNodeGroup pin, String shortName)
        {
            if (pins == null)
            {
                pins = new ArrayList<Xml.MenuNodeInst>();
            }
            add(pins, pin, shortName);
        }
        void addElement(Xml.PrimitiveNodeGroup element, String shortName)
        {
            if (elements == null)
            {
                elements = new ArrayList<Xml.MenuNodeInst>();
            }
            add(elements, element, shortName);
        }
    }

    // ANTENNA RULES
	private double poly_antenna_ratio;
	private double [] metal_antenna_ratio;

	// GDS-II LAYERS
    public static class LayerInfo
    {
        String name;
        int value; // normal gds value
        int type; // datatype of the normal gds value
        int pin; // gds pin value
        int pinType; // gds pin datatype
        int text; // gds text value
        int textType; // gds text datatype
        String cif; // cif value
        String graphicsTemplate; // uses other template for the graphics
        Color graphicsColor; // uses this color with no fill
        EGraphics.Outline graphicsOutline; // uses this outline with graphicsColor
        int [] graphicsPattern; // uses this pattern with graphicsColor
        double width = 0; // width of the pure layer node to create
        double height = -1; // height for 3D view
        double thickness = -1; // thickness for 3D view
        boolean addArc = false;
        WizardField spacing, minimum;
        Layer.Function function = Layer.Function.ART; // ART is better default than UNKNOWN
        PaletteGroup grp;  // to place extra elements for a given layer later in the palette

        LayerInfo(String n)
        {
            name = n;
        }
        String getValueWithType() {return (type != 0) ? value + "/" + type : value + "";}
        String getPinWithType() {return (pinType != 0) ? pin + "/" + pinType : pin + "";}
        String getTextWithType() {return (textType != 0) ? text + "/" + textType : text + "";}
        void setGDSData(int[] vals)
        {
            assert(vals.length == 6);
            value = vals[0];
            type = vals[1];
            pin = vals[2];
            pinType = vals[3];
            text = vals[4];
            textType = vals[5];
        }

        void setLayerInformation(String s)
        {
            StringTokenizer p = new StringTokenizer(s, ":", false);
            while (p.hasMoreTokens())
            {
                String str = p.nextToken();

                // Remove white spaces
                str = str.trim();

                int index = str.indexOf("=");
                if (str.startsWith("G"))
                {
                    setGDSData(getGDSValuesFromString(str.substring(index+1)));
                }
                else if (str.startsWith("W")) // width
                {
                    assert(index != -1);
                    width = Double.parseDouble(str.substring(index+1));
                }
                else if (str.startsWith("T")) // thick for 3D View
                {
                    assert(index != -1);
                    thickness = Double.parseDouble(str.substring(index+1));
                }
                else if (str.startsWith("H")) // height for 3D View
                {
                    assert(index != -1);
                    height = Double.parseDouble(str.substring(index+1));
                }
                else if (str.startsWith("S")) // spacing rule
                {
                    assert(index != -1);
                    spacing = new WizardField();
                    fillRule(str.substring(index+1), "{/}", spacing);
                }
                else if (str.startsWith("M")) // spacing rule
                {
                    assert(index != -1);
                    minimum = new WizardField();
                    fillRule(str.substring(index+1), "{/}", minimum);
                }
                else if (str.startsWith("A")) // for arcs
                {
                    assert (str.length() == 1 && str.toLowerCase().equals("a")); // It must be A
                    addArc = true;
                }
                else if (str.startsWith("F")) // function
                {
                    assert(index != -1);
                    function = Layer.Function.findFunction(str.substring(index+1));
                }
                else if (str.startsWith("CIF")) // CIF
                {
                    assert(index != -1);
                    cif = str.substring(index+1);
                }
                else if (str.startsWith("C")) // color
                {
                    assert(index != -1);
                    setGraphicsTemplate(str.substring(index+1));
                }
                else
                {
                    System.out.println("Case not implemented");
                    assert(false);
                }
            }
        }

        void setGraphicsTemplate(String s)
        {
            if (s.startsWith("[")) // color
            {
                StringTokenizer p = new StringTokenizer(s, ". []", false);
                int[] colors = new int[3];
                String outlineOrPattern = null; // EGraphics.Outline.NOPAT.name(); // default
                int itemCount = 0;
                while (p.hasMoreTokens())
                {
                    String str = p.nextToken();
                    if (itemCount < 3)
                        colors[itemCount++] = Integer.parseInt(str);
                    else
                        outlineOrPattern = str;
                    assert(itemCount < 4);
                }

                EGraphics.Outline outline = EGraphics.Outline.findOutline(EGraphics.Outline.NOPAT.name());
                int[] pattern = new int[16];

                graphicsColor = new Color(colors[0], colors[1], colors[2]);
                if (outlineOrPattern != null)
                {
                    EGraphics.Outline out = EGraphics.Outline.findOutline(outlineOrPattern);
                    if (out != null) // manages to parse a valid Outline
                        outline = out;
                    else
                    {
                        assert(outlineOrPattern.startsWith("{"));

                        // Pattern information
                        StringTokenizer pat = new StringTokenizer(outlineOrPattern, "/ {}", false);
                        int count = 0;
                        while (pat.hasMoreTokens())
                        {
                            String str = pat.nextToken();
                            int num = Integer.parseInt(str);
                            assert(count < 16);
                            pattern[count++] = num;
                        }
                        if (count != 16)
                        assert(count == 16);
                    }
                }
                graphicsOutline = outline;
                graphicsPattern = pattern;
            }
            else
                graphicsTemplate = s;
        }

        public String toString()
        {
            String val = getValueWithType(); // useful datatype
            if (pin != 0)
            {
                val = val + "," + getPinWithType() + "p";
            }
            if (text != 0)
            {
                val = val + "," + getTextWithType() + "t";
            }
            return val;
        }
    }

    private LayerInfo diff_layer = new LayerInfo("Diff");
    private LayerInfo poly_layer = new LayerInfo("Poly");
    private LayerInfo poly2_layer = new LayerInfo("Polysilicon-2");
    private LayerInfo nplus_layer = new LayerInfo("NPlus");
	private LayerInfo pplus_layer = new LayerInfo("PPlus");
	private LayerInfo nwell_layer = new LayerInfo("N-Well");
	private LayerInfo contact_layer = new LayerInfo("Contact");
	private LayerInfo [] metal_layers;
	private LayerInfo [] via_layers;
    private LayerInfo marking_layer = new LayerInfo("DeviceMark");		// Device marking layer
    private List<LayerInfo> extraLayers; // extra layers

    LayerInfo[] getBasicLayers()
    {
        List<LayerInfo> layers = new ArrayList<LayerInfo>();

        layers.add(diff_layer);
        layers.add(poly_layer);
        layers.addAll(extraLayers); // nothing added if list is empty
        layers.add(nplus_layer);
        layers.add(pplus_layer);
        layers.add(nwell_layer);
        layers.add(contact_layer);
        layers.add(marking_layer);
        LayerInfo[] array = new LayerInfo[layers.size()];
        layers.toArray(array);
        return array;
    }

    public TechEditWizardData()
	{
		stepsize = 100;
        num_metal_layers = 2;
        allocateVariables();
    }
    
    private void allocateVariables()
    {
    	// Garbage collector will deal with previouly allocated variables.
    	// Not worth it to clear them
		metal_width = new WizardField[num_metal_layers];
		metal_spacing = new WizardField[num_metal_layers];
		via_size = new WizardField[num_metal_layers-1];
		via_inline_spacing = new WizardField[num_metal_layers-1];
		via_array_spacing = new WizardField[num_metal_layers-1];
		via_overhang = new WizardField[num_metal_layers-1];
		metal_antenna_ratio = new double[num_metal_layers];
        metal_layers = new LayerInfo[num_metal_layers];
		via_layers = new LayerInfo[num_metal_layers-1];

        for(int i=0; i<num_metal_layers; i++)
		{
			metal_width[i] = new WizardField();
			metal_spacing[i] = new WizardField();
            metal_layers[i] = new LayerInfo("Metal-"+(i+1));
        }

        for(int i=0; i<num_metal_layers-1; i++)
		{
			via_size[i] = new WizardField();
			via_inline_spacing[i] = new WizardField();
			via_array_spacing[i] = new WizardField();
			via_overhang[i] = new WizardField();
            via_layers[i] = new LayerInfo("Via-"+(i+1));
        }

        // extra layers
        extraLayers = new ArrayList<LayerInfo>();
    }

	/************************************** ACCESSOR METHODS **************************************/

	String getTechName() { return tech_name; }
	void setTechName(String s) { tech_name = s; }

	String getTechDescription() { return tech_description; }
	void setTechDescription(String s) { tech_description = s; }

	int getStepSize() { return stepsize; }
	void setStepSize(int n) { stepsize = n; }

    double getResolution() { return resolution; }
	void setResolution(double n) { resolution = n; }

    int getNumMetalLayers() { return num_metal_layers; }
	void setNumMetalLayers(int n)
	{
        if (n < 1)
        {
            System.out.println("Setting zero as number of metal layers");
            return; // nothing to do
        }
        int smallest = Math.min(n, num_metal_layers);
        
        WizardField [] new_metal_width = new WizardField[n];
		for(int i=0; i<smallest; i++) new_metal_width[i] = metal_width[i];
		for(int i=smallest; i<n; i++) new_metal_width[i] = new WizardField();
		metal_width = new_metal_width;

        WizardField [] new_metal_spacing = new WizardField[n];
		for(int i=0; i<smallest; i++) new_metal_spacing[i] = metal_spacing[i];
		for(int i=smallest; i<n; i++) new_metal_spacing[i] = new WizardField();
		metal_spacing = new_metal_spacing;

		WizardField [] new_via_size = new WizardField[n-1];
		for(int i=0; i<smallest-1; i++) new_via_size[i] = via_size[i];
		for(int i=smallest-1; i<n-1; i++) new_via_size[i] = new WizardField();
		via_size = new_via_size;

		WizardField [] new_via_spacing = new WizardField[n-1];
		for(int i=0; i<smallest-1; i++) new_via_spacing[i] = via_inline_spacing[i];
		for(int i=smallest-1; i<n-1; i++) new_via_spacing[i] = new WizardField();
		via_inline_spacing = new_via_spacing;

		WizardField [] new_via_array_spacing = new WizardField[n-1];
		for(int i=0; i<smallest-1; i++) new_via_array_spacing[i] = via_array_spacing[i];
		for(int i=smallest-1; i<n-1; i++) new_via_array_spacing[i] = new WizardField();
		via_array_spacing = new_via_array_spacing;

		WizardField [] new_via_overhang_inline = new WizardField[n-1];
		for(int i=0; i<smallest-1; i++) new_via_overhang_inline[i] = via_overhang[i];
		for(int i=smallest-1; i<n-1; i++) new_via_overhang_inline[i] = new WizardField();
		via_overhang = new_via_overhang_inline;

        double [] new_metal_antenna_ratio = new double[n];
		for(int i=0; i<smallest; i++) new_metal_antenna_ratio[i] = metal_antenna_ratio[i];
		metal_antenna_ratio = new_metal_antenna_ratio;

		LayerInfo [] new_gds_metal_layer = new LayerInfo[n];
        for(int i=0; i<smallest; i++)
        {
            new_gds_metal_layer[i] = metal_layers[i];
        }
        for(int i=smallest-1; i<n; i++)
        {
            new_gds_metal_layer[i] = new LayerInfo("Metal-"+(i+1));
        }
        metal_layers = new_gds_metal_layer;

        LayerInfo [] new_gds_via_layer = new LayerInfo[n-1];
		for(int i=0; i<smallest-1; i++) new_gds_via_layer[i] = via_layers[i];
        for(int i=smallest-1; i<n-1; i++) new_gds_via_layer[i] = new LayerInfo("Via-"+(i+1));
        via_layers = new_gds_via_layer;

		num_metal_layers = n;
	}

    // Flags
    boolean getPSubstratelProcess() { return pSubstrateProcess;}
    void setPSubstratelProcess(boolean b) { pSubstrateProcess = b; }
    boolean getHorizontalTransistors() { return horizontalFlag;}
    void setHorizontalTransistors(boolean b) { horizontalFlag = b; }
    private boolean isComplexCase() { return caseFlag == 1;} // complex case
    private boolean isBasicCase() { return caseFlag == -1;} // basic case = only metals
    private void setCaseFlag(int b) { assert(b > -2 && b < 2); caseFlag = b; }  // -1, 0, 1
    private boolean getAnalogFlag() { return analogElementsFlag;}
    private void setAnalogFlag(boolean b) { analogElementsFlag = b; }
    private boolean getSecondPolyFlag() { return secondPolyFlag;}
    private void setSecondPolyFlag(boolean b) { secondPolyFlag = b; }

    // DIFFUSION RULES
	WizardField getDiffWidth() { return diff_width; }
	void setDiffWidth(WizardField v) { diff_width = v; }
	WizardField getDiffPolyOverhang() { return diff_poly_overhang; }
	void setDiffPolyOverhang(WizardField v) { diff_poly_overhang = v; }
	WizardField getDiffContactOverhang() { return diff_contact_overhang; }
	void setDiffContactOverhang(WizardField v) { diff_contact_overhang = v; }
	WizardField getDiffSpacing() { return diff_spacing; }
	void setDiffSpacing(WizardField v) { diff_spacing = v; }

	// POLY RULES
	WizardField getPolyWidth() { return poly_width; }
	void setPolyWidth(WizardField v) { poly_width = v; }
	WizardField getPolyEndcap() { return poly_endcap; }
	void setPolyEndcap(WizardField v) { poly_endcap = v; }
	WizardField getPolySpacing() { return poly_spacing; }
	void setPolySpacing(WizardField v) { poly_spacing = v; }
	WizardField getPolyDiffSpacing() { return poly_diff_spacing; }
	void setPolyDiffSpacing(WizardField v) { poly_diff_spacing = v; }
    WizardField getPolyProtectionSpacing() { return poly_protection_spacing; }
	void setPolyProtectionSpacing(WizardField v) { poly_protection_spacing = v; }

    // GATE RULES
	WizardField getGateLength() { return gate_length; }
	void setGateLength(WizardField v) { gate_length = v; }
	WizardField getGateWidth() { return gate_width; }
	void setGateWidth(WizardField v) { gate_width = v; }
	WizardField getGateSpacing() { return gate_spacing; }
	void setGateSpacing(WizardField v) { gate_spacing = v; }
	WizardField getGateContactSpacing() { return gate_contact_spacing; }
	void setGateContactSpacing(WizardField v) { gate_contact_spacing = v; }


    // CONTACT RULES
	WizardField getContactSize() { return contact_size; }
	void setContactSize(WizardField v) { contact_size = v; }
	WizardField getContactSpacing() { return contact_spacing; }
	void setContactSpacing(WizardField v) { contact_spacing = v; }
    WizardField getContactArraySpacing() { return contact_array_spacing; }
	void setContactArraySpacing(WizardField v) { contact_array_spacing = v; }
    WizardField getContactMetalOverhangInlineOnly() { return contact_metal_overhang_inline_only; }
	void setContactMetalOverhangInlineOnly(WizardField v) { contact_metal_overhang_inline_only = v; }
	WizardField getContactMetalOverhangAllSides() { return contact_metal_overhang_all_sides; }
	void setContactMetalOverhangAllSides(WizardField v) { contact_metal_overhang_all_sides = v; }
	WizardField getContactPolyOverhang() { return contact_poly_overhang; }
	void setContactPolyOverhang(WizardField v) { contact_poly_overhang = v; }
	WizardField getPolyconDiffSpacing() { return polycon_diff_spacing; }
	void setPolyconDiffSpacing(WizardField v) { polycon_diff_spacing = v; }

	// WELL AND IMPLANT RULES
	WizardField getNPlusWidth() { return nplus_width; }
	void setNPlusWidth(WizardField v) { nplus_width = v; }
	WizardField getNPlusOverhangDiff() { return nplus_overhang_diff; }
    void setNPlusOverhangDiff(WizardField v) { nplus_overhang_diff = v; }
	WizardField getNPlusOverhangStrap() { return nplus_overhang_strap; }
    void setNPlusOverhangStrap(WizardField v) { nplus_overhang_strap = v; }
    WizardField getNPlusOverhangPoly() { return nplus_overhang_poly; }
    void setNPlusOverhangPoly(WizardField v) { nplus_overhang_poly = v; }
	WizardField getNPlusSpacing() { return nplus_spacing; }
	void setNPlusSpacing(WizardField v) { nplus_spacing = v; }

	WizardField getPPlusWidth() { return pplus_width; }
	void setPPlusWidth(WizardField v) { pplus_width = v; }
	WizardField getPPlusOverhangDiff() { return pplus_overhang_diff; }
	void setPPlusOverhangDiff(WizardField v) { pplus_overhang_diff = v; }
	WizardField getPPlusOverhangStrap() { return pplus_overhang_strap; }
	void setPPlusOverhangStrap(WizardField v) { pplus_overhang_strap = v; }
	WizardField getPPlusOverhangPoly() { return pplus_overhang_poly; }
	void setPPlusOverhangPoly(WizardField v) { pplus_overhang_poly = v; }
	WizardField getPPlusSpacing() { return pplus_spacing; }
	void setPPlusSpacing(WizardField v) { pplus_spacing = v; }

	WizardField getNWellWidth() { return nwell_width; }
	void setNWellWidth(WizardField v) { nwell_width = v; }
	WizardField getNWellOverhangDiffP() { return nwell_overhang_diff_p; }
	void setNWellOverhangDiffP(WizardField v) { nwell_overhang_diff_p = v; }
	WizardField getNWellOverhangDiffN() { return nwell_overhang_diff_n; }
	void setNWellOverhangDiffN(WizardField v) { nwell_overhang_diff_n = v; }
	WizardField getNWellSpacing() { return nwell_spacing; }
	void setNWellSpacing(WizardField v) { nwell_spacing = v; }

	// METAL RULES
	WizardField [] getMetalWidth() { return metal_width; }
	void setMetalWidth(int met, WizardField value) { metal_width[met] = value; }
	WizardField [] getMetalSpacing() { return metal_spacing; }
	void setMetalSpacing(int met, WizardField value) { metal_spacing[met] = value; }

	// VIA RULES
	WizardField [] getViaSize() { return via_size; }
	void setViaSize(int via, WizardField value) { via_size[via] = value; }
	WizardField [] getViaSpacing() { return via_inline_spacing; }
	void setViaSpacing(int via, WizardField value) { via_inline_spacing[via] = value; }
	WizardField [] getViaArraySpacing() { return via_array_spacing; }
	void setViaArraySpacing(int via, WizardField value) { via_array_spacing[via] = value; }
	WizardField [] getViaOverhangInline() { return via_overhang; }
	void setViaOverhangInline(int via, WizardField value) { via_overhang[via] = value; }

	// ANTENNA RULES
	public double getPolyAntennaRatio() { return poly_antenna_ratio; }
	void setPolyAntennaRatio(double v) { poly_antenna_ratio = v; }
	public double [] getMetalAntennaRatio() { return metal_antenna_ratio; }
	void setMetalAntennaRatio(int met, double value) { metal_antenna_ratio[met] = value; }

	// GDS-II LAYERS
    static int[] getGDSValuesFromString(String s)
    {
        int[] vals = new int[6];
        StringTokenizer parse = new StringTokenizer(s, ",", false);

        while (parse.hasMoreTokens())
        {
            String v = parse.nextToken();
            int pos = 0;
            int index = v.indexOf("/");

            if (v.contains("p")) // pin section
            {
                pos = 2;
            } else if (v.contains("t")) // text section
            {
                pos = 4;
            }
            if (index != -1) // datatype value
            {
                vals[pos] = TextUtils.atoi(v.substring(0, index));
                vals[pos+1] = TextUtils.atoi(v.substring(index+1));
            }
            else
                vals[pos] = TextUtils.atoi(v);
        }
        return vals;
    }

	TechEditWizardData.LayerInfo [] getGDSMetal() { return metal_layers; }
	TechEditWizardData.LayerInfo [] getGDSVia() { return via_layers; }

	private String errorInData()
	{
		// check the General data
		if (tech_name == null || tech_name.length() == 0) return "General panel: No technology name";
		if (stepsize == 0) return "General panel: Invalid unit size";

        if (!isBasicCase())  // at least standard case
        {
            // check the Active data
            if (diff_width.value == 0) return "Active panel: Invalid width";

            // check the Poly data
            if (poly_width.value == 0) return "Poly panel: Invalid width";

            // check the Gate data
            if (gate_width.value == 0) return "Gate panel: Invalid width";
            if (gate_length.value == 0) return "Gate panel: Invalid length";

            // check the Contact data
            if (contact_size.value == 0) return "Contact panel: Invalid size";

            // check the Well/Implant data
            if (nplus_width.value == 0) return "Well/Implant panel: Invalid NPlus width";
            if (pplus_width.value == 0) return "Well/Implant panel: Invalid PPlus width";
            if (nwell_width.value == 0) return "Well/Implant panel: Invalid NWell width";   
        }

		// check the Metal data
		for(int i=0; i<num_metal_layers; i++)
			if (metal_width[i].value == 0) return "Metal panel: Invalid Metal-" + (i+1) + " width";

		// check the Via data
		for(int i=0; i<num_metal_layers-1; i++)
			if (via_size[i].value == 0) return "Via panel: Invalid Via-" + (i+1) + " size";
		return null;
	}

	/************************************** IMPORT RAW NUMBERS FROM DISK **************************************/

	/**
	 * Method to import data from a file to this object.
	 * @return true on success; false on failure.
	 */
	boolean importData()
	{
		String fileName = OpenFile.chooseInputFile(FileType.ANY, "Technology Wizard File", null);
		if (fileName == null) return false;
        return importData(fileName);
    }

    /**
     * Method to import data from a given file to this object. It is also in the regression so
     * keep the access.
     * @param fileName the name of the file to import.
     * @return true on success; false on failure.
     */
    public boolean importData(String fileName)
    {
        URL url = TextUtils.makeURLToFile(fileName);

        // clean containers before reading the next txt file
        allocateVariables();
        
        try
		{
			URLConnection urlCon = url.openConnection();
			InputStreamReader is = new InputStreamReader(urlCon.getInputStream());
			LineNumberReader lineReader = new LineNumberReader(is);
			for(;;)
			{
				String buf = lineReader.readLine();
				if (buf == null) break;
				buf = buf.trim();
				if (buf.length() == 0 || buf.startsWith("#")) continue;

				// parse the assignment
				if (buf.startsWith("$") || buf.startsWith("@"))
				{
					int spacePos = buf.indexOf(' ');
					int equalsPos = buf.indexOf('=');
					if (equalsPos < 0)
					{
						Job.getUserInterface().showErrorMessage("Missing '=' on line " + lineReader.getLineNumber(),
							"Syntax Error In Technology File");
						break;
					}
					if (spacePos < 0) spacePos = equalsPos; else
						spacePos = Math.min(spacePos, equalsPos);
					String varName = buf.substring(1, spacePos);
                    boolean single = buf.startsWith("$");
					int semiPos = buf.indexOf(';');
					if (semiPos < 0)
					{
						Job.getUserInterface().showErrorMessage("Missing ';' on line " + lineReader.getLineNumber(),
							"Syntax Error In Technology File");
						break;
					}
					equalsPos++;
					while (equalsPos < semiPos && buf.charAt(equalsPos) == ' ') equalsPos++;
					String varValue = buf.substring(equalsPos, semiPos);

					// now figure out what to assign
					if (varName.equalsIgnoreCase("tech_libname")) { } else
					if (varName.equalsIgnoreCase("tech_name")) setTechName(stripQuotes(varValue)); else
					if (varName.equalsIgnoreCase("tech_description")) setTechDescription(stripQuotes(varValue)); else
					if (varName.equalsIgnoreCase("num_metal_layers")) setNumMetalLayers(TextUtils.atoi(varValue)); else
                    if (varName.equalsIgnoreCase("psubstrate_process")) setPSubstratelProcess(Boolean.valueOf(varValue)); else
                    if (varName.equalsIgnoreCase("horizontal_transistors")) setHorizontalTransistors(Boolean.valueOf(varValue)); else
                    if (varName.equalsIgnoreCase("extra_info")) setCaseFlag(Integer.valueOf(varValue)); else
                    if (varName.equalsIgnoreCase("stepsize")) setStepSize(TextUtils.atoi(varValue)); else
                    if (varName.equalsIgnoreCase("resolution")) setResolution(TextUtils.atof(varValue)); else
                    if (varName.equalsIgnoreCase("analog_elements")) setAnalogFlag(Boolean.valueOf(varValue)); else
                    if (varName.equalsIgnoreCase("second_poly")) setSecondPolyFlag(Boolean.valueOf(varValue)); else

                    if (varName.equalsIgnoreCase("diff_width")) diff_width.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("diff_width_rule")) diff_width.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("diff_poly_overhang")) diff_poly_overhang.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("diff_poly_overhang_rule")) diff_poly_overhang.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("diff_contact_overhang")) diff_contact_overhang.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("diff_contact_overhang_rule")) diff_contact_overhang.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("diff_contact_overhang_short_min")) diff_contact_overhang_min_short.value = TextUtils.atof(varValue); else
                    if (varName.equalsIgnoreCase("diff_contact_overhang_short_min_rule")) diff_contact_overhang_min_short.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("diff_contact_overhang_long_min")) diff_contact_overhang_min_long.value = TextUtils.atof(varValue); else
                    if (varName.equalsIgnoreCase("diff_contact_overhang_long_min_rule")) diff_contact_overhang_min_long.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("diff_spacing")) diff_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("diff_spacing_rule")) diff_spacing.rule = stripQuotes(varValue); else

					if (varName.equalsIgnoreCase("poly_width")) poly_width.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("poly_width_rule")) poly_width.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("poly_endcap")) poly_endcap.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("poly_endcap_rule")) poly_endcap.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("poly_spacing")) poly_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("poly_spacing_rule")) poly_spacing.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("poly_diff_spacing")) poly_diff_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("poly_diff_spacing_rule")) poly_diff_spacing.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("poly_protection_spacing")) poly_protection_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("poly_protection_spacing_rule")) poly_protection_spacing.rule = stripQuotes(varValue); else

                    if (varName.equalsIgnoreCase("gate_length")) gate_length.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("gate_length_rule")) gate_length.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("gate_width")) gate_width.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("gate_width_rule")) gate_width.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("gate_spacing")) gate_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("gate_spacing_rule")) gate_spacing.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("gate_contact_spacing")) gate_contact_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("gate_contact_spacing_rule")) gate_contact_spacing.rule = stripQuotes(varValue); else

                    if (varName.equalsIgnoreCase("gate_od18_length")) fillRule(varValue, gate_od18_length); else
                    if (varName.equalsIgnoreCase("gate_od18_width")) fillRule(varValue, gate_od18_width); else
                    if (varName.equalsIgnoreCase("od18_diff_overhang")) fillRule(varValue, od18_diff_overhang); else

                    if (varName.equalsIgnoreCase("gate_nt_length")) fillRule(varValue, gate_nt_length); else
                    if (varName.equalsIgnoreCase("gate_nt_width")) fillRule(varValue, gate_nt_width); else
                    if (varName.equalsIgnoreCase("poly_nt_endcap")) fillRule(varValue, poly_nt_endcap); else
                    if (varName.equalsIgnoreCase("nt_diff_overhang")) fillRule(varValue, nt_diff_overhang); else

                    if (varName.equalsIgnoreCase("vthl_diff_overhang")) fillRule(varValue, vthl_diff_overhang); else
                    if (varName.equalsIgnoreCase("vthl_poly_overhang")) fillRule(varValue, vthl_poly_overhang); else

                    if (varName.equalsIgnoreCase("contact_size")) contact_size.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("contact_size_rule")) contact_size.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("contact_spacing")) contact_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("contact_spacing_rule")) contact_spacing.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("contact_array_spacing")) contact_array_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("contact_array_spacing_rule")) contact_array_spacing.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("contact_metal_overhang_inline_only")) contact_metal_overhang_inline_only.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("contact_metal_overhang_inline_only_rule")) contact_metal_overhang_inline_only.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("contact_metal_overhang_all_sides")) contact_metal_overhang_all_sides.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("contact_metal_overhang_all_sides_rule")) contact_metal_overhang_all_sides.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("contact_poly_overhang")) contact_poly_overhang.value = TextUtils.atof(varValue); else
                    if (varName.equalsIgnoreCase("contact_poly_overhang_rule")) contact_poly_overhang.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("polycon_diff_spacing")) polycon_diff_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("polycon_diff_spacing_rule")) polycon_diff_spacing.rule = stripQuotes(varValue); else

					if (varName.equalsIgnoreCase("nplus_width")) nplus_width.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nplus_width_rule")) nplus_width.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("nplus_overhang_diff")) nplus_overhang_diff.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nplus_overhang_diff_rule")) nplus_overhang_diff.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("nplus_overhang_strap")) nplus_overhang_strap.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nplus_overhang_strap_rule")) nplus_overhang_strap.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("nplus_overhang_poly")) nplus_overhang_poly.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nplus_overhang_poly_rule")) nplus_overhang_poly.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("nplus_spacing")) nplus_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nplus_spacing_rule")) nplus_spacing.rule = stripQuotes(varValue); else

					if (varName.equalsIgnoreCase("pplus_width")) pplus_width.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("pplus_width_rule")) pplus_width.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("pplus_overhang_diff")) pplus_overhang_diff.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("pplus_overhang_diff_rule")) pplus_overhang_diff.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("pplus_overhang_strap")) pplus_overhang_strap.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("pplus_overhang_strap_rule")) pplus_overhang_strap.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("pplus_overhang_poly")) pplus_overhang_poly.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("pplus_overhang_poly_rule")) pplus_overhang_poly.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("pplus_spacing")) pplus_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("pplus_spacing_rule")) pplus_spacing.rule = stripQuotes(varValue); else

					if (varName.equalsIgnoreCase("nwell_width")) nwell_width.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nwell_width_rule")) nwell_width.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("nwell_overhang_diff_p")) nwell_overhang_diff_p.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nwell_overhang_diff_rule_p")) nwell_overhang_diff_p.rule = stripQuotes(varValue); else
					if (varName.equalsIgnoreCase("nwell_overhang_diff_n")) nwell_overhang_diff_n.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nwell_overhang_diff_rule_n")) nwell_overhang_diff_n.rule = stripQuotes(varValue); else
                    if (varName.equalsIgnoreCase("nwell_spacing")) nwell_spacing.value = TextUtils.atof(varValue); else
					if (varName.equalsIgnoreCase("nwell_spacing_rule")) nwell_spacing.rule = stripQuotes(varValue); else

					if (varName.equalsIgnoreCase("metal_width")) fillWizardArray(varValue, metal_width, false); else
					if (varName.equalsIgnoreCase("metal_width_rule")) fillWizardArray(varValue, metal_width, true); else
					if (varName.equalsIgnoreCase("metal_spacing")) fillWizardArray(varValue, metal_spacing, false); else
					if (varName.equalsIgnoreCase("metal_spacing_rule")) fillWizardArray(varValue, metal_spacing, true); else
                    if (varName.equalsIgnoreCase("wide_metal_spacing_rules")) fillWizardWideArray(varValue, wide_metal_spacing); else
                    if (varName.equalsIgnoreCase("via_size")) fillWizardArray(varValue, via_size, false); else
					if (varName.equalsIgnoreCase("via_size_rule")) fillWizardArray(varValue, via_size, true); else
					if (varName.equalsIgnoreCase("via_spacing")) fillWizardArray(varValue, via_inline_spacing, false); else
					if (varName.equalsIgnoreCase("via_spacing_rule")) fillWizardArray(varValue, via_inline_spacing, true); else
					if (varName.equalsIgnoreCase("via_array_spacing")) fillWizardArray(varValue, via_array_spacing, false); else
					if (varName.equalsIgnoreCase("via_array_spacing_rule")) fillWizardArray(varValue, via_array_spacing, true); else
					if (varName.equalsIgnoreCase("via_overhang_inline")) fillWizardArray(varValue, via_overhang, false); else
					if (varName.equalsIgnoreCase("via_overhang_inline_rule")) fillWizardArray(varValue, via_overhang, true); else

                    if (varName.equalsIgnoreCase("metal_contacts_series")) fillContactSeries(varValue, metalContacts); else
                    if (varName.equalsIgnoreCase("contacts_series")) fillContactSeries(varValue, otherContacts); else
                    // capacitors
                    if (varName.equalsIgnoreCase("capacitors_series")) fillContactSeries(varValue, capacitors); else
                    // more generic contacts that are not multicuts.
                    if (varName.equalsIgnoreCase("nomulti_contacts_series")) fillContactSeries(varValue, genericContacts); else
                    // Special layers
                    if (varName.equalsIgnoreCase("extra_layers")) fillLayerSeries(varValue, extraLayers); else
                        
                    if (varName.equalsIgnoreCase("poly_antenna_ratio")) setPolyAntennaRatio(TextUtils.atof(varValue)); else
					if (varName.equalsIgnoreCase("metal_antenna_ratio")) metal_antenna_ratio = makeDoubleArray(varValue); else

					if (varName.equalsIgnoreCase("gds_diff_layer")) diff_layer.setGDSData(getGDSValuesFromString(varValue)); else
					if (varName.equalsIgnoreCase("gds_poly_layer")) poly_layer.setGDSData(getGDSValuesFromString(varValue)); else
                    if (varName.equalsIgnoreCase("gds_nplus_layer")) nplus_layer.setGDSData(getGDSValuesFromString(varValue)); else
					if (varName.equalsIgnoreCase("gds_pplus_layer")) pplus_layer.setGDSData(getGDSValuesFromString(varValue)); else
					if (varName.equalsIgnoreCase("gds_nwell_layer")) nwell_layer.setGDSData(getGDSValuesFromString(varValue)); else
                    if (varName.equalsIgnoreCase("gds_contact_layer")) contact_layer.setGDSData(getGDSValuesFromString(varValue)); else
					if (varName.equalsIgnoreCase("gds_metal_layer")) metal_layers = setGDSDataArray(varValue, num_metal_layers, "Metal-"); else
					if (varName.equalsIgnoreCase("gds_via_layer")) via_layers = setGDSDataArray(varValue, num_metal_layers - 1, "Via-"); else
                    if (varName.equalsIgnoreCase("gds_marking_layer")) marking_layer.setGDSData(getGDSValuesFromString(varValue));
                    else
                    {
                        Object obj = null;
                        if (single)
                        {
                            WizardField wf = findWizardField(varName);
                            if (wf != null)
                            {
                                fillRule(varValue, wf);
                            }
                            obj = wf;
                        }
                        else
                        {
                            // two rules in the definition
                            WizardRuleFields wfs = findWizardRuleFields(varName);
                            if (wfs != null)
                            {
                                fillRules(varValue, wfs);
                            }
                            obj = wfs;
                        }
                        if (obj == null)
                        {
                            Job.getUserInterface().showErrorMessage("Unknown keyword '" + varName + "' on line " + lineReader.getLineNumber(),
                                "Syntax Error In Technology File");
                            break;
                        }
                    }
				}
			}
			lineReader.close();
		} catch (IOException e)
		{
			System.out.println("Error reading " + fileName);
			return false;
		}
		return true;
	}

    private static List<WizardField> extraVariables = new ArrayList<WizardField>();
    private WizardField findWizardField(String varName)
    {
        for (WizardField wf : extraVariables)
        {
            if (wf.name.equals(varName))
                return wf;
        }
        // it doesn't exist yet. Adding it now.
        WizardField w = new WizardField(varName);
        extraVariables.add(w);
        return w;
    }

    private static List<WizardRuleFields> extraDoubleFields = new ArrayList<WizardRuleFields>();    
    private WizardRuleFields findWizardRuleFields(String varName)
    {
        for (WizardRuleFields wf : extraDoubleFields)
        {
            if (wf.name.equals(varName))
                return wf;
        }
        // it doesn't exist yet. Adding it now.
        WizardRuleFields w = new WizardRuleFields(varName);
        extraDoubleFields.add(w);
        return w;
    }

    private String stripQuotes(String str)
	{
		if (str.startsWith("\"") && str.endsWith("\""))
			return str.substring(1, str.length()-1);
		return str;
	}

	private LayerInfo [] setGDSDataArray(String str, int len, String extra)
	{
		LayerInfo [] foundArray = new LayerInfo[len];
		for(int i=0; i<len; i++) foundArray[i] = new LayerInfo(extra + (i+1));
        StringTokenizer parse = new StringTokenizer(str, "( \")", false);
        int count = 0;
        while (parse.hasMoreTokens())
        {
            if (count >= len)
            {
                System.out.println("More GDS values than metal layers in TechEditWizardData");
                break;
            }
            else
            {
                String value = parse.nextToken();
                // array delimeters must be discarded here because GDS string may
                // contain "," for the pin/text definition ("," can't be used in the StringTokenizer
                if (!value.equals(","))
                    foundArray[count++].setGDSData(getGDSValuesFromString(value));
            }
        }
        return foundArray;
	}

	private double [] makeDoubleArray(String str)
	{
		WizardField [] foundArray = new WizardField[num_metal_layers];
		for(int i=0; i<num_metal_layers; i++) foundArray[i] = new WizardField();
		fillWizardArray(str, foundArray, false);
		double [] retArray = new double[foundArray.length];
		for(int i=0; i<foundArray.length; i++)
			retArray[i] = foundArray[i].value;
		return retArray;
	}

    private void fillWizardWideArray(String str, List<WideWizardField> wideList)
	{
        StringTokenizer parse = new StringTokenizer(str, "[]", false);
        int blocks = 0;
        WideWizardField tmp = new WideWizardField();

        while (parse.hasMoreTokens())
        {
            String value = parse.nextToken();

            if (value.equals(";")) // end of line
                continue;

            // first block: [value, maxW, minLen, rule name]
            if (blocks == 0)
            {
                StringTokenizer p = new StringTokenizer(value, ", ", false);
                int count = 0;

                while (p.hasMoreTokens())
                {
                    String v = p.nextToken();
                    switch (count)
                    {
                        case 0: // value
                            tmp.value = Double.parseDouble(v);
                            break;
                        case 1: // maxW
                            tmp.maxW = Double.parseDouble(v);
                            break;
                        case 2: // minLen
                            tmp.minLen = Double.parseDouble(v);
                            break;
                        case 3: // rule name
                            tmp.rule = stripQuotes(v);
                            break;
                        default:
                            assert(false); // only 4 values
                    }
                    count++;
                }
                blocks++;
            }
            else
            {
                // layers involved
                StringTokenizer p = new StringTokenizer(value, ",", false);

                while (p.hasMoreTokens())
                {
                    String s = p.nextToken();
                    tmp.names.add(s);
                }
            }
        }
        wideList.add(tmp);
    }

    private void fillWizardArray(String str, WizardField[] fieldArray, boolean getRule)
	{
		if (!str.startsWith("("))
		{
			Job.getUserInterface().showErrorMessage("Array does not start with '(' on " + str,
				"Syntax Error In Technology File");
			return;
		}

		int pos = 1;
		int index = 0;
		for(;;)
		{
			while (pos < str.length() && str.charAt(pos) == ' ') pos++;

            if (index >= fieldArray.length)
            {
//                 Job.getUserInterface().showErrorMessage("Invalid metal index: " + index,
//						"Syntax Error In Technology File");
					return;
            }
            if (getRule)
			{
				if (str.charAt(pos) != '"')
				{
					Job.getUserInterface().showErrorMessage("Rule element does not start with quote on " + str,
						"Syntax Error In Technology File");
					return;
				}
				pos++;
				int end = pos;
				while (end < str.length() && str.charAt(end) != '"') end++;
				if (str.charAt(end) != '"')
				{
					Job.getUserInterface().showErrorMessage("Rule element does not end with quote on " + str,
						"Syntax Error In Technology File");
					return;
				}
				fieldArray[index++].rule = str.substring(pos, end);
				pos = end+1;
			} else
			{
				double v = TextUtils.atof(str.substring(pos));
                fieldArray[index++].value = v;
			}
			while (pos < str.length() && str.charAt(pos) != ',' && str.charAt(pos) != ')') pos++;
			if (str.charAt(pos) != ',') break;
			pos++;
		}
	}

    // fillRules
    private static void fillRules(String str, WizardRuleFields rule)
    {
        fillRule(str, "(,)", rule.xValue, rule.yValue);
    }

    // fillRule
    private static void fillRule(String str, WizardField... rules)
    {
        fillRule(str, "(,)", rules);
    }

    private static void fillRule(String str, String tokens, WizardField... rules)
    {
        StringTokenizer parse = new StringTokenizer(str, tokens, false);
        int count = 0;
        int pos = 0;

        while (parse.hasMoreTokens())
        {
            String value = parse.nextToken();
            switch (count)
            {
                case 0:
                case 2:
                    rules[pos].value = Double.parseDouble(value);
                    break;
                case 1:
                case 3:
                    value = value.replaceAll("\"", ""); // remove extra quotes
                    rules[pos].rule = value;
                    break;
                default:
                    assert(false); // only 2 values
            }
            count++;
            if (count == 2) pos++;
        }
    }

    // fillLayerSeries
    private void fillLayerSeries(String str, List<LayerInfo> layersList)
    {
        StringTokenizer parse = new StringTokenizer(str, "()", false);

        while (parse.hasMoreTokens())
        {
            String value = parse.nextToken();

            if (!value.contains(",")) // only white space
                continue;

            // Sequence ("layer name", "GDS value", "[Display data]", "{A|P,W=?}")
            StringTokenizer p = new StringTokenizer(value, ",", false);
            // can't use space as token because of possible DRC rules
            int itemCount = 0; // 2 max items: layer name and GDS value
            LayerInfo layer = null;

            while (p.hasMoreTokens())
            {
                String s = p.nextToken();
                if (s.startsWith(",")) continue; // skipping comma. Not in the parser because of the color
                switch (itemCount)
                {
                    case 0:
                        layer = new LayerInfo(s);
                        layersList.add(layer);
                        break;
                    case 1:
                    case 2:
                    case 3:
                        layer.setLayerInformation(s);
                        break;
                    default:
                        assert(false);
                }
                itemCount++;
            }
            assert(itemCount > 1 && itemCount < 5); // 2, 3 or 3
        }
    }

    // to get general contact
    private void fillContactSeries(String str, Map<String, List<Contact>> contactMap)
    {
        StringTokenizer parse = new StringTokenizer(str, "[]", false);
        List<ContactNode> nodeList = new ArrayList<ContactNode>();

        while (parse.hasMoreTokens())
        {
            String value = parse.nextToken();

            if (value.equals(";")) // end of line
                continue;

            // checking the metal pair lists.
            // overhang values should be in by now
            if (value.contains("{"))
            {
                assert(nodeList.size() > 0);
                int index = value.indexOf("{");
                assert(index != -1); // it should come with a prefix name
                String prefix = value.substring(0, index);
                String v = value.substring(index);
                StringTokenizer p = new StringTokenizer(v, "{}", false);

                while (p.hasMoreTokens())
                {
                    String pair = p.nextToken();
                    // getting metal numbers {a,b,c}
                    StringTokenizer n = new StringTokenizer(pair, ", ", false); // getting the layer names
                    List<String> layerNames = new ArrayList<String>();

                    while (n.hasMoreTokens())
                    {
                        String l = n.nextToken();
                        layerNames.add(l);
                    }
                    assert (nodeList.size() == layerNames.size());
                    Contact cont = new Contact(prefix);
                    for (int i = 0; i < layerNames.size(); i++)
                    {
                        String name = layerNames.get(i);
                        ContactNode tmp = nodeList.get(i);
                        ContactNode node = new ContactNode(name,
                            tmp.valueX.value,  tmp.valueX.rule,
                            tmp.valueY.value,  tmp.valueY.rule);
                        cont.layers.add(node);
                    }

                    String layer1 = layerNames.get(0);
                    String layer2 = layerNames.get(1); // n/p plus regions should go at the end
                    // Always store them by lowMetal-highMetal if happens
                    if (layer1.compareToIgnoreCase(layer2) > 0) // layer1 name is second
                    {
                        String temp = layer1;
                        layer1 = layer2;
                        layer2 = temp;
                    }
                    String key = layer1 + "-" + layer2;
                    List<Contact> l = contactMap.get(key);
                    if (l == null)
                    {
                        l = new ArrayList<Contact>();
                        contactMap.put(key, l);
                    }
                    l.add(cont);
                }
            }
            else
            {
                // syntax: (overX, overXS, overY, overYS)(overX, overXS, overY, overYS)
                // pair of layers found
                StringTokenizer p = new StringTokenizer(value, "()", false);

                while (p.hasMoreTokens())
                {
                    String s = p.nextToken();
                    // layer info
                    int itemCount = 0; // 4 max items: metal layer, overhang X, overhang X rule, overhang Y, overhang Y rule
                    StringTokenizer x = new StringTokenizer(s, ", ", false);
                    double overX = 0, overY = 0;
                    String overXS = null, overYS = null;

                    while (x.hasMoreTokens() && itemCount < 4)
                    {
                        String item = x.nextToken();
                        switch (itemCount)
                        {
                            case 0: // overhang X value
                               overX = Double.valueOf(item);
                                break;
                            case 1: // overhang X rule name
                                overXS = item;
                                break;
                            case 2: // overhang Y value
                                overY = Double.valueOf(item);
                                break;
                            case 3: // overhang Y rule name
                                overYS = item;
                                break;
                        }
                        itemCount++;
                    }
                    assert(itemCount == 4);
                    ContactNode node = new ContactNode("", overX, overXS, overY, overYS);
                    nodeList.add(node);
                }
            }
        }
    }

    /************************************** EXPORT RAW NUMBERS TO DISK **************************************/

	void exportData()
	{
		String fileName = OpenFile.chooseOutputFile(FileType.TEXT, "Technology Wizard File", "Technology.txt");
		if (fileName == null) return;
		try
		{
			PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
			dumpNumbers(printWriter);
			printWriter.close();
		} catch (IOException e)
		{
			System.out.println("Error writing XML file '" + fileName + "'");
			return;
		}
	}

	private void dumpNumbers(PrintWriter pw)
	{
        pw.print("#### Electric(tm) VLSI Design System, version ");
        if (User.isIncludeDateAndVersionInOutput())
        {
            pw.println(com.sun.electric.database.text.Version.getVersion());
        } else
        {
            pw.println();
        }
        pw.println("#### ");
        pw.println("#### Technology wizard data file");
		pw.println("####");
		pw.println("#### All dimensions in nanometers.");

        if (IOTool.isUseCopyrightMessage())
        {
            String str = IOTool.getCopyrightMessage();
            int start = 0;
            while (start < str.length())
            {
                int endPos = str.indexOf('\n', start);
                if (endPos < 0) endPos = str.length();
                String oneLine = str.substring(start, endPos);
                pw.println("#### " + oneLine);
                start = endPos+1;
            }
        }

        pw.println();
		pw.println("$tech_name = \"" + tech_name + "\";");
		pw.println("$tech_description = \"" + tech_description + "\";");
		pw.println("$num_metal_layers = " + num_metal_layers + ";");
		pw.println("$psubstrate_process = " + pSubstrateProcess + ";");
		pw.println("$horizontal_transistors = " + horizontalFlag + ";");
        pw.println("$extra_info = " + caseFlag + ";");
        pw.println();
		pw.println("## stepsize is minimum granularity that will be used as movement grid");
		pw.println("## set to manufacturing grid or lowest common denominator with design rules");
		pw.println("$stepsize = " + stepsize + ";");
        pw.println();
		pw.println("######  DIFFUSION RULES  #####");
		pw.println("$diff_width = " + TextUtils.formatDouble(diff_width.value) + ";");
		pw.println("$diff_width_rule = \"" + diff_width.rule + "\";");
		pw.println("$diff_poly_overhang = " + TextUtils.formatDouble(diff_poly_overhang.value) + ";        # min. diff overhang from gate edge");
		pw.println("$diff_poly_overhang_rule = \"" + diff_poly_overhang.rule + "\";        # min. diff overhang from gate edge");
		pw.println("$diff_contact_overhang = " + TextUtils.formatDouble(diff_contact_overhang.value) + ";     # min. diff overhang contact");
		pw.println("$diff_contact_overhang_rule = \"" + diff_contact_overhang.rule + "\";     # min. diff overhang contact");
		pw.println("$diff_spacing = " + TextUtils.formatDouble(diff_spacing.value) + ";");
		pw.println("$diff_spacing_rule = \"" + diff_spacing.rule + "\";");
		pw.println();
		pw.println("######  POLY RULES  #####");
		pw.println("$poly_width = " + TextUtils.formatDouble(poly_width.value) + ";");
		pw.println("$poly_width_rule = \"" + poly_width.rule + "\";");
		pw.println("$poly_endcap = " + TextUtils.formatDouble(poly_endcap.value) + ";               # min. poly gate extension from edge of diffusion");
		pw.println("$poly_endcap_rule = \"" + poly_endcap.rule + "\";               # min. poly gate extension from edge of diffusion");
		pw.println("$poly_spacing = " + TextUtils.formatDouble(poly_spacing.value) + ";");
		pw.println("$poly_spacing_rule = \"" + poly_spacing.rule + "\";");
		pw.println("$poly_diff_spacing = " + TextUtils.formatDouble(poly_diff_spacing.value) + ";         # min. spacing between poly and diffusion");
		pw.println("$poly_diff_spacing_rule = \"" + poly_diff_spacing.rule + "\";         # min. spacing between poly and diffusion");
        pw.println("$poly_protection_spacing = " + TextUtils.formatDouble(poly_protection_spacing.value) + ";         # min. spacing between poly and dummy poly");
		pw.println("$poly_protection_spacing_rule = \"" + poly_protection_spacing.rule + "\";         # min. spacing between poly and dummy poly");
        pw.println();
		pw.println("######  GATE RULES  #####");
		pw.println("$gate_length = " + TextUtils.formatDouble(gate_length.value) + ";               # min. transistor gate length");
		pw.println("$gate_length_rule = \"" + gate_length.rule + "\";               # min. transistor gate length");
		pw.println("$gate_width = " + TextUtils.formatDouble(gate_width.value) + ";                # min. transistor gate width");
		pw.println("$gate_width_rule = \"" + gate_width.rule + "\";                # min. transistor gate width");
		pw.println("$gate_spacing = " + TextUtils.formatDouble(gate_spacing.value) + ";             # min. gate to gate spacing on diffusion");
		pw.println("$gate_spacing_rule = \"" + gate_spacing.rule + "\";             # min. gate to gate spacing on diffusion");
		pw.println("$gate_contact_spacing = " + TextUtils.formatDouble(gate_contact_spacing.value) + ";      # min. spacing from gate edge to contact inside diffusion");
		pw.println("$gate_contact_spacing_rule = \"" + gate_contact_spacing.rule + "\";      # min. spacing from gate edge to contact inside diffusion");
		pw.println();
		pw.println("######  CONTACT RULES  #####");
		pw.println("$contact_size = " + TextUtils.formatDouble(contact_size.value) + ";");
		pw.println("$contact_size_rule = \"" + contact_size.rule + "\";");
		pw.println("$contact_spacing = " + TextUtils.formatDouble(contact_spacing.value) + ";");
		pw.println("$contact_spacing_rule = \"" + contact_spacing.rule + "\";");
        pw.println("$contact_array_spacing = " + TextUtils.formatDouble(contact_array_spacing.value) + ";");
		pw.println("$contact_array_spacing_rule = \"" + contact_array_spacing.rule + "\";");
        pw.println("$contact_metal_overhang_inline_only = " + TextUtils.formatDouble(contact_metal_overhang_inline_only.value) + ";      # metal overhang when overhanging contact from two sides only");
		pw.println("$contact_metal_overhang_inline_only_rule = \"" + contact_metal_overhang_inline_only.rule + "\";      # metal overhang when overhanging contact from two sides only");

        pw.println("$contact_metal_overhang_all_sides = " + TextUtils.formatDouble(contact_metal_overhang_all_sides.value) + ";         # metal overhang when surrounding contact");
		pw.println("$contact_metal_overhang_all_sides_rule = \"" + contact_metal_overhang_all_sides.rule + "\";         # metal overhang when surrounding contact");
		pw.println("$contact_poly_overhang = " + TextUtils.formatDouble(contact_poly_overhang.value) + ";                    # poly overhang contact, recommended value");
		pw.println("$contact_poly_overhang_rule = \"" + contact_poly_overhang.rule + "\";                    # poly overhang contact, recommended value");
		pw.println("$polycon_diff_spacing = " + TextUtils.formatDouble(polycon_diff_spacing.value) + ";                    # spacing between poly-metal contact edge and diffusion");
		pw.println("$polycon_diff_spacing_rule = \"" + polycon_diff_spacing.rule + "\";                    # spacing between poly-metal contact edge and diffusion");
		pw.println();
		pw.println("######  WELL AND IMPLANT RULES  #####");
		pw.println("$nplus_width = " + TextUtils.formatDouble(nplus_width.value) + ";");
		pw.println("$nplus_width_rule = \"" + nplus_width.rule + "\";");
		pw.println("$nplus_overhang_diff = " + TextUtils.formatDouble(nplus_overhang_diff.value) + ";");
		pw.println("$nplus_overhang_diff_rule = \"" + nplus_overhang_diff.rule + "\";");
		pw.println("$nplus_overhang_strap = " + TextUtils.formatDouble(nplus_overhang_strap.value) + ";");
		pw.println("$nplus_overhang_strap_rule = \"" + nplus_overhang_strap.rule + "\";");
		pw.println("$nplus_overhang_poly = " + TextUtils.formatDouble(nplus_overhang_poly.value) + ";");
		pw.println("$nplus_overhang_poly_rule = \"" + nplus_overhang_poly.rule + "\";");
		pw.println("$nplus_spacing = " + TextUtils.formatDouble(nplus_spacing.value) + ";");
		pw.println("$nplus_spacing_rule = \"" + nplus_spacing.rule + "\";");
		pw.println();
		pw.println("$pplus_width = " + TextUtils.formatDouble(pplus_width.value) + ";");
		pw.println("$pplus_width_rule = \"" + pplus_width.rule + "\";");
		pw.println("$pplus_overhang_diff = " + TextUtils.formatDouble(pplus_overhang_diff.value) + ";");
		pw.println("$pplus_overhang_diff_rule = \"" + pplus_overhang_diff.rule + "\";");
		pw.println("$pplus_overhang_strap = " + TextUtils.formatDouble(pplus_overhang_strap.value) + ";");
		pw.println("$pplus_overhang_strap_rule = \"" + pplus_overhang_strap.rule + "\";");
        pw.println("$pplus_overhang_poly = " + TextUtils.formatDouble(pplus_overhang_poly.value) + ";");
		pw.println("$pplus_overhang_poly_rule = \"" + pplus_overhang_poly.rule + "\";");
        pw.println("$pplus_spacing = " + TextUtils.formatDouble(pplus_spacing.value) + ";");
		pw.println("$pplus_spacing_rule = \"" + pplus_spacing.rule + "\";");
		pw.println();
		pw.println("$nwell_width = " + TextUtils.formatDouble(nwell_width.value) + ";");
		pw.println("$nwell_width_rule = \"" + nwell_width.rule + "\";");
		pw.println("$nwell_overhang_diff_p = " + TextUtils.formatDouble(nwell_overhang_diff_p.value) + ";");
		pw.println("$nwell_overhang_diff_rule_p = \"" + nwell_overhang_diff_p.rule + "\";");
        pw.println("$nwell_overhang_diff_n = " + TextUtils.formatDouble(nwell_overhang_diff_n.value) + ";");
		pw.println("$nwell_overhang_diff_rule_n = \"" + nwell_overhang_diff_n.rule + "\";");
        pw.println("$nwell_spacing = " + TextUtils.formatDouble(nwell_spacing.value) + ";");
		pw.println("$nwell_spacing_rule = \"" + nwell_spacing.rule + "\";");
		pw.println();
		pw.println("######  METAL RULES  #####");
		pw.print("@metal_width = (");
		for(int i=0; i<num_metal_layers; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(metal_width[i].value));
		}
		pw.println(");");
		pw.print("@metal_width_rule = (");
		for(int i=0; i<num_metal_layers; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print("\"" + metal_width[i].rule + "\"");
		}
		pw.println(");");
		pw.print("@metal_spacing = (");
		for(int i=0; i<num_metal_layers; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(metal_spacing[i].value));
		}
		pw.println(");");
		pw.print("@metal_spacing_rule = (");
		for(int i=0; i<num_metal_layers; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print("\"" + metal_spacing[i].rule + "\"");
		}
		pw.println(");");
		pw.println();
		pw.println("######  VIA RULES  #####");
		pw.print("@via_size = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(via_size[i].value));
		}
		pw.println(");");
		pw.print("@via_size_rule = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print("\"" + via_size[i].rule + "\"");
		}
		pw.println(");");
		pw.print("@via_spacing = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(via_inline_spacing[i].value));
		}
		pw.println(");");
		pw.print("@via_spacing_rule = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print("\"" + via_inline_spacing[i].rule + "\"");
		}
		pw.println(");");
		pw.println();
		pw.println("## \"sep2d\" spacing, close proximity via array spacing");
		pw.print("@via_array_spacing = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(via_array_spacing[i].value));
		}
		pw.println(");");
		pw.print("@via_array_spacing_rule = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print("\"" + via_array_spacing[i].rule + "\"");
		}
		pw.println(");");
		pw.print("@via_overhang_inline = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(via_overhang[i].value));
		}
		pw.println(");");
		pw.print("@via_overhang_inline_rule = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print("\"" + via_overhang[i].rule + "\"");
		}
		pw.println(");");
		pw.println();
		pw.println("######  ANTENNA RULES  #####");
		pw.println("$poly_antenna_ratio = " + TextUtils.formatDouble(poly_antenna_ratio) + ";");
		pw.print("@metal_antenna_ratio = (");
		for(int i=0; i<num_metal_layers; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(TextUtils.formatDouble(metal_antenna_ratio[i]));
		}
		pw.println(");");
		pw.println();
		pw.println("######  GDS-II LAYERS  #####");
		pw.println("$gds_diff_layer = " + diff_layer + ";");
		pw.println("$gds_poly_layer = " + poly_layer + ";");
		pw.println("$gds_nplus_layer = " + nplus_layer + ";");
		pw.println("$gds_pplus_layer = " + pplus_layer + ";");
		pw.println("$gds_nwell_layer = " + nwell_layer + ";");
        pw.println("$gds_contact_layer = " + contact_layer + ";");
		pw.print("@gds_metal_layer = (");
		for(int i=0; i<num_metal_layers; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(metal_layers[i]);
		}
		pw.println(");");
		pw.print("@gds_via_layer = (");
		for(int i=0; i<num_metal_layers-1; i++)
		{
			if (i > 0) pw.print(", ");
			pw.print(via_layers[i]);
		}
		pw.println(");");
		pw.println();
		pw.println("## Device marking layer");
		pw.println("$gds_marking_layer = " + marking_layer + ";");
		pw.println();
		pw.println("# End of techfile");
	}

    /************************************** WRITE XML FILE **************************************/

	void writeXML()
	{
		String errorMessage = errorInData();
		if (errorMessage != null)
		{
			Job.getUserInterface().showErrorMessage("ERROR: " + errorMessage,
				"Missing Technology Data");
			return;
		}
        String suggestedName = getTechName() + ".xml";
        String fileName = OpenFile.chooseOutputFile(FileType.XML, "Technology XML File", suggestedName); //"Technology.xml");
		if (fileName == null) return;
		try
		{
            dumpXMLFile(fileName);
        } catch (Exception e)
		{
			System.out.println("Error writing XML file in '" + fileName + "'");
			return;
		}
	}

    /**
     * Method to create the XML version of a PrimitiveNode representing a pin
     * @return new XML.
     */
    private Xml.PrimitiveNodeGroup makeXmlPrimitivePin(Xml.Technology t, String name, double size,
                                                       SizeOffset so, List<String> portNames, Xml.NodeLayer... list)
    {
        List<Xml.NodeLayer> nodesList = new ArrayList<Xml.NodeLayer>(list.length);
        List<Xml.PrimitivePort> nodePorts = new ArrayList<Xml.PrimitivePort>();

        for (Xml.NodeLayer lb : list)
        {
            if (lb == null) continue; // in case the pwell layer off
            nodesList.add(lb);
        }

        // default uses the same name from the pin node
        if (portNames == null)
        {
            portNames = new ArrayList<String>();
            portNames.add(name);
        }
        nodePorts.add(makeXmlPrimitivePort(name.toLowerCase(), 0, 180, 0, null, 0, -1, 0, 1, 0, -1, 0, 1, portNames));
        Xml.PrimitiveNodeGroup n = makeXmlPrimitive(t.nodeGroups, name + "-Pin", PrimitiveNode.Function.PIN, size, size, 0, 0,
                so, nodesList, nodePorts, null, true);
        return n;
    }

    /**
     * Method to create the XML version of a PrimitiveNode representing a contact
     * @return new XML.
     */
    private Xml.PrimitiveNodeGroup makeXmlPrimitiveCon(List<Xml.PrimitiveNodeGroup> nodeGroups, String name,
                                                       PrimitiveNode.Function function, double sizeX, double sizeY,
                                                       SizeOffset so, List<String> portArcNames, Xml.NodeLayer... list)
    {
        List<Xml.NodeLayer> nodesList = new ArrayList<Xml.NodeLayer>(list.length);
        List<Xml.PrimitivePort> nodePorts = new ArrayList<Xml.PrimitivePort>();

        for (Xml.NodeLayer lb : list)
        {
            if (lb == null) continue; // in case the pwell layer off
            nodesList.add(lb);
        }

        nodePorts.add(makeXmlPrimitivePort(name.toLowerCase(), 0, 180, 0, null, 0, -1, 0, 1, 0, -1, 0, 1, portArcNames));
        return makeXmlPrimitive(nodeGroups, name + "-Con", function, sizeX, sizeY, 0, 0,
                so, nodesList, nodePorts, null, false);
    }

    /**
     * Method to create the XML version of a PrimitiveNode representing a contact
     * @return new XML.
     */
    private Xml.PrimitiveNodeGroup makeXmlCapacitor(List<Xml.PrimitiveNodeGroup> nodeGroups, String name,
                                                    PrimitiveNode.Function function, double sizeX, double sizeY,
                                                    SizeOffset so, List<String> portNames, List<String> portArcNames,
                                                    Xml.NodeLayer... list)
    {
        List<Xml.NodeLayer> nodesList = new ArrayList<Xml.NodeLayer>(list.length);
        List<Xml.PrimitivePort> nodePorts = new ArrayList<Xml.PrimitivePort>();

        for (Xml.NodeLayer lb : list)
        {
            if (lb == null) continue; // in case the pwell layer off
            nodesList.add(lb);
        }

        for (String port : portNames)
        {
            nodePorts.add(makeXmlPrimitivePort(port, 0, 180, 0, null,
                0, -1, 0, 1, 0, -1, 0, 1, portArcNames));
        }
        return makeXmlPrimitive(nodeGroups, name + "-Capacitor", function, sizeX, sizeY, 0, 0,
                so, nodesList, nodePorts, null, false);
    }

    /**
     * Method to create the XML version of a PrimitiveNode
     * @return new XML.
     */
    private Xml.PrimitiveNodeGroup makeXmlPrimitive(List<Xml.PrimitiveNodeGroup> nodeGroups,
                                                    String name, PrimitiveNode.Function function,
                                                    double width, double height,
                                                    double ppLeft, double ppBottom,
                                                    SizeOffset so, List<Xml.NodeLayer> nodeLayers,
                                                    List<Xml.PrimitivePort> nodePorts,
                                                    PrimitiveNode.NodeSizeRule nodeSizeRule, boolean isArcsShrink)
    {
        Xml.PrimitiveNodeGroup ng = new Xml.PrimitiveNodeGroup();
        ng.isSingleton = true;
        Xml.PrimitiveNode n = new Xml.PrimitiveNode();
        n.name = name;
        n.function = function;
        ng.nodes.add(n);

        ng.shrinkArcs = isArcsShrink;
//        n.square = isSquare();
//        n.canBeZeroSize = isCanBeZeroSize();
//        n.wipes = isWipeOn1or2();
//        n.lockable = isLockedPrim();
//        n.edgeSelect = isEdgeSelect();
//        n.skipSizeInPalette = isSkipSizeInPalette();
//        n.notUsed = isNotUsed();
//        n.lowVt = isNodeBitOn(PrimitiveNode.LOWVTBIT);
//        n.highVt = isNodeBitOn(PrimitiveNode.HIGHVTBIT);
//        n.nativeBit  = isNodeBitOn(PrimitiveNode.NATIVEBIT);
//        n.od18 = isNodeBitOn(PrimitiveNode.OD18BIT);
//        n.od25 = isNodeBitOn(PrimitiveNode.OD25BIT);
//        n.od33 = isNodeBitOn(PrimitiveNode.OD33BIT);

//        PrimitiveNode.NodeSizeRule nodeSizeRule = getMinSizeRule();
//        EPoint minFullSize = nodeSizeRule != null ?
//            EPoint.fromLambda(0.5*nodeSizeRule.getWidth(), 0.5*nodeSizeRule.getHeight()) :
//            EPoint.fromLambda(0.5*getDefWidth(), 0.5*getDefHeight());
//         EPoint minFullSize = EPoint.fromLambda(0.5*width, 0.5*height);
        EPoint topLeft = EPoint.fromLambda(ppLeft, ppBottom + height);
        EPoint size =  EPoint.fromLambda(width, height);

//        double getDefWidth = width, getDefHeight = height;
        if (function.isPin() && isArcsShrink) {
//            assert getNumPorts() == 1;
//            assert nodeSizeRule == null;
//            PrimitivePort pp = getPort(0);
//            assert pp.getLeft().getMultiplier() == -0.5 && pp.getRight().getMultiplier() == 0.5 && pp.getBottom().getMultiplier() == -0.5 && pp.getTop().getMultiplier() == 0.5;
//            assert pp.getLeft().getAdder() == -pp.getRight().getAdder() && pp.getBottom().getAdder() == -pp.getTop().getAdder();
//            minFullSize = EPoint.fromLambda(ppLeft, ppBottom);
        }
//            DRCTemplate nodeSize = xmlRules.getRule(pnp.getPrimNodeIndexInTech(), DRCTemplate.DRCRuleType.NODSIZ);
//        SizeOffset so = getProtoSizeOffset();
        if (so != null &&
            (so.getLowXOffset() == 0 && so.getHighXOffset() == 0 &&
                so.getLowYOffset() == 0 && so.getHighYOffset() == 0))
            so = null;

            ERectangle base = calcBaseRectangle(so, nodeLayers, nodeSizeRule);
            ng.baseLX.value = base.getLambdaMinX();
            ng.baseHX.value = base.getLambdaMaxX();
            ng.baseLY.value = base.getLambdaMinY();
            ng.baseHY.value = base.getLambdaMaxY();
//            n.sizeOffset = so;

//        if (!minFullSize.equals(EPoint.ORIGIN))
//            n.diskOffset = minFullSize;
//        if (so != null) {
//            EPoint p2 = EPoint.fromGrid(
//                    minFullSize.getGridX() - ((so.getLowXGridOffset() + so.getHighXGridOffset()) >> 1),
//                    minFullSize.getGridY() - ((so.getLowYGridOffset() + so.getHighYGridOffset()) >> 1));
//            n.diskOffset.put(Integer.valueOf(1), minFullSize);
//            n.diskOffset.put(Integer.valueOf(2), p2);
//            n.diskOffset.put(Integer.valueOf(2), minFullSize);
//        }
//        n.defaultWidth.addLambda(DBMath.round(getDefWidth)); // - 2*minFullSize.getLambdaX());
//        n.defaultHeight.addLambda(DBMath.round(getDefHeight)); // - 2*minFullSize.getLambdaY());
        /* ERectangle baseRectangle = */ ERectangle.fromGrid(topLeft.getGridX(), topLeft.getGridY(),
            size.getGridX(), size.getGridY());
/*        n.nodeBase = baseRectangle;*/

//        List<Technology.NodeLayer> nodeLayers = Arrays.asList(getLayers());
//        List<Technology.NodeLayer> electricalNodeLayers = nodeLayers;
//        if (getElectricalLayers() != null)
//            electricalNodeLayers = Arrays.asList(getElectricalLayers());
//        boolean isSerp = false; //getSpecialType() == PrimitiveNode.SERPTRANS;
        if (nodeLayers != null)
            ng.nodeLayers.addAll(nodeLayers);
//        int m = 0;
//        for (Technology.NodeLayer nld: electricalNodeLayers) {
//            int j = nodeLayers.indexOf(nld);
//            if (j < 0) {
//                n.nodeLayers.add(nld.makeXml(isSerp, minFullSize, false, true));
//                continue;
//            }
//            while (m < j)
//                n.nodeLayers.add(nodeLayers.get(m++).makeXml(isSerp, minFullSize, true, false));
//            n.nodeLayers.add(nodeLayers.get(m++).makeXml(isSerp, minFullSize, true, true));
//        }
//        while (m < nodeLayers.size())
//            n.nodeLayers.add(nodeLayers.get(m++).makeXml(isSerp, minFullSize, true, false));

//        for (Iterator<PrimitivePort> pit = getPrimitivePorts(); pit.hasNext(); ) {
//            PrimitivePort pp = pit.next();
//            n.ports.add(pp.makeXml(minFullSize));
//        }
        ng.specialType = PrimitiveNode.NORMAL; // getSpecialType();
//        if (getSpecialValues() != null)
//            n.specialValues = getSpecialValues().clone();
        if (nodeSizeRule != null) {
            ng.nodeSizeRule = new Xml.NodeSizeRule();
            ng.nodeSizeRule.width = nodeSizeRule.getWidth();
            ng.nodeSizeRule.height = nodeSizeRule.getHeight();
            ng.nodeSizeRule.rule = nodeSizeRule.getRuleName();
        }
//        n.spiceTemplate = "";//getSpiceTemplate();

        // ports
        ng.ports.addAll(nodePorts);

        nodeGroups.add(ng);

        return ng;
    }

    private ERectangle calcBaseRectangle(SizeOffset so, List<Xml.NodeLayer> nodeLayers, PrimitiveNode.NodeSizeRule nodeSizeRule) {
        long lx, hx, ly, hy;
        if (nodeSizeRule != null) {
            hx = DBMath.lambdaToGrid(0.5*nodeSizeRule.getWidth());
            lx = -hx;
            hy = DBMath.lambdaToGrid(0.5*nodeSizeRule.getHeight());
            ly = -hy;
        } else {
            lx = Long.MAX_VALUE;
            hx = Long.MIN_VALUE;
            ly = Long.MAX_VALUE;
            hy = Long.MIN_VALUE;
            for (int i = 0; i < nodeLayers.size(); i++) {
                Xml.NodeLayer nl = nodeLayers.get(i);
                long x, y;
                if (nl.representation == Technology.NodeLayer.BOX || nl.representation == Technology.NodeLayer.MULTICUTBOX) {
                    x = DBMath.lambdaToGrid(nl.lx.value);
                    lx = Math.min(lx, x);
                    hx = Math.max(hx, x);
                    x = DBMath.lambdaToGrid(nl.hx.value);
                    lx = Math.min(lx, x);
                    hx = Math.max(hx, x);
                    y = DBMath.lambdaToGrid(nl.ly.value);
                    ly = Math.min(ly, y);
                    hy = Math.max(hy, y);
                    y = DBMath.lambdaToGrid(nl.hy.value);
                    ly = Math.min(ly, y);
                    hy = Math.max(hy, y);
                } else {
                    for (Technology.TechPoint p: nl.techPoints) {
                        x = p.getX().getAdder().getGrid();
                        lx = Math.min(lx, x);
                        hx = Math.max(hx, x);
                        y = p.getY().getAdder().getGrid();
                        ly = Math.min(ly, y);
                        hy = Math.max(hy, y);
                    }
                }
            }
        }
        if (so != null) {
            lx += so.getLowXGridOffset();
            hx -= so.getHighXGridOffset();
            ly += so.getLowYGridOffset();
            hy -= so.getHighYGridOffset();
        }
        return ERectangle.fromGrid(lx, ly, hx - lx, hy - ly);
    }

    /**
     * Method to create the XML version of a ArcProto
     * @param name
     * @param function
     * @return new XML.
     */
    private Xml.ArcProto makeXmlArc(Xml.Technology t, String name, com.sun.electric.technology.ArcProto.Function function,
                                    double ant, Xml.ArcLayer ... arcLayers)
    {
        Xml.ArcProto a = new Xml.ArcProto();
        a.name = name;
        a.function = function;
        a.wipable = true;
//        a.curvable = false;
//        a.special = false;
//        a.notUsed = false;
//        a.skipSizeInPalette = false;

//        a.elibWidthOffset = getLambdaElibWidthOffset();
        a.extended = true;
        a.fixedAngle = true;
        a.angleIncrement = 90;
        a.antennaRatio = DBMath.round(ant);

        for (Xml.ArcLayer al: arcLayers)
        {
            if (al == null) continue; // in case the pwell layer off
            a.arcLayers.add(al);
        }
        t.arcs.add(a);
        return a;
    }

    /**
     * Method to create the XML version of a Layer.
     * @return new XML.
     */
    private Xml.Layer makeXmlLayer(List<Xml.Layer> layers, Map<Xml.Layer, WizardField> layerMap, String name,
                                   Layer.Function function, int extraf, EGraphics graph,
                                   WizardField width, boolean pureLayerNode, boolean pureLayerPortArc,
                                   String... portArcNames)
    {
        Xml.Layer l = makeXmlLayer(layers, name, function, extraf, graph, width.value, pureLayerNode, pureLayerPortArc, portArcNames);
        layerMap.put(l, width);
        return l;
    }

    /**
     * Method to create the XML version of a Layer.
     * @return new XML.
     */
    private Xml.Layer makeXmlLayer(List<Xml.Layer> layers, String name, Layer.Function function, int extraf, EGraphics graph,
                                   double width, boolean pureLayerNode, boolean pureLayerPortArc, String... portArcNames)
    {
        Xml.Layer l = new Xml.Layer();
        l.name = name;
        l.function = function;
        l.extraFunction = extraf;
        graph = graph.withTransparencyMode(EGraphics.J3DTransparencyOption.NONE);
        graph = graph.withTransparencyFactor(1);
        l.desc = graph;
        l.thick3D = 1;
        l.height3D = 1;
        l.cif = "Not set"; //"C" + cifLetter + cifLetter;
        l.skill = name;
        l.resistance = 1;
        l.capacitance = 0;
        l.edgeCapacitance = 0;
        l.inductanceAreaFactor = 0;
        l.inductanceLengthFactor = 0;
//            if (layer.getPseudoLayer() != null)
//                l.pseudoLayer = layer.getPseudoLayer().getName();

        // if pureLayerNode is false, pureLayerPortArc must be false
        assert(pureLayerNode || !pureLayerPortArc);

        if (pureLayerNode) {
            l.pureLayerNode = new Xml.PureLayerNode();
            l.pureLayerNode.name = name + "-Node";
            l.pureLayerNode.style = Poly.Type.FILLED;
            l.pureLayerNode.size.addLambda(scaledValue(width));
            l.pureLayerNode.port = "Port_" + name;
/*            l.pureLayerNode.size.addRule(width.rule, 1);*/
            if (pureLayerPortArc)
            {
                if (portArcNames.length == 0) // only 1 port
                    l.pureLayerNode.portArcs.add(name);
                else
                {
                    for (String s : portArcNames)
                    l.pureLayerNode.portArcs.add(s);
                }
            }
//            for (ArcProto ap: pureLayerNode.getPort(0).getConnections()) {
//                if (ap.getTechnology() != tech) continue;
//                l.pureLayerNode.portArcs.add(ap.getName());
//            }
        }
        layers.add(l);
        return l;
    }

    private Xml.NodeLayer addXmlNodeLayer(List<Xml.NodeLayer> nodesList, Xml.Technology t, String layerName,
                                          double xVal, double yVal)
    {
        return addXmlNodeLayerInternal(nodesList, t, layerName, xVal, yVal, true, true, 0);
    }

    private Xml.NodeLayer addXmlNodeLayerInternal(List<Xml.NodeLayer> nodesList, Xml.Technology t, String layerName,
                                                  double xVal, double yVal,
                                                  boolean inLayers, boolean electricalLayers, int port)
    {
        Xml.Layer layer = t.findLayer(layerName);
        if (layer == null)
        {
            System.out.println("Error adding layer '" + layerName + "'");
            return null;
        }
        Xml.NodeLayer nl = makeXmlNodeLayer(xVal, xVal, yVal, yVal, layer, Poly.Type.FILLED,
                inLayers, electricalLayers, port);
        nodesList.add(nl);
        return nl;
    }

    /**
     * Method to create the XML version of NodeLayer
     */
    private Xml.NodeLayer makeXmlNodeLayer(double lx, double hx, double ly, double hy, Xml.Layer lb, Poly.Type style)
    {
        return makeXmlNodeLayer(lx, hx, ly, hy, lb, style, true, true, 0);
    }

    /**
     * Method to create the XML version of NodeLayer either graphical or electrical.
     * makeXmlNodeLayer is the default one where layer is available in both mode.
     */
    private Xml.NodeLayer makeXmlNodeLayer(double lx, int lxk, double hx, int hxk,
                                           double ly, int lyk, double hy, int hyk,
                                           Xml.Layer lb, Poly.Type style,
                                           boolean inLayers, boolean electricalLayers, int port)
    {
        Xml.NodeLayer nl = new Xml.NodeLayer();
        // can't continue with rest of functions if this is null
        // catching the exception in  writeXML();
        if(lb == null)
           System.out.println("Error: null layer in makeXmlNodeLayer");
        nl.layer = lb.name;
        nl.style = style;
        nl.portNum = port;
        nl.inLayers = inLayers;
        nl.inElectricalLayers = electricalLayers;
        nl.representation = Technology.NodeLayer.BOX;
        nl.lx.k = lxk; nl.hx.k = hxk; nl.ly.k = lyk; nl.hy.k = hyk;
        nl.lx.addLambda(-lx); nl.hx.addLambda(hx); nl.ly.addLambda(-ly); nl.hy.addLambda(hy);
        return nl;
    }

    /**
     * Method to create the XML version of NodeLayer either graphical or electrical.
     * makeXmlNodeLayer is the default one where layer is available in both mode.
     */
    private Xml.NodeLayer makeXmlNodeLayer(double lx, double hx, double ly, double hy, Xml.Layer lb, Poly.Type style,
                                           boolean inLayers, boolean electricalLayers, int port)
    {
        return makeXmlNodeLayer(lx, -1, hx, 1, ly, -1, hy, 1, lb, style, inLayers, electricalLayers, port);
    }

    /**
     * Method to create the default XML version of a MultiCUt NodeLayer
     * @return new XML.
     */
    private Xml.NodeLayer makeXmlMulticut(Xml.Layer lb, double sizeRule, double sepRule, double sepRule2D)
    {
        return makeXmlMulticut(0, 0, 0, 0, lb, sizeRule, sepRule, sepRule2D);
    }

    /**
     * Method to create the default XML version of a MultiCUt NodeLayer
     * @return new XML.
     */
    private Xml.NodeLayer makeXmlMulticut(double lx, double hx, double ly, double hy, Xml.Layer lb,
                                          double sizeRule, double sepRule, double sepRule2D)
    {
        return makeXmlMulticut(lx, -1, hx, 1, ly, -1, hy, 1, lb, sizeRule, sepRule, sepRule2D);
    }
    
    /**
     * Method to create the default XML version of a MultiCUt NodeLayer
     * @return new XML.
     */
    private Xml.NodeLayer makeXmlMulticut(double lx, int lxk, double hx, int hxk, double ly, int lyk, double hy, int hyk,
                                          Xml.Layer lb, double sizeRule, double sepRule, double sepRule2D)
    {
        Xml.NodeLayer nl = new Xml.NodeLayer();
        nl.layer = lb.name;
        nl.style = Poly.Type.FILLED;
        nl.inLayers = nl.inElectricalLayers = true;
        nl.representation = Technology.NodeLayer.MULTICUTBOX;
        nl.lx.k = lxk; nl.hx.k = hxk; nl.ly.k = lyk; nl.hy.k = hyk;
        nl.lx.addLambda(-lx); nl.hx.addLambda(hx); nl.ly.addLambda(-ly); nl.hy.addLambda(hy);

//        nl.sizeRule = sizeRule;
        nl.sizex = sizeRule;
        nl.sizey = sizeRule;
        nl.sep1d = sepRule;
        nl.sep2d = sepRule2D;
        return nl;

    }

    /**
     * Method to create the XML versio nof PrimitivePort
     * @return New Xml.PrimitivePort
     */
    private Xml.PrimitivePort makeXmlPrimitivePort(String name, int portAngle, int portRange, int portTopology,
                                                   EPoint minFullSize,
                                                   double lx, int slx, double hx, int shx,
                                                   double ly, int sly, double hy, int shy, List<String> portArcs)
    {
        Xml.PrimitivePort ppd = new Xml.PrimitivePort();
        double lambdaX = (minFullSize != null) ? minFullSize.getLambdaX() : 0;
        double lambdaY = (minFullSize != null) ? minFullSize.getLambdaY() : 0;
        ppd.name = name;
        ppd.portAngle = portAngle;
        ppd.portRange = portRange;
        ppd.portTopology = portTopology;

        ppd.lx.k = slx;//-1; //getLeft().getMultiplier()*2;
        ppd.lx.addLambda(DBMath.round(lx + lambdaX*ppd.lx.k));
        ppd.hx.k = shx;//1; //getRight().getMultiplier()*2;
        ppd.hx.addLambda(DBMath.round(hx + lambdaX*ppd.hx.k));
        ppd.ly.k = sly;//-1; // getBottom().getMultiplier()*2;
        ppd.ly.addLambda(DBMath.round(ly + lambdaY*ppd.ly.k));
        ppd.hy.k = shy;//1; // getTop().getMultiplier()*2;
        ppd.hy.addLambda(DBMath.round(hy + lambdaY*ppd.hy.k));

        if (portArcs != null) {
            for (String s: portArcs)
            {
                ppd.portArcs.add(s);
            }
        }
        return ppd;
    }

    /**
     * To create zero, cross, aligned and squared contacts from the same set of rules
     */
    private Xml.PrimitiveNodeGroup makeContactSeries(List<Xml.PrimitiveNodeGroup> nodeGroups, String composeName,
                                           double contSize, Xml.Layer conLayer, double spacing, double arraySpacing,
                                           double extLayer1, Xml.Layer layer1,
                                           double extLayer2, Xml.Layer layer2)
    {
        List<String> portNames = new ArrayList<String>(2);

        portNames.add(layer1.name);
        portNames.add(layer2.name);

        // align contact
        double hlaLong1 = DBMath.round(contSize/2 + extLayer1);
        double hlaLong2 = DBMath.round(contSize/2 + extLayer2);
//        double longD = DBMath.isGreaterThan(extLayer1, extLayer2) ? extLayer1 : extLayer2;

        // long square contact. Standard ones
        return (makeXmlPrimitiveCon(nodeGroups, composeName, PrimitiveNode.Function.CONTACT, -1, -1,
            null, /*new SizeOffset(longD, longD, longD, longD),*/ portNames,
                makeXmlNodeLayer(hlaLong1, hlaLong1, hlaLong1, hlaLong1, layer1, Poly.Type.FILLED), // layer1
                makeXmlNodeLayer(hlaLong2, hlaLong2, hlaLong2, hlaLong2, layer2, Poly.Type.FILLED), // layer2
                makeXmlMulticut(conLayer, contSize, spacing, arraySpacing))); // contact
    }

    /**
     * Leave as oublic for the regression.
     * @param fileName
     * @throws IOException
     */
    public void dumpXMLFile(String fileName)
        throws IOException
    {
        Xml.Technology t = new Xml.Technology();

        t.techName = getTechName();
        t.shortTechName = getTechName();
        t.description = getTechDescription();
        t.minNumMetals = t.maxNumMetals = t.defaultNumMetals = getNumMetalLayers();
        t.scaleValue = getStepSize();
        t.scaleRelevant = true;
        t.resolutionValue = getResolution();
//        t.scaleRelevant = isScaleRelevant();
        t.defaultFoundry = "NONE";
        t.minResistance = 1.0;
        t.minCapacitance = 0.1;
        // menus
        t.menuPalette = new Xml.MenuPalette();
        t.menuPalette.numColumns = 3;

        /** RULES **/
        Xml.Foundry f = new Xml.Foundry();
        f.name = Foundry.Type.NONE.getName();
        t.foundries.add(f);

        // LAYER COLOURS
		Color [] metal_colour = new Color[]
		{
			new Color(0,150,255),   // cyan/blue
			new Color(148,0,211),   // purple
			new Color(255,215,0),   // yellow
			new Color(132,112,255), // mauve
			new Color(255,160,122), // salmon
			new Color(34,139,34),   // dull green
			new Color(178,34,34),   // dull red
			new Color(34,34,178),   // dull blue
			new Color(153,153,153), // light gray
			new Color(102,102,102)  // dark gray
		};
		Color poly_colour = new Color(255,155,192);   // pink
		Color diff_colour = new Color(107,226,96);    // light green
		Color via_colour = new Color(205,205,205);    // lighter gray
        // Five transparent colors: poly_colour, diff_colour, metal_colour[0->2]
        Color[] colorMap = {poly_colour, diff_colour, metal_colour[0], metal_colour[1], metal_colour[2]};
        for (int i = 0; i < colorMap.length; i++) {
            Color transparentColor = colorMap[i];
            t.transparentLayers.add(transparentColor);
        }

        // Layers
        List<Xml.Layer> metalLayers = new ArrayList<Xml.Layer>();
        List<Xml.Layer> dummyMetalLayers = new ArrayList<Xml.Layer>();
        List<Xml.Layer> exclusionMetalLayers = new ArrayList<Xml.Layer>();
        List<Xml.Layer> viaLayers = new ArrayList<Xml.Layer>();
        Map<Xml.Layer,WizardField> layerMap = new LinkedHashMap<Xml.Layer,WizardField>();
        int[] nullPattern = new int[] {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
            0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
        int[] dexclPattern = new int[] {
                        0x1010,   //    X       X
                        0x2020,   //   X       X
                        0x4040,   //  X       X
                        0x8080,   // X       X
                        0x4040,   //  X       X
                        0x2020,   //   X       X
                        0x1010,   //    X       X
                        0x0808,   //     X       X
                        0x1010,   //    X       X
                        0x2020,   //   X       X
                        0x4040,   //  X       X
                        0x8080,   // X       X
                        0x4040,   //  X       X
                        0x2020,   //   X       X
                        0x1010,   //    X       X
                        0x0808}; //     X       X

        for (int i = 0; i < num_metal_layers; i++)
        {
            // Adding the metal
            int metalNum = i + 1;
            double opacity = (75 - metalNum * 5)/100.0;
            int metLayHigh = i / 10;
            int metLayDig = i % 10;
            int r = metal_colour[metLayDig].getRed() * (10-metLayHigh) / 10;
            int g = metal_colour[metLayDig].getGreen() * (10-metLayHigh) / 10;
            int b = metal_colour[metLayDig].getBlue() * (10-metLayHigh) / 10;
            int tcol = 0;
            int[] pattern = null;

            switch (metLayDig)
            {
                case 0: tcol = 3;   break;
                case 1: tcol = 4;   break;
                case 2: tcol = 5;   break;
                case 3: pattern = new int[] {0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000,   //
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x0000}; break;
                case 4: pattern = new int[] { 0x8888,   // X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x8888,   // X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x8888,   // X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x8888,   // X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x4444};
                    break;
                case 5: pattern = new int[] { 0x1111,   //    X   X   X   X
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x1111,   //    X   X   X   X
                        0x5555,   //  X X X X X X X X
                        0x1111,   //    X   X   X   X
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x1111,   //    X   X   X   X
                        0x5555,   //  X X X X X X X X
                        0x1111,   //    X   X   X   X
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x1111,   //    X   X   X   X
                        0x5555,   //  X X X X X X X X
                        0x1111,   //    X   X   X   X
                        0xFFFF,   // XXXXXXXXXXXXXXXX
                        0x1111,   //    X   X   X   X
                        0x5555};
                    break;
                case 6: pattern =  new int[] { 0x8888,   // X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x8888,   // X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x8888,   // X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x1111,   //    X   X   X   X
                        0x8888,   // X   X   X   X
                        0x4444,   //  X   X   X   X
                        0x2222,   //   X   X   X   X
                        0x1111};
                    break;
                case 7: pattern =  new int[] { 0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000};
                    break;
                case 8: pattern =  new int[] {0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888,   // X   X   X   X
                        0x0000,   //
                        0x2222,   //   X   X   X   X
                        0x0000,   //
                        0x8888};  // X   X   X   X
                    break;
                case 9: pattern = new int[] { 0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555,   //  X X X X X X X X
                        0x5555};
                    break;
            }
            boolean onDisplay = true, onPrinter = true;

            if (pattern == null)
            {
                pattern = nullPattern;
                onDisplay = false; onPrinter = false;
            }
            EGraphics graph = new EGraphics(onDisplay, onPrinter, null, tcol, r, g, b, opacity, true, pattern);
            Layer.Function fun = Layer.Function.getMetal(metalNum);
            if (fun == null)
                throw new IOException("invalid number of metals");
            String metalName = "Metal-"+metalNum;
            Xml.Layer layer = makeXmlLayer(t.layers, layerMap, metalName, fun, 0, graph,
                metal_width[i], true, true);
            metalLayers.add(layer);

            if (isComplexCase())
            {
                // dummy layers
                graph = new EGraphics(true, true, null, tcol, r, g, b, opacity, false, nullPattern);
                layer = makeXmlLayer(t.layers, "DMY-"+metalName, Layer.Function.getDummyMetal(metalNum), 0, graph,
                    5*metal_width[i].value, true, false);
                dummyMetalLayers.add(layer);

                // exclusion layers for metals
                graph = new EGraphics(true, true, null, tcol, r, g, b, opacity, true, dexclPattern);
                layer = makeXmlLayer(t.layers, "DEXCL-"+metalName, Layer.Function.getDummyExclMetal(i), 0, graph,
                    2*metal_width[i].value, true, false);
                exclusionMetalLayers.add(layer);
            }
        }

        // Vias
        for (int i = 0; i < num_metal_layers - 1; i++)
        {
            // Adding the metal
            int metalNum = i + 1;
            // adding the via
            int r = via_colour.getRed();
            int g = via_colour.getGreen();
            int b = via_colour.getBlue();
            double opacity = 0.7;
            EGraphics graph = new EGraphics(false, false, null, 0, r, g, b, opacity, true, nullPattern);
            Layer.Function fun = Layer.Function.getContact(metalNum+1, 0); //via contact starts with CONTACT2
            if (fun == null)
                throw new IOException("invalid number of vias");
            viaLayers.add(makeXmlLayer(t.layers, layerMap, "Via-"+metalNum, fun, Layer.Function.CONMETAL,
                graph, via_size[i], true, false));
        }

        // Aggregating all palette groups into one
        List<PaletteGroup> allGroups = new ArrayList<PaletteGroup>();
        EGraphics graph;
        if (!isBasicCase())
            addStandardLayers(t, layerMap, nullPattern, dexclPattern);

        if (isComplexCase())
        {
            // exclusion layer N/P diff
            graph = new EGraphics(true, true, null, 2, 0, 0, 0, 1, true, dexclPattern);
            Xml.Layer exclusionDiffPLayer = makeXmlLayer(t.layers, "DEXCL-P-"+ diff_layer.name, Layer.Function.DEXCLDIFF, 0, graph,
                2*diff_width.value, true, false);
            Xml.Layer exclusionDiffNLayer = makeXmlLayer(t.layers, "DEXCL-N-"+ diff_layer.name, Layer.Function.DEXCLDIFF, 0, graph,
                2*diff_width.value, true, false);
            makeLayerGDS(t, exclusionDiffPLayer, "150/20");
            makeLayerGDS(t, exclusionDiffNLayer, "150/20");
        }

        // DeviceMark
        graph = new EGraphics(false, false, null, 0, 255, 0, 0, 0.4, true, nullPattern);
        Xml.Layer deviceMarkLayer = makeXmlLayer(t.layers, layerMap, marking_layer.name, Layer.Function.CONTROL, 0,
            graph, nplus_width, true, false);

        // Extra layers
        List<PaletteGroup> extraPaletteList = new ArrayList<PaletteGroup>();
        for (LayerInfo info : extraLayers)
        {
            graph = null;
            // either color or template
            assert (info.graphicsTemplate == null || info.graphicsColor == null);
            if (info.graphicsTemplate != null)
            {
                // look for layer name and get its EGraphics
                for (Xml.Layer l : t.layers)
                {
                    if (l.name.equals(info.graphicsTemplate))
                    {
                        graph = l.desc;
                        break;
                    }
                }
                if (graph == null)
                    System.out.println("No template layer " + info.graphicsTemplate + " found");
            }
            else if (info.graphicsColor != null)
            {
                boolean displayPatterned = (info.graphicsOutline != EGraphics.Outline.NOPAT);
                graph = new EGraphics(displayPatterned, displayPatterned, info.graphicsOutline, 0,
                    info.graphicsColor.getRed(), info.graphicsColor.getGreen(), info.graphicsColor.getBlue(),
                    1, true, info.graphicsPattern);
            }
            if (graph == null)
                graph = new EGraphics(false, false, null, 0, 255, 0, 0, 0.4, true, nullPattern);

            if (DBMath.areEquals(info.width, 0))
                System.out.println("Adding pure layer node '" + info.name + "' with zero width");
            WizardField wf = new WizardField(info.width, info.name); // name is irrelevant
            Xml.Layer layer = makeXmlLayer(t.layers, layerMap, info.name, info.function, 0, graph,
                wf, true, info.addArc, info.name);
            if (info.cif != null) layer.cif = info.cif;
            makeLayerGDS(t, layer, String.valueOf(info));

            if (info.addArc)
            {
                info.grp = new PaletteGroup();
                info.grp.addArc(makeXmlArc(t, info.name, ArcProto.Function.UNKNOWN, 0,
                    makeXmlArcLayer(layer, wf)));
                double hla = scaledValue(info.width / 2);
                info.grp.addPinOrResistor(makeXmlPrimitivePin(t, info.name, hla, null,
                    null, makeXmlNodeLayer(hla, hla, hla, hla, layer, Poly.Type.CROSSED)), null);
                extraPaletteList.add(info.grp);
            }

            // Adding 3D info
            if (info.height > -1) // -1 is the default valuu
                layer.height3D = info.height;
            if (info.thickness > -1) // -1 is the default valuu
                layer.thick3D = info.thickness;
        }

        // Generic contacts which might be based on extraLayers
        addGenericContacts(t, genericContacts, extraPaletteList);

        // Palette elements should be added at the end so they will appear in groups
        PaletteGroup[] metalPalette = new PaletteGroup[num_metal_layers];

         /**************************** Metal Nodes/Arcs ***********************************************/
        // write metal arcs
        for(int i=1; i<=num_metal_layers; i++)
        {
            double ant = (int)Math.round(metal_antenna_ratio[i-1]) | 200;
            PaletteGroup group = new PaletteGroup();
            metalPalette[i-1] = group;
            group.addArc(makeXmlArc(t, "Metal-"+i, ArcProto.Function.getContact(i), ant,
                makeXmlArcLayer(metalLayers.get(i-1), metal_width[i-1])));
        }

        if (!isBasicCase())
            addStandardElements(t, layerMap, metalLayers, allGroups);

        /**************************** Metals Nodes/Arcs ***********************************************/

        // Pins
        for (int i = 0; i < num_metal_layers; i++)
        {
            double hla = scaledValue(metal_width[i].value / 2);
            Xml.Layer lt = metalLayers.get(i);
            PaletteGroup group = metalPalette[i];  // structure created by the arc definition
            group.addPinOrResistor(makeXmlPrimitivePin(t, lt.name, hla, null, //new SizeOffset(hla, hla, hla, hla),
                null, makeXmlNodeLayer(hla, hla, hla, hla, lt, Poly.Type.CROSSED)), null);
        }

        // contacts
        for(int i=1; i<num_metal_layers; i++)
		{
            Xml.Layer lb = metalLayers.get(i-1);
            Xml.Layer lt = metalLayers.get(i);
            PaletteGroup group = metalPalette[i-1];  // structure created by the arc definition

            if (!isComplexCase())
            {
                // original contact Square
                // via
                Xml.Layer via = viaLayers.get(i-1);
                double viaSize = scaledValue(via_size[i-1].value);
                double viaSpacing = scaledValue(via_inline_spacing[i-1].value);
                double viaArraySpacing = scaledValue(via_array_spacing[i-1].value);
                String name = lb.name + "-" + lt.name;

                double longDist = scaledValue(via_overhang[i-1].value);
                group.addElement(makeContactSeries(t.nodeGroups, name, viaSize, via, viaSpacing, viaArraySpacing,
                    longDist, lt, longDist, lb), null);
            }
        }

        List<String> portNames = new ArrayList<String>();

        // metal contacts
        for (Map.Entry<String,List<Contact>> e : metalContacts.entrySet())
        {
            // generic contacts
            for (Contact c : e.getValue())
            {
                // We know those layer names are numbers!
                assert(c.layers.size() == 2);
                ContactNode verticalLayer = c.layers.get(0);
                ContactNode horizontalLayer = c.layers.get(1);

                int i = Integer.valueOf(verticalLayer.layer);
                int j = Integer.valueOf(horizontalLayer.layer);
                Xml.Layer ly = metalLayers.get(i-1);
                Xml.Layer lx = metalLayers.get(j-1);
                String name = (j>i)?ly.name + "-" + lx.name:lx.name + "-" + ly.name;
                int via = (j>i)?i:j;
                double metalContSize = scaledValue(via_size[via-1].value);
                double spacing = scaledValue(via_inline_spacing[via-1].value);
                double arraySpacing = scaledValue(via_array_spacing[via-1].value);
                Xml.Layer metalConLayer = viaLayers.get(via-1);
                double h1x = scaledValue(via_size[via-1].value /2 + verticalLayer.valueX.value);
                double h1y = scaledValue(via_size[via-1].value /2 + verticalLayer.valueY.value);
                double h2x = scaledValue(via_size[via-1].value /2 + horizontalLayer.valueX.value);
                double h2y = scaledValue(via_size[via-1].value /2 + horizontalLayer.valueY.value);
                double longX = scaledValue(Math.abs(verticalLayer.valueX.value - horizontalLayer.valueX.value));
                double longY = scaledValue(Math.abs(verticalLayer.valueY.value - horizontalLayer.valueY.value));
                portNames.clear();
                portNames.add(lx.name);
                portNames.add(ly.name);

                // some primitives might not have prefix. "-" should not be in the prefix to avoid
                // being displayed in the palette
                String p = (c.prefix == null || c.prefix.equals("")) ? "" : c.prefix + "-";
                metalPalette[via-1].addElement(makeXmlPrimitiveCon(t.nodeGroups, p + name, PrimitiveNode.Function.CONTACT, -1, -1,
                    new SizeOffset(longX, longX, longY, longY),
                    portNames,
                    makeXmlNodeLayer(h1x, h1x, h1y, h1y, ly, Poly.Type.FILLED), // layer1
                    makeXmlNodeLayer(h2x, h2x, h2y, h2y, lx, Poly.Type.FILLED), // layer2
                    makeXmlMulticut(metalConLayer, metalContSize, spacing, arraySpacing)), c.prefix); // contact
            }
        }

        // Aggregating all palette groups into one
        for (PaletteGroup g : metalPalette)
            allGroups.add(g);
        // Extra layers with pins/arcs
        allGroups.addAll(extraPaletteList);

        // Adding elements in palette
        for (PaletteGroup o : allGroups)
        {
            t.menuPalette.menuBoxes.add(o.arcs);  // arcs
            t.menuPalette.menuBoxes.add(o.pins);  // pins
            t.menuPalette.menuBoxes.add(o.elements);  // contacts
        }

        // Writting GDS values
        makeLayerGDS(t, deviceMarkLayer, String.valueOf(marking_layer));

        for (int i = 0; i < num_metal_layers; i++) {
            Xml.Layer met = metalLayers.get(i);
            makeLayerGDS(t, met, String.valueOf(metal_layers[i]));

            if (isComplexCase())
            {
                // Type is always 1
                makeLayerGDS(t, dummyMetalLayers.get(i), metal_layers[i].value + "/1");
                // exclusion always takes 150
                makeLayerGDS(t, exclusionMetalLayers.get(i), "150/" + (i + 1));
            }

            if (i > num_metal_layers - 2) continue;

            Xml.Layer via = viaLayers.get(i);
            makeLayerGDS(t, via, String.valueOf(via_layers[i]));
        }

        //
        // Writting Layer Rules
        //
        
        // Simple spacing rules included here
        for (int i = 0; i < num_metal_layers; i++) {
            Xml.Layer met = metalLayers.get(i);
            makeLayerRuleMinWid(t, met, metal_width[i]); 
            makeLayersRule(t, met, DRCTemplate.DRCRuleType.SPACING, metal_spacing[i].rule, metal_spacing[i].value);

            if (i >= num_metal_layers - 1) continue;
            Xml.Layer via = viaLayers.get(i);
            makeLayerRuleMinWid(t, via, via_size[i]);
            makeLayersRule(t, via, DRCTemplate.DRCRuleType.SPACING, via_inline_spacing[i].rule, via_inline_spacing[i].value);
        }
        // wide metal rules
        for (WideWizardField w : wide_metal_spacing)
        {
            for (String layerName : w.names)
            {
                Xml.Layer layer = t.findLayer(layerName);
                assert(layer != null);
                makeLayersWideRule(t, layer, DRCTemplate.DRCRuleType.SPACING, w.rule, w.value, w.maxW, w.minLen);
            }
        }
        // spacing/min rules in extra layers
        for (LayerInfo layer : extraLayers)
        {
            Xml.Layer l = t.findLayer(layer.name);
            if (layer.minimum != null)
                makeLayerRuleMinWid(t, l, layer.minimum);
            if (layer.spacing != null)
                makeLayersRule(t, l, DRCTemplate.DRCRuleType.SPACING, layer.spacing.rule, layer.spacing.value);
        }

        // Finish menu with Pure, Misc and Cell
        List<Object> l = new ArrayList<Object>();
        l.add(new String("Pure"));
        t.menuPalette.menuBoxes.add(l);
        l = new ArrayList<Object>();
        l.add(new String("Misc."));
        t.menuPalette.menuBoxes.add(l);
        l = new ArrayList<Object>();
        l.add(new String("Cell"));
        t.menuPalette.menuBoxes.add(l);

        // Sort before writing data. We might need to sort primitive nodes in group before...
        Collections.sort(t.nodeGroups, primitiveNodeGroupSort);
        for (Xml.PrimitiveNodeGroup nodeGroup: t.nodeGroups)
        {
            // sort NodeLayer before writing them
            Collections.sort(nodeGroup.nodeLayers, nodeLayerSort);
        }

        // write finally the file
        boolean includeDateAndVersion = User.isIncludeDateAndVersionInOutput();
        String copyrightMessage = IOTool.isUseCopyrightMessage() ? IOTool.getCopyrightMessage() : null;
        t.writeXml(fileName, includeDateAndVersion, copyrightMessage);
    }

    private PrimitiveNode.Function getWellContactFunction(int i)
    {
        if (i == Technology.P_TYPE)
            return (pSubstrateProcess) ? PrimitiveNode.Function.SUBSTRATE : PrimitiveNode.Function.WELL;
        return (pSubstrateProcess) ? PrimitiveNode.Function.WELL : PrimitiveNode.Function.SUBSTRATE;
    }

    private void prepareTransistor(double gateWidth, double gateLength, double polyEndcap, double diffPolyOverhang,
                                   double gateContactSpacing, double contactSize,
                                   Xml.Layer activeLayer, Xml.Layer polyLayer, Xml.Layer polyGateLayer,
                                   List<Xml.NodeLayer> nodesList, List<Xml.PrimitivePort> nodePorts)
    {
        double impx = scaledValue((gateWidth)/2);
        double impy = scaledValue((gateLength+diffPolyOverhang*2)/2);
        double diffY = scaledValue(gateLength/2+gateContactSpacing+contactSize/2);  // impy
        double diffX = 0;
        double xSign = 1, ySign = -1;

        // Active layers
        nodesList.add(makeXmlNodeLayer(impx, impx, impy, impy, activeLayer, Poly.Type.FILLED, true, false, -1));
        // electrical active layers
        nodesList.add(makeXmlNodeLayer(impx, impx, impy, 0, activeLayer, Poly.Type.FILLED, false, true, 3));  // bottom
        nodesList.add(makeXmlNodeLayer(impx, impx, 0, impy, activeLayer, Poly.Type.FILLED, false, true, 1));  // top

        // Diff port
        List<String> portNames = new ArrayList<String>();
        portNames.add(activeLayer.name);

        // top port
        Xml.PrimitivePort diffTopPort = makeXmlPrimitivePort("diff-top", 90, 90, 1, null,
            diffX, -1, diffX, 1, diffY, 1, diffY, 1, portNames);
        // bottom port
        Xml.PrimitivePort diffBottomPort = makeXmlPrimitivePort("diff-bottom", 270, 90, 2, null,
            xSign*diffX, -1, xSign*diffX, 1, ySign*diffY, -1, ySign*diffY, -1, portNames);

        // Electric layers
        // Gate layer Electrical
        double gatey = scaledValue(gateLength/2);
        double gatex = impx;
        double endPolyx = scaledValue((gateWidth+polyEndcap*2)/2);
        double endPolyy = gatey;
        double endLeftOrRight = -impx;
        double endTopOrBotton = endPolyy;
        double polyX = endPolyx;
        double polyY = 0;
        nodesList.add(makeXmlNodeLayer(gatex, gatex, gatey, gatey, polyGateLayer, Poly.Type.FILLED, false, true, -1));

        // Poly layers
        // left electrical
        nodesList.add(makeXmlNodeLayer(endPolyx, endLeftOrRight, endPolyy, endTopOrBotton, polyLayer,
            Poly.Type.FILLED, false, true, 0));
        // right electrical
        nodesList.add(makeXmlNodeLayer(endLeftOrRight, endPolyx, endTopOrBotton, endPolyy, polyLayer,
            Poly.Type.FILLED, false, true, 2));

        // non-electrical poly (just one poly layer)
        nodesList.add(makeXmlNodeLayer(endPolyx, endPolyx, endPolyy, endPolyy, polyLayer, Poly.Type.FILLED, true, false, -1));

        // Poly port
        portNames.clear();
        portNames.add(polyLayer.name);
        Xml.PrimitivePort polyLeftPort = makeXmlPrimitivePort("poly-left", 180, 90, 0, null,
            ySign*polyX, -1, ySign*polyX,
            -1, xSign*polyY, -1, xSign*polyY, 1, portNames);
        // right port
        Xml.PrimitivePort polyRightPort = makeXmlPrimitivePort("poly-right", 0, 180, 0, null,
            polyX, 1, polyX, 1, polyY, -1, polyY, 1, portNames);

        nodePorts.clear();
        nodePorts.add(polyLeftPort);
        nodePorts.add(diffTopPort);
        nodePorts.add(polyRightPort);
        nodePorts.add(diffBottomPort);
    }

    private Xml.ArcLayer makeXmlArcLayer(Xml.Layer layer, WizardField ... flds) {
        Xml.ArcLayer al = new Xml.ArcLayer();
        al.layer = layer.name;
        al.style = Poly.Type.FILLED;
        for (int i = 0; i < flds.length; i++)
            al.extend.addLambda(scaledValue(flds[i].value /2));
        return al;
    }

//    private Technology.Distance makeXmlDistance(WizardField ... flds) {
//        Technology.Distance dist = new Technology.Distance();
//        dist.addRule(flds[0].rule, 0.5);
//        for (int i = 1; i < flds.length; i++)
//            dist.addRule(flds[i].rule, 1);
//        return dist;
//    }

    private void makeLayerGDS(Xml.Technology t, Xml.Layer l, String gdsVal) {
        for (Xml.Foundry f: t.foundries) {
            f.layerGds.put(l.name, gdsVal);
        }
    }

    private void makeLayerRuleMinWid(Xml.Technology t, Xml.Layer l, WizardField fld) {
        for (Xml.Foundry f: t.foundries) {
            f.rules.add(new DRCTemplate(fld.rule, DRCTemplate.DRCMode.ALL.mode(), DRCTemplate.DRCRuleType.MINWID,
                l.name, null, new double[] {scaledValue(fld.value)}, null, null));
        }
    }

    private void makeLayersWideRule(Xml.Technology t, Xml.Layer l, DRCTemplate.DRCRuleType ruleType, String ruleName, 
                                    double ruleValue, double maxW, double minLen) {
        for (Xml.Foundry f: t.foundries) {
            f.rules.add(new DRCTemplate(ruleName, DRCTemplate.DRCMode.ALL.mode(), ruleType, maxW, minLen,
                l.name, l.name, new double[] {scaledValue(ruleValue)}, -1));
        }
    }

    private void makeLayersRule(Xml.Technology t, Xml.Layer l, DRCTemplate.DRCRuleType ruleType, String ruleName, double ruleValue) {
        for (Xml.Foundry f: t.foundries) {
            f.rules.add(new DRCTemplate(ruleName, DRCTemplate.DRCMode.ALL.mode(), ruleType,
                l.name, l.name, new double[] {scaledValue(ruleValue)}, null, null));
        }
    }

    private void makeLayersRuleSpacing(Xml.Technology t, Xml.Layer l1, Xml.Layer l2, String ruleName, double ruleValue) {
        double value = scaledValue(ruleValue);
        for (Xml.Foundry f: t.foundries) {
            f.rules.add(new DRCTemplate(ruleName, DRCTemplate.DRCMode.ALL.mode(), DRCTemplate.DRCRuleType.SPACING,
                l1.name, l2.name, new double[] {value, value}, null, null));
        }
    }

    private void makeLayersRuleSurround(Xml.Technology t, Xml.Layer l1, Xml.Layer l2, String ruleName, double ruleValue) {
        double value = scaledValue(ruleValue);
        for (Xml.Foundry f: t.foundries) {
            f.rules.add(new DRCTemplate(ruleName, DRCTemplate.DRCMode.ALL.mode(), DRCTemplate.DRCRuleType.SURROUND,
                l1.name, l2.name, new double[] {value, value}, null, null));
        }
    }

    private double scaledValue(double val) { return DBMath.round(val / stepsize); }

    /***************************************************************************************************
     * Analog Elements
     ***************************************************************************************************/

    private void createSecondPolyElements(Xml.Technology t, Map<Xml.Layer,WizardField> layerMap,
                                          List<PaletteGroup> polysGroup)
    {
        int[] nullPattern = new int[] {44975, 34952, 64250, 34952, 44975, 34952, 64250, 34952,
            44975, 34952, 64250, 34952, 44975, 34952, 64250, 34952};
        EGraphics graph = new EGraphics(true, true, null, 0, 255, 190, 6, 1, true, nullPattern);
        Xml.Layer poly2Layer = makeXmlLayer(t.layers, layerMap, poly2_layer.name, Layer.Function.POLY2, 0, graph,
            poly_width, true, true);

        PaletteGroup poly2Group = new PaletteGroup();
        double ant = (int)Math.round(poly_antenna_ratio) | 200;
        // Arc
        poly2Group.addArc(makeXmlArc(t, poly2Layer.name, ArcProto.Function.getPoly(2), ant,
            makeXmlArcLayer(poly2Layer, poly_width)));
        polysGroup.add(poly2Group);

        // pin
        double hla = scaledValue(poly_width.value / 2);
        poly2Group.addPinOrResistor(makeXmlPrimitivePin(t, poly2Layer.name, hla, null, // new SizeOffset(hla, hla, hla, hla),
            null, makeXmlNodeLayer(hla, hla, hla, hla, poly2Layer, Poly.Type.CROSSED)), null);
    }

    private void createAnalogElements(Xml.Technology t, List<Xml.Layer> metalLayers, List<PaletteGroup> polysGroup)
    {
        List<Xml.NodeLayer> nodesList = new ArrayList<Xml.NodeLayer>();
        List<Xml.PrimitivePort> nodePorts = new ArrayList<Xml.PrimitivePort>();
        Xml.Layer poly2Layer = t.findLayer(poly2_layer.name);

        assert(poly2Layer != null);

        Xml.Layer hiRestLayer = t.findLayer("Hi-Res");
        Xml.Layer polyConLayer = t.findLayer(poly_layer.name+"-Cut");

        PaletteGroup g = polysGroup.get(1); // second group in polys


        /*************************************/
        // Analog Capacitors
        /*************************************/
        for (Map.Entry<String,List<Contact>> e : capacitors.entrySet())
        {
            addContactsOrCapacitors(t, e.getValue(), metalLayers, null, null, g, true);
        }

        /*************************************/
        // Analog Hi Poly Resistors
        /*************************************/
        WizardField polyRL = findWizardField("hi_poly_resistor_length");
        WizardField poly2Overhang = findWizardField("contact_poly2_overhang");
        WizardField hiRestOverhang = findWizardField("hi-res_overhang");

        // using array value to guarantee proper spacing in nD cases
        addMetalElements(t, polyConLayer, contact_array_spacing.value, polyRL, poly2Overhang, nodesList, nodePorts);

        // poly
        double polyNoScaled = 2 * (poly2Overhang.value) + contact_size.value;
        double soxNoScaled = /*(hiRestOverhang.value) + */polyNoScaled;
        double polyL = scaledValue(polyRL.value /2 + polyNoScaled);
        double polyWNoScaled = scaledValue(contact_size.value/2 + poly2Overhang.value);
        double polyW = scaledValue(polyWNoScaled);
        nodesList.add(makeXmlNodeLayer(polyL, polyL, polyW, polyW, poly2Layer,
            Poly.Type.FILLED, true, true, 0));
        
        // hi res
        double hiresL = scaledValue(polyRL.value /2 + hiRestOverhang.value); // soxNoScaled);
        double hiresW = scaledValue(polyWNoScaled + hiRestOverhang.value);
        nodesList.add(makeXmlNodeLayer(hiresL, hiresL, hiresW, hiresW, hiRestLayer,
            Poly.Type.FILLED, true, true, 0));

        double sox = scaledValue(soxNoScaled);
        double soy = scaledValue(hiRestOverhang.value);
        Xml.PrimitiveNodeGroup n = makeXmlPrimitive(t.nodeGroups, "Hi-Res-Poly2-Resistor",
            PrimitiveNode.Function.RESHIRESPOLY2, 0, 0, 0, 0,
            new SizeOffset(sox, sox, soy, soy),
            nodesList, nodePorts, null, false);
        g.addElement(n, "Hi-RPoly2");

        /*************************************/
        // Analog Active Resistors
        /*************************************/
        WizardField activeRL = findWizardField("active_resistor_length");  //
        String[] diffNames = {"P", "N"};
        Xml.Layer activeConLayer = t.findLayer(diff_layer.name+"-Cut");

        for (int i = 0; i < 2; i++)
        {
            // active resistors
            Xml.Layer activeLayer = t.findLayer(diffNames[i]+"-"+diff_layer.name);   //$nplus_overhang_diff
            Xml.Layer selectLayer, wellLayer;
            WizardField selectWF;
            PrimitiveNode.Function func;

            nodesList.clear();
            nodePorts.clear();

            if (i==Technology.P_TYPE)
            {
                selectLayer = t.findLayer(pplus_layer.name);
                selectWF = pplus_overhang_diff;
                wellLayer = t.findLayer(nwell_layer.name);
                func = PrimitiveNode.Function.RESPACTIVE;
            }
            else
            {
                selectLayer = t.findLayer(nplus_layer.name);
                selectWF = nplus_overhang_diff;
                wellLayer = t.findLayer("P-Well");
                func = PrimitiveNode.Function.RESNACTIVE;
            }

            // active layer
            double activeNoScaled = 2 * (diff_contact_overhang.value) + contact_size.value;
            double activeL = scaledValue(activeRL.value /2 + activeNoScaled);
            double activeWNoScaled = contact_size.value/2 + diff_contact_overhang.value;
            double activeW = scaledValue(activeWNoScaled);
            nodesList.add(makeXmlNodeLayer(activeL, activeL, activeW, activeW, activeLayer, Poly.Type.FILLED, true, true, 0));

            // select layer
            double selectOverhang = scaledValue(selectWF.value);
            double selectL = selectOverhang + activeL;
            double selectW = selectOverhang + activeW;
            nodesList.add(makeXmlNodeLayer(selectL, selectL, selectW, selectW, selectLayer, Poly.Type.FILLED, true, true, 0));

            // well layer
            Xml.NodeLayer wellNodeLayer = null;
            if (!getPSubstratelProcess())
            {
                double wellOverhang = scaledValue(nwell_overhang_diff_p.value - selectWF.value);
                double wellL = wellOverhang + selectL;
                double wellW = wellOverhang + selectW;
                wellNodeLayer = makeXmlNodeLayer(wellL, wellL, wellW, wellW, wellLayer, Poly.Type.FILLED, true, true, 0);
                nodesList.add(wellNodeLayer);
            }

            addMetalElements(t, activeConLayer, contact_array_spacing.value, activeRL, diff_contact_overhang, nodesList, nodePorts);

            sox = scaledValue(nwell_overhang_diff_p.value + activeNoScaled);
            soy = scaledValue(nwell_overhang_diff_p.value);
            n = makeXmlPrimitive(t.nodeGroups, diffNames[i]+"-Active-Resistor", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy),
                nodesList, nodePorts, null, false);
            g.addElement(n, diffNames[i]+"-RActive");
        }

        /*************************************/
        // Analog Well Resistors
        /*************************************/
        WizardField wellRL = findWizardField("well_resistor_length");

        for (int i = 0; i < 2; i++)
        {
            Xml.Layer activeLayer = t.findLayer(diffNames[i]+"-"+diff_layer.name);   //$nplus_overhang_diff
            Xml.Layer selectLayer, wellLayer;
            WizardField selectWF;
            PrimitiveNode.Function func;

            nodesList.clear();
            nodePorts.clear();

            if (i==Technology.P_TYPE)
            {
                selectLayer = t.findLayer(pplus_layer.name);
                selectWF = pplus_overhang_diff;
                wellLayer = t.findLayer("P-Well");
                func = PrimitiveNode.Function.RESPWELL;
            }
            else
            {
                selectLayer = t.findLayer(nplus_layer.name);
                selectWF = nplus_overhang_diff;
                wellLayer = t.findLayer(nwell_layer.name);
                func = PrimitiveNode.Function.RESNWELL;
            }

            // active layer
            double activeNoScaled = 2 * (diff_contact_overhang.value) + contact_size.value;
            double activeDistance = scaledValue(wellRL.value /2);
            double activeX = scaledValue(2 * (diff_contact_overhang.value) + contact_size.value);
            double activeL = scaledValue(wellRL.value /2 + activeNoScaled);
            double activeWNoScaled = contact_size.value/2 + diff_contact_overhang.value;
            double activeW = scaledValue(activeWNoScaled);
            nodesList.add(makeXmlNodeLayer((activeDistance + activeX), -1, -activeDistance, -1, activeW, -1, activeW, 1, activeLayer,
                Poly.Type.FILLED, true, true, 0));
            // right metal
            nodesList.add(makeXmlNodeLayer(-activeDistance, 1, (activeDistance + activeX), 1, activeW, -1, activeW, 1, activeLayer,
                Poly.Type.FILLED, true, true, 1));

            // select layer
            double selectOverhang = scaledValue(selectWF.value);
            double selectL = selectOverhang + activeL;
            double selectW = selectOverhang + activeW;
            double selectDistance = activeDistance - scaledValue(selectWF.value);
            double selectX = activeX + 2 * (selectWF.value);
            nodesList.add(makeXmlNodeLayer((selectDistance + selectX), -1, -selectDistance, -1, selectW, -1, selectW, 1, selectLayer,
                Poly.Type.FILLED, true, true, 0));
            // right metal
            nodesList.add(makeXmlNodeLayer(-selectDistance, 1, (selectDistance + selectX), 1, selectW, -1, selectW, 1, selectLayer,
                Poly.Type.FILLED, true, true, 1));

            // well layer
            Xml.NodeLayer wellNodeLayer = null;
            if (!getPSubstratelProcess())
            {
                double wellOverhang = scaledValue(nwell_overhang_diff_n.value - selectWF.value);
                double wellL = wellOverhang + selectL;
                double wellW = wellOverhang + selectW;
                wellNodeLayer = makeXmlNodeLayer(wellL, wellL, wellW, wellW, wellLayer, Poly.Type.FILLED, true, true, 0);
                nodesList.add(wellNodeLayer);
            }

            addMetalElements(t, activeConLayer, contact_array_spacing.value, wellRL, diff_contact_overhang, nodesList, nodePorts);

            sox = scaledValue(nwell_overhang_diff_n.value) + activeX + selectOverhang;
            soy = 0; // scaledValue(nwell_overhang_diff_n.value);
            n = makeXmlPrimitive(t.nodeGroups, diffNames[i]+"-Well-Resistor", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy),
                nodesList, nodePorts, null, false);
            g.addElement(n, diffNames[i]+"-RWell");
        }

        /*************************************/
        // Analog Poly Resistors (no hi res)
        /*************************************/
        polyRL = findWizardField("poly_resistor_length");
        Xml.Layer polyLayer = t.findLayer(poly_layer.name);

        for (int i = 0; i < 2; i++)
        {
            Xml.Layer selectLayer;
            WizardField selectWF;
            PrimitiveNode.Function func;

            nodesList.clear();
            nodePorts.clear();

            if (i==Technology.P_TYPE)
            {
                selectLayer = t.findLayer(pplus_layer.name);
                selectWF = pplus_overhang_diff;
                func = PrimitiveNode.Function.RESPPOLY;
            }
            else
            {
                selectLayer = t.findLayer(nplus_layer.name);
                selectWF = nplus_overhang_diff;
                func = PrimitiveNode.Function.RESNPOLY;
            }

            // poly layer
            polyNoScaled = 2 * (contact_poly_overhang.value) + contact_size.value;
            polyL = scaledValue(polyRL.value /2 + polyNoScaled);
            polyWNoScaled = contact_size.value/2 + diff_contact_overhang.value;
            polyW = scaledValue(polyWNoScaled);
            nodesList.add(makeXmlNodeLayer(polyL, polyL, polyW, polyW, polyLayer, Poly.Type.FILLED, true, true, 0));

            // select layer
            double selectOverhang = scaledValue(selectWF.value);
            double selectL = selectOverhang + polyL;
            double selectW = selectOverhang + polyW;
            nodesList.add(makeXmlNodeLayer(selectL, selectL, selectW, selectW, selectLayer, Poly.Type.FILLED, true, true, 0));

            addMetalElements(t, polyConLayer, contact_array_spacing.value, polyRL, contact_poly_overhang, nodesList, nodePorts);

            sox = scaledValue(selectWF.value + polyNoScaled);
            soy = scaledValue(selectWF.value);
            n = makeXmlPrimitive(t.nodeGroups, diffNames[i]+"-Poly-Resistor", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy),
                nodesList, nodePorts, null, false);
            g.addElement(n, diffNames[i]+"-RPoly");
        }


        /*************************************/
        // Analog unsilicided Poly Resistors (no hi res)
        /*************************************/
        WizardField silicide_overhang = findWizardField("silicide_overhang");

        for (int i = 0; i < 2; i++)
        {
            Xml.Layer selectLayer;
            PrimitiveNode.Function func;
            WizardField selectWF;

            if (i==Technology.P_TYPE)
            {
                selectLayer = t.findLayer(pplus_layer.name);
                selectWF = pplus_overhang_diff;
                func = PrimitiveNode.Function.RESPNSPOLY;
            }
            else
            {
                selectLayer = t.findLayer(nplus_layer.name);
                selectWF = nplus_overhang_diff;
                func = PrimitiveNode.Function.RESNNSPOLY;
            }
            nodesList.clear();
            nodePorts.clear();

            // poly layer
//            polyNoScaled = 2 * (contact_poly_overhang.value) + contact_size.value;
            polyNoScaled = contact_poly_overhang.value + silicide_overhang.value + contact_size.value; // due to silicide block not covering cut
            polyL = scaledValue(polyRL.value /2 + polyNoScaled);
            polyWNoScaled = contact_size.value/2 + diff_contact_overhang.value;
            polyW = scaledValue(polyWNoScaled);
            nodesList.add(makeXmlNodeLayer(polyL, polyL, polyW, polyW, polyLayer, Poly.Type.FILLED, true, true, 0));

            // select layer
            double selectOverhang = scaledValue(selectWF.value);
            double selectL = selectOverhang + polyL;
            double selectW = selectOverhang + polyW;
            nodesList.add(makeXmlNodeLayer(selectL, selectL, selectW, selectW, selectLayer, Poly.Type.FILLED, true, true, 0));
            WizardField len = polyRL;

            // fake WizardField to compensate  silicide_overhang.value - contact_poly_overhang.value
            if (silicide_overhang.value > contact_poly_overhang.value)
            {
                len = new WizardField(polyRL.value + 2*(silicide_overhang.value - contact_poly_overhang.value), "modified polyRL");
            }

            // silicide_block layer
            Xml.Layer silicideLayer = t.findLayer(marking_layer.name);
            double silicideOverhang = scaledValue(silicide_overhang.value);
            double silicideL = scaledValue(polyRL.value /2); // silicideOverhang + selectL;
            double silicideW = silicideOverhang + selectW;
            nodesList.add(makeXmlNodeLayer(silicideL, silicideL, silicideW, silicideW, silicideLayer, Poly.Type.FILLED, true, true, 0));

            addMetalElements(t, polyConLayer, contact_array_spacing.value, len, contact_poly_overhang, nodesList, nodePorts);

            sox = scaledValue(/*silicide_overhang.value + */selectWF.value + polyNoScaled);
            soy = scaledValue(silicide_overhang.value + selectWF.value);
            n = makeXmlPrimitive(t.nodeGroups, diffNames[i]+"-No-Silicide-Poly-Resistor",
                func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy),
                nodesList, nodePorts, null, false);
            g.addElement(n, diffNames[i]+"-RNSPoly");
        }
    }

    private void addMetalElements(Xml.Technology t, Xml.Layer conLayer, double spacing, WizardField width, WizardField overhang,
                                  List<Xml.NodeLayer> nodesList, List<Xml.PrimitivePort> nodePorts)
    {
        Xml.Layer m1Layer = t.findLayer("Metal-1");
        List<String> portNames = new ArrayList<String>();
        portNames.add(m1Layer.name);

        // metal left
        double m1Y = scaledValue(contact_metal_overhang_all_sides.value + contact_size.value/2);
        double m1X = scaledValue(2*contact_metal_overhang_all_sides.value + contact_size.value);
        double m1Distance = scaledValue(width.value/2 + overhang.value - contact_metal_overhang_all_sides.value);
        nodesList.add(makeXmlNodeLayer((m1Distance + m1X), -1, -m1Distance, -1, m1Y, -1, m1Y, 1, m1Layer,
            Poly.Type.FILLED, true, true, 0));
        // right metal
        nodesList.add(makeXmlNodeLayer(-m1Distance, 1, (m1Distance + m1X), 1, m1Y, -1, m1Y, 1, m1Layer,
            Poly.Type.FILLED, true, true, 1));

        // left port
        double contSize = scaledValue(contact_size.value);
        double cutSizeHalf = scaledValue(contact_size.value /2);
        double cutStart = scaledValue(width.value /2 + overhang.value);
        Xml.PrimitivePort port = makeXmlPrimitivePort("left", 0, 180, 0, null,
            -(cutStart + contSize), -1, -cutStart, -1, -cutSizeHalf, -1, cutSizeHalf, 1, portNames);
        nodePorts.add(port);
        // right port
        port = makeXmlPrimitivePort("right", 0, 180, 1, null,
            cutStart, 1, (cutStart + contSize), 1, -cutSizeHalf, -1, cutSizeHalf, 1, portNames);
        nodePorts.add(port);

        // Cuts
        double cutEnd = scaledValue(width.value/2 + overhang.value);
        double cutEndY = 0; // not sure why has to be zero and not scaledValue(contact_size.value/2);
        // left
        nodesList.add(makeXmlMulticut(cutEnd+contSize, -1, -cutEnd, -1, cutEndY, -1, cutEndY, 1,
            conLayer, contSize, spacing, spacing));
        // right
        nodesList.add(makeXmlMulticut(-cutEnd, 1, cutEnd+contSize, 1, cutEndY, -1, cutEndY, 1,
            conLayer, contSize, spacing, spacing));
    }

    private void addStandardLayers(Xml.Technology t, Map<Xml.Layer, WizardField> layerMap,
                                   int[] nullPattern, int[] dexclPattern)
    {
		Color contact_colour = new Color(100,100,100);   // darker gray
        Color nplus_colour = new Color(224,238,224);
		Color pplus_colour = new Color(224,224,120);
		Color nwell_colour = new Color(140,140,140);

        //
        // Adding necessary layers
        //
        // Poly
        EGraphics graph = new EGraphics(false, false, null, 1, 0, 0, 0, 1, true, nullPattern);
        /* Xml.Layer polyLayer = */ makeXmlLayer(t.layers, layerMap, poly_layer.name, Layer.Function.POLY1, 0, graph,
            poly_width, true, true);
        // PolyGate
        /* Xml.Layer polyGateLayer = */ makeXmlLayer(t.layers, layerMap, poly_layer.name+"Gate", Layer.Function.GATE, 0, graph,
            poly_width, true, false); // false for the port otherwise it won't find any type

        if (isComplexCase())
        {
            // exclusion layer poly
            graph = new EGraphics(true, true, null, 1, 0, 0, 0, 1, true, dexclPattern);
            Xml.Layer exclusionPolyLayer = makeXmlLayer(t.layers, "DEXCL-"+poly_layer.name, Layer.Function.DEXCLPOLY1, 0, graph,
                2*poly_width.value, true, false);
            makeLayerGDS(t, exclusionPolyLayer, "150/21");
        }

        // PolyCon and DiffCon
        graph = new EGraphics(false, false, null, 0, contact_colour.getRed(), contact_colour.getGreen(),
            contact_colour.getBlue(), 0.5, true, nullPattern);
        // PolyCon
        /* Xml.Layer polyConLayer = */ makeXmlLayer(t.layers, layerMap, "Poly-Cut", Layer.Function.CONTACT1,
            Layer.Function.CONPOLY, graph, contact_size, true, false);
        // DiffCon
        /* Xml.Layer diffConLayer = */ makeXmlLayer(t.layers, layerMap, diff_layer.name+"-Cut", Layer.Function.CONTACT1,
            Layer.Function.CONDIFF, graph, contact_size, true, false);

        // P-Diff and N-Diff
        graph = new EGraphics(false, false, null, 2, 0, 0, 0, 1, true, nullPattern);
        // N-Diff
        /* Xml.Layer diffNLayer = */ makeXmlLayer(t.layers, layerMap, "N-"+ diff_layer.name, Layer.Function.DIFFN, 0, graph,
            diff_width, true, true, "N-"+ diff_layer.name, "N-Well", "S-N-Well");
        // P-Diff                                                                                                    dd
        /* Xml.Layer diffPLayer = */ makeXmlLayer(t.layers, layerMap, "P-"+ diff_layer.name, Layer.Function.DIFFP, 0, graph,
            diff_width, true, true, "P-"+ diff_layer.name, "P-Well", "S-P-Well");

                // NPlus and PPlus
        int [] patternSlash = new int[] { 0x1010,   //    X       X
                        0x2020,   //   X       X
                        0x4040,   //  X       X
                        0x8080,   // X       X
                        0x0101,   //        X       X
                        0x0202,   //       X       X
                        0x0404,   //      X       X
                        0x0808,   //     X       X
                        0x1010,   //    X       X
                        0x2020,   //   X       X
                        0x4040,   //  X       X
                        0x8080,   // X       X
                        0x0101,   //        X       X
                        0x0202,   //       X       X
                        0x0404,   //      X       X
                        0x0808};

        int [] patternBackSlash = new int[] { 0x0202,   //       X       X
                        0x0101,   //        X       X
                        0x8080,   // X       X
                        0x4040,   //  X       X
                        0x2020,   //   X       X
                        0x1010,   //    X       X
                        0x0808,   //     X       X
                        0x0404,   //      X       X
                        0x0202,   //       X       X
                        0x0101,   //        X       X
                        0x8080,   // X       X
                        0x4040,   //  X       X
                        0x2020,   //   X       X
                        0x1010,   //    X       X
                        0x0808,   //     X       X
                        0x0404};

        int[] patternDots = new int[] {
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000};   //

        int[] patternDotsShift = new int[] {
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202,   //       X       X
                        0x0000,   //
                        0x2020,   //   X       X
                        0x0000,   //
                        0x0202};   //       X       X
        // NPlus
        graph = new EGraphics(true, true, null, 0, nplus_colour.getRed(), nplus_colour.getGreen(),
            nplus_colour.getBlue(), 1, true, patternSlash);
        /* Xml.Layer nplusLayer = */ makeXmlLayer(t.layers, layerMap, nplus_layer.name, Layer.Function.IMPLANTN, 0, graph,
            nplus_width, true, false);
        // PPlus
        graph = new EGraphics(true, true, null, 0, pplus_colour.getRed(), pplus_colour.getGreen(),
            pplus_colour.getBlue(), 1, true, patternDots);
        /* Xml.Layer pplusLayer = */ makeXmlLayer(t.layers, layerMap, pplus_layer.name, Layer.Function.IMPLANTP, 0, graph,
            pplus_width, true, false);

        // N-Well
        graph = new EGraphics(true, true, null, 0, nwell_colour.getRed(), nwell_colour.getGreen(),
            nwell_colour.getBlue(), 1, true, patternDotsShift);
        /* Xml.Layer nwellLayer = */ makeXmlLayer(t.layers, layerMap, nwell_layer.name, Layer.Function.WELLN, 0, graph,
            nwell_width, true, false);
        // P-Well
        graph = new EGraphics(true, true, null, 0, nwell_colour.getRed(), nwell_colour.getGreen(),
            nwell_colour.getBlue(), 1, true, patternBackSlash);
        /* Xml.Layer pwellLayer = */ makeXmlLayer(t.layers, layerMap, "P-Well", Layer.Function.WELLP, 0, graph,
            nwell_width, true, false);
    }

    private void addStandardElements(Xml.Technology t, Map<Xml.Layer, WizardField> layerMap,
                                     List<Xml.Layer> metalLayers, List<PaletteGroup> allGroups)
    {
        /**************************** POLY Nodes/Arcs ***********************************************/
        // poly arc
        double ant = (int)Math.round(poly_antenna_ratio) | 200;
        List<PaletteGroup> polysGroup = new ArrayList<PaletteGroup>();
        PaletteGroup polyGroup = new PaletteGroup();
        polysGroup.add(polyGroup);

        List<String> portNames = new ArrayList<String>();
        Xml.Layer polyLayer = t.findLayer(poly_layer.name);
        Xml.Layer polyGateLayer = t.findLayer(poly_layer.name+"Gate");
        Xml.Layer polyConLayer = t.findLayer("Poly-Cut");
        Xml.Layer diffConLayer = t.findLayer(diff_layer.name+"-Cut");
        Xml.Layer nwellLayer = t.findLayer(nwell_layer.name);
        Xml.Layer pwellLayer = t.findLayer("P-Well");
        Xml.Layer nplusLayer = t.findLayer(nplus_layer.name);
        Xml.Layer pplusLayer = t.findLayer(pplus_layer.name);
        Xml.Layer diffNLayer = t.findLayer("N-"+ diff_layer.name);
        Xml.Layer diffPLayer = t.findLayer("P-"+ diff_layer.name);

        polyGroup.addArc(makeXmlArc(t, polyLayer.name, ArcProto.Function.getPoly(1), ant,
                makeXmlArcLayer(polyLayer, poly_width)));
        // poly pin
        double hla = scaledValue(poly_width.value / 2);
        polyGroup.addPinOrResistor(makeXmlPrimitivePin(t, polyLayer.name, hla, null, // new SizeOffset(hla, hla, hla, hla),
            null, makeXmlNodeLayer(hla, hla, hla, hla, polyLayer, Poly.Type.CROSSED)), null);

        if (getSecondPolyFlag()) createSecondPolyElements(t, layerMap, polysGroup);
        if (getAnalogFlag()) createAnalogElements(t, metalLayers, polysGroup);

        Xml.Layer m1Layer = metalLayers.get(0);
        // poly contact
        portNames.clear();
        portNames.add(polyLayer.name);
        portNames.add(m1Layer.name);
        hla = scaledValue((contact_size.value /2 + contact_poly_overhang.value));
        double contSize = scaledValue(contact_size.value);
        double contSpacing = scaledValue(contact_spacing.value);
        double contArraySpacing = scaledValue(contact_array_spacing.value);
        double metal1Over = scaledValue(contact_size.value /2 + contact_metal_overhang_all_sides.value);

        // only for standard cases when getExtraInfoFlag() is false
        if (!isComplexCase())
        {
            if (POLY_CONTACT_SAME_AS_DIFF_CONTACT) {
                polyGroup.addElement(makeContactSeries(t.nodeGroups, polyLayer.name, contSize, polyConLayer, contSpacing, contArraySpacing,
                        scaledValue(contact_poly_overhang.value), polyLayer,
                        scaledValue(contact_metal_overhang_all_sides.value), m1Layer), null);
            } else {
                if (via_overhang.length > 0)
                    polyGroup.addElement(makeContactSeries(t.nodeGroups, polyLayer.name, contSize, polyConLayer, contSpacing, contArraySpacing,
                            scaledValue(contact_poly_overhang.value), polyLayer,
                            scaledValue(via_overhang[0].value), m1Layer), null);
                else
                    System.out.println("Not via 0 layer");
            }
        }

        /**************************** N/P-Diff Nodes/Arcs/Group ***********************************************/
        PaletteGroup[] diffPalette = new PaletteGroup[2];
        diffPalette[0] = new PaletteGroup(); diffPalette[1] = new PaletteGroup();

        PaletteGroup[] wellPalette = new PaletteGroup[2];
        wellPalette[0] = new PaletteGroup(); wellPalette[1] = new PaletteGroup();

        // ndiff/pdiff pins
        hla = scaledValue((contact_size.value /2 + diff_contact_overhang.value));
        double nsel = scaledValue(contact_size.value /2 + diff_contact_overhang.value + nplus_overhang_diff.value);
        double psel = scaledValue(contact_size.value /2 + diff_contact_overhang.value + pplus_overhang_diff.value);
        double nwell = scaledValue(contact_size.value /2 + diff_contact_overhang.value + nwell_overhang_diff_p.value);
        double nso = scaledValue(nwell_overhang_diff_p.value /*+ diff_contact_overhang.v*/); // valid for elements that have nwell layers
        double pso = (!pSubstrateProcess)?nso:scaledValue(nplus_overhang_diff.value/* + diff_contact_overhang.v*/);

        // ndiff/pdiff contacts
        String[] diffNames = {"P", "N"};
        double[] sos = {nso, pso};
        double[] sels = {psel, nsel};
        Xml.Layer[] diffLayers = {diffPLayer, diffNLayer};
        Xml.Layer[] plusLayers = {pplusLayer, nplusLayer};
//        Xml.Layer pol2yLayer = t.findLayer(poly2_layer.name);

        // Active and poly contacts. They are defined first that the Full types
        for (Map.Entry<String,List<Contact>> e : otherContacts.entrySet())
        {
            addContactsOrCapacitors(t, e.getValue(), metalLayers, diffPalette, wellPalette, polyGroup, false);
//            // generic contacts
//            String name = null;
//
//            for (Contact c : e.getValue())
//            {
//                Xml.Layer ly = null, lx = null;
//                Xml.Layer conLay = diffConLayer;
//                PaletteGroup g = null;
//                ContactNode metalLayer = c.layers.get(0);
//                ContactNode otherLayer = c.layers.get(1);
//                String extraName = "";
//
//                if (!TextUtils.isANumber(metalLayer.layer)) // horizontal must be!
//                {
//                    assert (TextUtils.isANumber(otherLayer.layer));
//                    metalLayer = c.layers.get(1);
//                    otherLayer = c.layers.get(0);
//                }
//
//                int m1 = Integer.valueOf(metalLayer.layer);
//                ly = metalLayers.get(m1-1);
//                String layerName = otherLayer.layer;
//                if (layerName.equals(diffLayers[0].name))
//                {
//                    lx = diffLayers[0];
//                    g = diffPalette[0];
//                    extraName = "P";
//                }
//                else if (layerName.equals(diffLayers[1].name))
//                {
//                    lx = diffLayers[1];
//                    g = diffPalette[1];
//                    extraName = "N";
//                }
//                else if (layerName.equals(polyLayer.name))
//                {
//                    lx = polyLayer;
//                    conLay = polyConLayer;
//                    g = polyGroup;
//                }
//                else if (getSecondPolyFlag() && layerName.equals(pol2yLayer.name))
//                {
//                    lx = pol2yLayer;
//                    conLay = polyConLayer;
//                    g = polyGroup;
//                }
//                else
//                    assert(false); // it should not happen
//                double h1x = scaledValue(contact_size.value /2 + metalLayer.valueX.value);
//                double h1y = scaledValue(contact_size.value /2 + metalLayer.valueY.value);
//                double h2x = scaledValue(contact_size.value /2 + otherLayer.valueX.value);
//                double h2y = scaledValue(contact_size.value /2 + otherLayer.valueY.value);
//                double longX = (Math.abs(metalLayer.valueX.value - otherLayer.valueX.value));
//                double longY = (Math.abs(metalLayer.valueY.value - otherLayer.valueY.value));
//
//                PrimitiveNode.Function func = PrimitiveNode.Function.CONTACT;
//                Xml.NodeLayer[] nodes = new Xml.NodeLayer[c.layers.size() + 1]; // all plus cut
//                int count = 0;
//
//                // cut
//                nodes[count++] = makeXmlMulticut(conLay, contSize, contSpacing, contArraySpacing);
//                // metal
//                nodes[count++] = makeXmlNodeLayer(h1x, h1x, h1y, h1y, ly, Poly.Type.FILLED); // layer1
//                // active or poly
//                nodes[count++] = makeXmlNodeLayer(h2x, h2x, h2y, h2y, lx, Poly.Type.FILLED); // layer2
//
//                Xml.Layer otherLayerPort = lx;
//
//                for (int i = 2; i < c.layers.size(); i++) // rest of layers. Either select or well.
//                {
//                    ContactNode node = c.layers.get(i);
//                    Xml.Layer lz = t.findLayer(node.layer);
//
//                    if ((lz == pwellLayer && lx == diffLayers[0]) ||
//                        (lz == nwellLayer && lx == diffLayers[1])) // well contact
//                    {
//                        otherLayerPort = lz;
//                        if (lz == pwellLayer)
//                        {
//                            g = wellPalette[0];
//                            func = getWellContactFunction(Technology.P_TYPE);
//                            extraName = "PW"; // W for well
//                        }
//                        else // nwell
//                        {
//                            g = wellPalette[1];
//                            func = getWellContactFunction(Technology.N_TYPE);
//                            extraName = "NW"; // W for well
//                        }
//                    }
//                    if (pSubstrateProcess && lz == pwellLayer)
//                        continue; // skip this layer
//
//                    double h3x = scaledValue(contact_size.value /2 + node.valueX.value);
//                    double h3y = scaledValue(contact_size.value /2 + node.valueY.value);
//                    nodes[count++] = makeXmlNodeLayer(h3x, h3x, h3y, h3y, lz, Poly.Type.FILLED);
//
//                    // This assumes no well is defined
//                    double longXLocal = (Math.abs(node.valueX.value - otherLayer.valueX.value));
//                    double longYLocal = (Math.abs(node.valueY.value - otherLayer.valueY.value));
//                    if (DBMath.isGreaterThan(longXLocal, longX))
//                        longX = longXLocal;
//                    if (DBMath.isGreaterThan(longYLocal, longY))
//                        longY = longYLocal;
//                }
//                longX = scaledValue(longX);
//                longY = scaledValue(longY);
//
//                // prt names now after determing wheter is a diff or well contact
//                portNames.clear();
////                if (!pSubstrateProcess || otherLayerPort == pwellLayer)
//                    portNames.add(otherLayerPort.name);
//                portNames.add(ly.name); // always should represent the metal1
//                name = ly.name + "-" + otherLayerPort.name;
//
//
//                // some primitives might not have prefix. "-" should not be in the prefix to avoid
//                // being displayed in the palette
//                String p = (c.prefix == null || c.prefix.equals("")) ? "" : c.prefix + "-";
//                g.addElement(makeXmlPrimitiveCon(t.nodeGroups, p + name, func, -1, -1,
//                    new SizeOffset(longX, longX, longY, longY), portNames,
//                    nodes), p + extraName); // contact
//            }
        }

        // ndiff/pdiff contact
        for (int i = 0; i < 2; i++)
        {
            portNames.clear();
            portNames.add(diffLayers[i].name);
            portNames.add(m1Layer.name);
            String composeName = diffNames[i] + "-" + diff_layer.name; //Diff";
            Xml.NodeLayer wellNode, wellNodePin;
            ArcProto.Function arcF;
            Xml.ArcLayer arcL;
            WizardField arcVal;

            if (i == Technology.P_TYPE)
            {
                wellNodePin = makeXmlNodeLayer(nwell, nwell, nwell, nwell, nwellLayer, Poly.Type.CROSSED);
                wellNode = makeXmlNodeLayer(nwell, nwell, nwell, nwell, nwellLayer, Poly.Type.FILLED);
                arcF = ArcProto.Function.DIFFP;
                arcL = makeXmlArcLayer(nwellLayer, diff_width, nwell_overhang_diff_p);
                arcVal = pplus_overhang_diff;
            }
            else
            {
                wellNodePin = (!pSubstrateProcess)?makeXmlNodeLayer(nwell, nwell, nwell, nwell, pwellLayer, Poly.Type.CROSSED):null;
                wellNode = (!pSubstrateProcess)?makeXmlNodeLayer(nwell, nwell, nwell, nwell, pwellLayer, Poly.Type.FILLED):null;
                arcF = ArcProto.Function.DIFFN;
                arcL = (!pSubstrateProcess)?makeXmlArcLayer(pwellLayer, diff_width, nwell_overhang_diff_p):null;
                arcVal = nplus_overhang_diff;
            }

            PaletteGroup diffG = diffPalette[i];

            // active arc
            diffG.addArc(makeXmlArc(t, composeName, arcF, 0,
                makeXmlArcLayer(diffLayers[i], diff_width),
                makeXmlArcLayer(plusLayers[i], diff_width, arcVal),
                arcL));

            // active pin
            diffG.addPinOrResistor(makeXmlPrimitivePin(t, composeName, hla,
                new SizeOffset(sos[i], sos[i], sos[i], sos[i]), null,
                makeXmlNodeLayer(hla, hla, hla, hla, diffLayers[i], Poly.Type.CROSSED),
                makeXmlNodeLayer(sels[i], sels[i], sels[i], sels[i], plusLayers[i], Poly.Type.CROSSED),
                wellNodePin), null);

            // F stands for full (all layers)
            diffG.addElement(makeXmlPrimitiveCon(t.nodeGroups, "F-"+composeName, PrimitiveNode.Function.CONTACT,
                hla, hla, new SizeOffset(sos[i], sos[i], sos[i], sos[i]), portNames,
                makeXmlNodeLayer(metal1Over, metal1Over, metal1Over, metal1Over, m1Layer, Poly.Type.FILLED), // meta1 layer
                makeXmlNodeLayer(hla, hla, hla, hla, diffLayers[i], Poly.Type.FILLED), // active layer
                makeXmlNodeLayer(sels[i], sels[i], sels[i], sels[i], plusLayers[i], Poly.Type.FILLED), // select layer
                wellNode, // well layer
                makeXmlMulticut(diffConLayer, contSize, contSpacing, contArraySpacing)), "Full-" + diffNames[i]); // contact
        }

        /**************************** N/P-Well Contacts ***********************************************/
        nwell = scaledValue(contact_size.value /2 + diff_contact_overhang.value + nwell_overhang_diff_n.value);
        nso = scaledValue(/*diff_contact_overhang.v +*/ nwell_overhang_diff_n.value); // valid for elements that have nwell layers
        pso = (!pSubstrateProcess)?nso:scaledValue(/*diff_contact_overhang.v +*/ pplus_overhang_strap.value);
        double[] wellSos = {pso, nso};

        Xml.Layer[] wellLayers = {pwellLayer, nwellLayer};
        double nselW = scaledValue(contact_size.value /2 + diff_contact_overhang.value + nplus_overhang_strap.value);
        double pselW = scaledValue(contact_size.value /2 + diff_contact_overhang.value + pplus_overhang_strap.value);
        double[] wellSels = {pselW, nselW};

        // nwell/pwell contact
        for (int i = 0; i < 2; i++)
        {
            String composeName = diffNames[i] + "-Well";
            Xml.NodeLayer wellNodeLayer = null, wellNodePinLayer = null;
            PaletteGroup g = wellPalette[i];
            PrimitiveNode.Function func = getWellContactFunction(i);
            Xml.ArcLayer arcL;
            WizardField arcVal;

            portNames.clear();
            if (i == Technology.P_TYPE)
            {
                if (!pSubstrateProcess)
                {
                    wellNodePinLayer = makeXmlNodeLayer(nwell, nwell, nwell, nwell, pwellLayer, Poly.Type.CROSSED);
                    wellNodeLayer = makeXmlNodeLayer(nwell, nwell, nwell, nwell, pwellLayer, Poly.Type.FILLED);
                }
                portNames.add(pwellLayer.name);
                arcL = (!pSubstrateProcess)?makeXmlArcLayer(pwellLayer, diff_width, nwell_overhang_diff_p):null;
                arcVal = pplus_overhang_diff;
            }
            else
            {
                portNames.add(nwellLayer.name);
                wellNodePinLayer = makeXmlNodeLayer(nwell, nwell, nwell, nwell, nwellLayer, Poly.Type.CROSSED);
                wellNodeLayer = makeXmlNodeLayer(nwell, nwell, nwell, nwell, nwellLayer, Poly.Type.FILLED);
                arcL = makeXmlArcLayer(nwellLayer, diff_width, nwell_overhang_diff_p);
                arcVal = nplus_overhang_diff;
            }
            portNames.add(m1Layer.name);

            // three layers arcs. This is the first port defined so it will be the default in the palette
            g.addArc(makeXmlArc(t, composeName, ArcProto.Function.WELL, 0,
                    makeXmlArcLayer(diffLayers[i], diff_width),
                    makeXmlArcLayer(plusLayers[i], diff_width, arcVal),
                    arcL));

            // simple arc. S for simple
            g.addArc(makeXmlArc(t, "S-"+composeName, ArcProto.Function.WELL, 0,
                makeXmlArcLayer(wellLayers[i], diff_width, nwell_overhang_diff_p)));

            // well pin
            List<String> arcNames = new ArrayList<String>();
            arcNames.add(composeName); arcNames.add("S-"+composeName);
            g.addPinOrResistor(makeXmlPrimitivePin(t, composeName, hla,
                new SizeOffset(wellSos[i], wellSos[i], wellSos[i], wellSos[i]), arcNames,
                makeXmlNodeLayer(hla, hla, hla, hla, diffLayers[i], Poly.Type.CROSSED),
                makeXmlNodeLayer(sels[i], sels[i], sels[i], sels[i], plusLayers[i], Poly.Type.CROSSED),
                wellNodePinLayer), null);

            // well contact
            // F stands for full
            g.addElement(makeXmlPrimitiveCon(t.nodeGroups, "F-"+composeName, func,
                hla, hla, new SizeOffset(wellSos[i], wellSos[i], wellSos[i], wellSos[i]), portNames,
                makeXmlNodeLayer(metal1Over, metal1Over, metal1Over, metal1Over, m1Layer, Poly.Type.FILLED), // meta1 layer
                makeXmlNodeLayer(hla, hla, hla, hla, diffLayers[i], Poly.Type.FILLED), // active layer
                makeXmlNodeLayer(wellSels[i], wellSels[i], wellSels[i], wellSels[i], plusLayers[i], Poly.Type.FILLED), // select layer
                wellNodeLayer, // well layer
                makeXmlMulticut(diffConLayer, contSize, contSpacing, contArraySpacing)), "Full-"+diffNames[i] + "W"); // contact
        }

        /**************************** Transistors ***********************************************/
        /** Transistors **/
        // write the transistors
        List<Xml.NodeLayer> nodesList = new ArrayList<Xml.NodeLayer>();
        List<Xml.PrimitivePort> nodePorts = new ArrayList<Xml.PrimitivePort>();
        EPoint minFullSize = null; //EPoint.fromLambda(0, 0);  // default zero    horizontalFlag
        PaletteGroup[] transPalette = new PaletteGroup[2];

        for(int i = 0; i < 2; i++)
        {
            String name;
            double selecty = 0, selectx = 0;
            Xml.Layer wellLayer = null, activeLayer, selectLayer;
            double sox = 0, soy = 0;
            double impx = scaledValue((gate_width.value)/2);
            double impy = scaledValue((gate_length.value +diff_poly_overhang.value *2)/2);
            double nwell_overhangX = 0, nwell_overhangY = 0;
            PaletteGroup g = new PaletteGroup();
            transPalette[i] = g;

            double protectDist = scaledValue(poly_protection_spacing.value);
            double extraSelX = 0, extraSelY = 0;
            PrimitiveNode.Function func = null, prFunc = null, wrFunc;

            if (i==Technology.P_TYPE)
            {
				name = "P";
                nwell_overhangY = nwell_overhangX = nwell_overhang_diff_n.value;
                wellLayer = nwellLayer;
                activeLayer = diffPLayer;
                selectLayer = pplusLayer;
                extraSelX = pplus_overhang_poly.value;
                extraSelY = pplus_overhang_diff.value;
                func = PrimitiveNode.Function.TRAPMOS;
                prFunc = PrimitiveNode.Function.RESPPOLY;
                wrFunc = PrimitiveNode.Function.RESPWELL;
            }
            else
            {
				name = "N";
                activeLayer = diffNLayer;
                selectLayer = nplusLayer;
                extraSelX = nplus_overhang_poly.value;
                extraSelY = nplus_overhang_diff.value;
                func = PrimitiveNode.Function.TRANMOS;
                prFunc = PrimitiveNode.Function.RESNPOLY;
                wrFunc = PrimitiveNode.Function.RESNWELL;
                if (!pSubstrateProcess)
                {
                    nwell_overhangY = nwell_overhangX = nwell_overhang_diff_p.value;
                    wellLayer = pwellLayer;
                }
                else
                {
                    nwell_overhangX = poly_endcap.value +extraSelX;
                    nwell_overhangY = extraSelY;
                }
            }

            selectx = scaledValue(gate_width.value /2+poly_endcap.value +extraSelX);
            selecty = scaledValue(gate_length.value /2+diff_poly_overhang.value +extraSelY);

            // Using P values in transistors
            double wellx = scaledValue((gate_width.value /2+nwell_overhangX));
            double welly = scaledValue((gate_length.value /2+diff_poly_overhang.value +nwell_overhangY));

            sox = scaledValue(nwell_overhangX);
            soy = scaledValue(diff_poly_overhang.value +nwell_overhangY);

            if (DBMath.isLessThan(wellx, selectx))
            {
                sox = scaledValue(poly_endcap.value +extraSelX);
                wellx = selectx;
            }
            if (DBMath.isLessThan(welly, selecty))
            {
                soy = scaledValue(diff_poly_overhang.value +extraSelY);
                welly = selecty;
            }

            nodesList.clear();
            nodePorts.clear();
            portNames.clear();

            // Gate layer Electrical
            double gatey = scaledValue(gate_length.value /2);
            double gatex = impx;
            // Poly layers
            // left electrical
            double endPolyx = scaledValue((gate_width.value +poly_endcap.value *2)/2);
            double endPolyy = gatey;
            double endLeftOrRight = -impx;   // for horizontal transistors. Default
            double endTopOrBotton = endPolyy; // for horizontal transistors. Default
            double diffX = 0, diffY = scaledValue(gate_length.value /2+gate_contact_spacing.value +contact_size.value /2);  // impy
            double xSign = 1, ySign = -1;
            double polyX = endPolyx, polyY = 0;

            if (!horizontalFlag) // swap the numbers to get vertical transistors
            {
                double tmp;
                tmp = impx; impx = impy; impy = tmp;
                tmp = wellx; wellx = welly; welly = tmp;
                tmp = sox; sox = soy; soy = tmp;
                tmp = selectx; selectx = selecty; selecty = tmp;
                tmp = gatex; gatex = gatey; gatey = tmp;
                tmp = endPolyx; endPolyx = endPolyy; endPolyy = tmp;
                tmp = diffX; diffX = diffY; diffY = tmp;
                tmp = polyX; polyX = polyY; polyY = tmp;
                tmp = xSign; xSign = ySign; ySign = tmp;
                endLeftOrRight = endPolyx;
                endTopOrBotton = -impx;
            }

            // Well layer
            Xml.NodeLayer xTranWellLayer = null;
            if (wellLayer != null)
            {
                xTranWellLayer = (makeXmlNodeLayer(wellx, wellx, welly, welly, wellLayer, Poly.Type.FILLED));
                nodesList.add(xTranWellLayer);
            }

            // Active layers
            nodesList.add(makeXmlNodeLayer(impx, impx, impy, impy, activeLayer, Poly.Type.FILLED, true, false, -1));
            // electrical active layers
            nodesList.add(makeXmlNodeLayer(impx, impx, impy, 0, activeLayer, Poly.Type.FILLED, false, true, 3));  // bottom
            nodesList.add(makeXmlNodeLayer(impx, impx, 0, impy, activeLayer, Poly.Type.FILLED, false, true, 1));  // top

            // Diff port
            portNames.clear();
            portNames.add(activeLayer.name);

            Xml.PrimitivePort diffTopPort = makeXmlPrimitivePort("diff-top", 90, 90, 1, minFullSize,
                diffX, -1, diffX, 1, diffY, 1, diffY, 1, portNames);
            // bottom port
            Xml.PrimitivePort diffBottomPort = makeXmlPrimitivePort("diff-bottom", 270, 90, 2, minFullSize,
                xSign*diffX, -1, xSign*diffX, 1, ySign*diffY, -1, ySign*diffY, -1, portNames);

            // Electric layers
            // Gate layer Electrical
            nodesList.add(makeXmlNodeLayer(gatex, gatex, gatey, gatey, polyGateLayer, Poly.Type.FILLED, false, true, -1));

            // Poly layers
            // left electrical
            nodesList.add(makeXmlNodeLayer(endPolyx, endLeftOrRight, endPolyy, endTopOrBotton, polyLayer,
                Poly.Type.FILLED, false, true, 0));
            // right electrical
            nodesList.add(makeXmlNodeLayer(endLeftOrRight, endPolyx, endTopOrBotton, endPolyy, polyLayer,
                Poly.Type.FILLED, false, true, 2));

            // non-electrical poly (just one poly layer)
            nodesList.add(makeXmlNodeLayer(endPolyx, endPolyx, endPolyy, endPolyy, polyLayer, Poly.Type.FILLED, true, false, -1));

            // Poly port
            portNames.clear();
            portNames.add(polyLayer.name);
            Xml.PrimitivePort polyLeftPort = makeXmlPrimitivePort("poly-left", 180, 90, 0, minFullSize,
                ySign*polyX, -1, ySign*polyX,
                -1, xSign*polyY, -1, xSign*polyY, 1, portNames);
            // right port
            Xml.PrimitivePort polyRightPort = makeXmlPrimitivePort("poly-right", 0, 180, 0, minFullSize,
                polyX, 1, polyX, 1, polyY, -1, polyY, 1, portNames);

            // Select layer
            Xml.NodeLayer xTranSelLayer = (makeXmlNodeLayer(selectx, selectx, selecty, selecty, selectLayer, Poly.Type.FILLED));
            nodesList.add(xTranSelLayer);

            //One (undocumented) requirement of transistors is that the ports must appear in the
            //order: Poly-left, Diff-top, Poly-right, Diff-bottom.  This requirement is
            //because of the methods Technology.getTransistorGatePort(),
            //Technology.getTransistorAltGatePort(), Technology.getTransistorSourcePort(),
            //and Technology.getTransistorDrainPort().
            // diff-top = 1, diff-bottom = 2, polys=0
            // ports in the correct order: Poly-left, Diff-top, Poly-right, Diff-bottom
            nodePorts.add(polyLeftPort);
            nodePorts.add(diffTopPort);
            nodePorts.add(polyRightPort);
            nodePorts.add(diffBottomPort);

            // Standard Transistor
            Xml.PrimitiveNodeGroup n = makeXmlPrimitive(t.nodeGroups, name + "-Transistor", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
            g.addElement(n, name);

            // Extra transistors which don't have select nor well
            // Extra protection poly. No ports are necessary.
            if (isComplexCase())
            {
                /*************************************/
                // Short transistors
                // Adding extra transistors whose select and well are aligned with poly along the X axis
                nodesList.remove(xTranSelLayer);
                double shortSelectX = scaledValue(gate_width.value /2+poly_endcap.value);
                xTranSelLayer = (makeXmlNodeLayer(shortSelectX, shortSelectX, selecty, selecty, selectLayer, Poly.Type.FILLED));
                nodesList.add(xTranSelLayer);
                double shortSox = sox;

                shortSox = scaledValue(poly_endcap.value);
                if (wellLayer != null)
                {
                    nodesList.remove(xTranWellLayer);
                    xTranWellLayer = (makeXmlNodeLayer(shortSelectX, shortSelectX, welly, welly, wellLayer, Poly.Type.FILLED));
                    nodesList.add(xTranWellLayer);
                }
                n = makeXmlPrimitive(t.nodeGroups, name + "-Transistor-S", func, 0, 0, 0, 0,
                     new SizeOffset(shortSox, shortSox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, name + "-S");

                /*************************************/
                // Short transistors with VTH and VTL

                double vthlx = scaledValue(gate_width.value /2+vthl_diff_overhang.value);
                double vthly = scaledValue(gate_length.value /2+ vthl_poly_overhang.value);

                // VTH Transistor
                String tmp = "VTH-" + name;
                Xml.NodeLayer nl = addXmlNodeLayer(nodesList, t, tmp, vthlx, vthly);

                n = makeXmlPrimitive(t.nodeGroups, tmp + "-Transistor-S", func, 0, 0, 0, 0,
                     new SizeOffset(shortSox, shortSox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, tmp + "-S");

                // VTL Transistor
                nodesList.remove(nl);
                tmp = "VTL-" + name;
                nl = addXmlNodeLayer(nodesList, t, tmp, vthlx, vthly);

                n = makeXmlPrimitive(t.nodeGroups, tmp + "-Transistor-S", func, 0, 0, 0, 0,
                     new SizeOffset(shortSox, shortSox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, tmp + "-S");

                /*************************************/
                // Transistors with extra polys

                // different select for those with extra protection layers
                nodesList.remove(xTranSelLayer);
                double endOfProtectionY = gate_length.value + poly_protection_spacing.value;
                double selectExtraY = scaledValue(gate_length.value /2 + endOfProtectionY + extraSelX); // actually is extraSelX because of the poly distance!
                xTranSelLayer = (makeXmlNodeLayer(selectx, selectx, selectExtraY, selectExtraY, selectLayer, Poly.Type.FILLED));
                nodesList.add(xTranSelLayer);

                // not sure which condition to apply. It doesn't apply  nwell_overhang_diff due to the extra poly
                if (DBMath.isLessThan(welly, selectExtraY))
                {
                    welly = selectExtraY;
                    soy = scaledValue(endOfProtectionY + extraSelX);
                }
                if (wellLayer != null)
                {
                    nodesList.remove(xTranWellLayer);
                    xTranWellLayer = (makeXmlNodeLayer(wellx, wellx, welly, welly, wellLayer, Poly.Type.FILLED));
                    nodesList.add(xTranWellLayer);
                }
                if (!horizontalFlag)
                {
                    System.out.println("Not working with !horizontal");
                    assert(false);
                }
                portNames.clear();
                portNames.add(polyLayer.name);

                // bottom or left
                Xml.NodeLayer bOrL = (makeXmlNodeLayer(gatex, gatex,
                    DBMath.round((protectDist + 3*endPolyy)),
                    -DBMath.round(endPolyy + protectDist),
                   polyLayer, Poly.Type.FILLED, true, false, -1/*3*/)); // port 3 for left/bottom extra poly lb=left bottom
                // Adding left
                nodesList.add(bOrL);
                n = makeXmlPrimitive(t.nodeGroups, name + "-Transistor-B", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, name + "-B");

                // top or right
                Xml.NodeLayer tOrR = (makeXmlNodeLayer(gatex, gatex,
                    -DBMath.round(endPolyy + protectDist),
                    DBMath.round((protectDist + 3*endPolyy)),
                    polyLayer, Poly.Type.FILLED, true, false, -1/*4*/)); // port 4 for right/top extra poly rt=right top

                // Adding both
                nodesList.add(tOrR);
                n = makeXmlPrimitive(t.nodeGroups, name + "-Transistor-TB", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, name + "-TB");

                // Adding right
                nodesList.remove(bOrL);
                n = makeXmlPrimitive(t.nodeGroups, name + "-Transistor-T", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, name +"-T");

                /*************************************/
                // Short transistors woth OD18

                double od18x = scaledValue(gate_od18_width.value /2+od18_diff_overhang[0].value);
                double od18y = scaledValue(gate_od18_length.value /2+diff_poly_overhang.value +od18_diff_overhang[1].value);

                nodePorts.clear();
                nodesList.clear();
                prepareTransistor(gate_od18_width.value, gate_od18_length.value, poly_endcap.value, diff_poly_overhang.value,
                    gate_contact_spacing.value, contact_size.value, activeLayer, polyLayer, polyGateLayer, nodesList, nodePorts);

                // OD18
                addXmlNodeLayer(nodesList, t, "OD_18", od18x, od18y);

                // adding short select
                shortSelectX = scaledValue(gate_od18_width.value /2+poly_endcap.value);
                selecty = scaledValue(gate_od18_length.value /2+diff_poly_overhang.value +extraSelY);
                xTranSelLayer = (makeXmlNodeLayer(shortSelectX, shortSelectX, selecty, selecty, selectLayer, Poly.Type.FILLED));
                nodesList.add(xTranSelLayer);

                // adding well
                if (wellLayer != null)
                {
                    xTranWellLayer = (makeXmlNodeLayer(od18x, od18x, od18y, od18y, wellLayer, Poly.Type.FILLED));
                    nodesList.add(xTranWellLayer);
                }

                sox = scaledValue(od18_diff_overhang[0].value);
                soy = scaledValue(diff_poly_overhang.value +od18_diff_overhang[1].value);
                n = makeXmlPrimitive(t.nodeGroups, "OD18-" + name + "-Transistor-S", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, "18-" + name + "-S");

                /*************************************/
                // Short transistors with OD18ud15
                WizardField gateOD18ud1Len = findWizardField("gate_od18ud15_length");

                nodePorts.clear();
                nodesList.clear();
                prepareTransistor(gate_od18_width.value, gateOD18ud1Len.value, poly_endcap.value, diff_poly_overhang.value,
                    gate_contact_spacing.value, contact_size.value, activeLayer, polyLayer, polyGateLayer, nodesList, nodePorts);

                // OD18. od18y is corrected to match gateOD18ud1Len
                od18y = scaledValue(gateOD18ud1Len.value /2+diff_poly_overhang.value +od18_diff_overhang[1].value);
                addXmlNodeLayer(nodesList, t, "OD_18", od18x, od18y);
                // OD18ud15. Not the same as od18 anymore to be more flexible.
                WizardRuleFields od18ud15DifOverhang = findWizardRuleFields("od18u15_diff_overhang");
                double od18du15x = scaledValue(gate_od18_width.value /2+od18ud15DifOverhang.xValue.value);
                double od18du15y = scaledValue(gateOD18ud1Len.value /2+diff_poly_overhang.value +od18ud15DifOverhang.yValue.value);

                addXmlNodeLayer(nodesList, t, "OD_18ud15", od18du15x, od18du15y);

                // adding short select
                shortSelectX = scaledValue(gate_od18_width.value /2+poly_endcap.value);
                selecty = scaledValue(gateOD18ud1Len.value /2+diff_poly_overhang.value +extraSelY);
                xTranSelLayer = (makeXmlNodeLayer(shortSelectX, shortSelectX, selecty, selecty, selectLayer, Poly.Type.FILLED));
                nodesList.add(xTranSelLayer);

                // Assuming od18 is bigger than od18ud15
                double wellX = od18x;
                double wellY = od18y;
                double biggerX = od18_diff_overhang[0].value;
                double biggerY = od18_diff_overhang[1].value;

                // adjusting well and so values if od18ud15 is bigger
                if (DBMath.isGreaterThan(od18ud15DifOverhang.xValue.value, od18_diff_overhang[0].value))
                {
                    wellX = od18du15x;
                    biggerX = od18ud15DifOverhang.xValue.value;
                }
                if (DBMath.isGreaterThan(od18ud15DifOverhang.yValue.value, od18_diff_overhang[1].value))
                {
                    wellY = od18du15y;
                    biggerY = od18ud15DifOverhang.yValue.value;
                }

                // adding well
                if (wellLayer != null)
                {
                    xTranWellLayer = (makeXmlNodeLayer(wellX, wellX, wellY, wellY, wellLayer, Poly.Type.FILLED));
                    nodesList.add(xTranWellLayer);
                }
                sox = scaledValue(biggerX);
                soy = scaledValue(diff_poly_overhang.value + biggerY);
                n = makeXmlPrimitive(t.nodeGroups, "OD18ud15-" + name + "-Transistor-S", func, 0, 0, 0, 0,
                new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addElement(n, "18ud15-" + name + "-S");

                /*************************************/
                // Short transistors with native

                if (i==Technology.N_TYPE)
                {
                    double ntx = scaledValue(gate_nt_width.value /2+nt_diff_overhang.value);
                    double nty = scaledValue(gate_nt_length.value /2+diff_poly_overhang.value +nt_diff_overhang.value);

                    nodePorts.clear();
                    nodesList.clear();
                    prepareTransistor(gate_nt_width.value, gate_nt_length.value, poly_nt_endcap.value, diff_poly_overhang.value,
                        gate_contact_spacing.value, contact_size.value, activeLayer, polyLayer, polyGateLayer, nodesList, nodePorts);

                    // NT-N
                    addXmlNodeLayer(nodesList, t, "NT-N", ntx, nty);

                    // adding short select
                    shortSelectX = scaledValue(gate_nt_width.value /2+poly_nt_endcap.value);
                    selecty = scaledValue(gate_nt_length.value /2+diff_poly_overhang.value +extraSelY);
                    xTranSelLayer = (makeXmlNodeLayer(shortSelectX, shortSelectX, selecty, selecty, selectLayer, Poly.Type.FILLED));
                    nodesList.add(xTranSelLayer);

                    // adding well
                    if (wellLayer != null)
                    {
                        xTranWellLayer = (makeXmlNodeLayer(ntx, ntx, nty, nty, wellLayer, Poly.Type.FILLED));
                        nodesList.add(xTranWellLayer);
                    }

                    sox = scaledValue(poly_nt_endcap.value);
                    soy = scaledValue(diff_poly_overhang.value +nt_diff_overhang.value);
                    n = makeXmlPrimitive(t.nodeGroups, "NT-" + name + "-Transistor-S", func, 0, 0, 0, 0,
                        new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                    g.addElement(n, "NT-" + name + "-S");
                }

                /*************************************/
                // Poly Resistors
                nodesList.clear();
                nodePorts.clear();
                WizardField polyRL = findWizardField("poly_resistor_length");
                WizardField polyRW = findWizardField("poly_resistor_width");
                WizardField rpoS = findWizardField("rpo_contact_spacing");
                WizardField rpoODPolyEx = findWizardField("rpo_odpoly_overhang");
                WizardField rhOverhang = findWizardField("rh_odpoly_overhang");

                double resistorSpacing = contact_array_spacing.value; // using array value to guarantee proper spacing in nD cases

                // poly
                double soxNoScaled = (rpoS.value + contact_poly_overhang.value + resistorSpacing + 2 * contact_size.value);
                double halfTotalL = scaledValue(polyRL.value /2 + soxNoScaled);
                double halfTotalW = scaledValue(polyRW.value /2);
                nodesList.add(makeXmlNodeLayer(halfTotalL, halfTotalL, halfTotalW, halfTotalW, polyLayer,
                    Poly.Type.FILLED, true, true, -1));

                // RPO
                double rpoY = scaledValue(polyRW.value /2 + rpoODPolyEx.value);
                double rpoX = scaledValue(polyRL.value /2);
                Xml.Layer rpoLayer = t.findLayer("RPO");
                addXmlNodeLayerInternal(nodesList, t, "RPO", rpoX, rpoY, true, true, -1);

                // left cuts
                double cutDistance = scaledValue(rpoS.value + polyRL.value /2);
                // M1 and Poly overhang will be the same for now
//                double absVal = (contact_poly_overhang.v - via_overhang[0].v);
                double m1Distance = cutDistance - scaledValue(contact_poly_overhang.value);
                double m1Y = scaledValue(polyRW.value /2); // - absVal);
                double m1W = scaledValue(2 * contact_poly_overhang.value + resistorSpacing + 2 * contact_size.value);
                double cutSizeHalf = scaledValue(contact_size.value /2);
                double cutEnd = cutDistance+contSize;
                double cutSpacing = scaledValue(resistorSpacing);
                double cutEnd2 = cutEnd+contSize+cutSpacing;

                portNames.clear();
                portNames.add(m1Layer.name);
                // left port
                Xml.PrimitivePort port = makeXmlPrimitivePort("left-rpo", 0, 180, 0, minFullSize,
                    -(cutEnd + cutSpacing), -1, -cutEnd, -1, -cutSizeHalf, -1, cutSizeHalf, 1, portNames);
                nodePorts.add(port);
                // right port
                port = makeXmlPrimitivePort("right-rpo", 0, 180, 1, minFullSize,
                    cutEnd, 1, (cutEnd + cutSpacing), 1, -cutSizeHalf, -1, cutSizeHalf, 1, portNames);
                nodePorts.add(port);

                // metal left
                nodesList.add(makeXmlNodeLayer((m1Distance + m1W), -1, -m1Distance, -1, m1Y, -1, m1Y, 1, m1Layer,
                    Poly.Type.FILLED, true, true, 0));
                // right metal
                nodesList.add(makeXmlNodeLayer(-m1Distance, 1, (m1Distance + m1W), 1, m1Y, -1, m1Y, 1, m1Layer,
                    Poly.Type.FILLED, true, true, 1));

                // select
                double selectY = scaledValue(polyRW.value /2 + rhOverhang.value);
                double selectX = scaledValue(polyRL.value /2 + soxNoScaled + extraSelX);
                nodesList.add(makeXmlNodeLayer(selectX, selectX, selectY, selectY, selectLayer,
                    Poly.Type.FILLED, true, true, -1));
                // RH
                addXmlNodeLayerInternal(nodesList, t, "RH", selectX, selectY, true, true, -1);

                // RPDMY
                addXmlNodeLayerInternal(nodesList, t, "RPDMY", selectX, selectY, true, true, -1);

                // cuts
                nodesList.add(makeXmlMulticut(cutEnd2, -1, -cutDistance, -1, cutSizeHalf, -1, cutSizeHalf, 1,
                    polyConLayer, contSize, contArraySpacing, contArraySpacing));
                nodesList.add(makeXmlMulticut(-cutDistance, 1, cutEnd2, 1, cutSizeHalf, -1, cutSizeHalf, 1,
                    polyConLayer, contSize, contArraySpacing, contArraySpacing));

                sox = scaledValue(soxNoScaled + extraSelX);
                soy = scaledValue(rpoODPolyEx.value);
                n = makeXmlPrimitive(t.nodeGroups, name + "-Poly-RPO-Resistor", prFunc, 0, 0, 0, 0,
                    new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addPinOrResistor(n, name + "-RPoly");

                /*************************************/
                // Well Resistors
                nodesList.clear();
                nodePorts.clear();
                WizardField wellRL = findWizardField("well_resistor_length");
                WizardField wellRW = findWizardField("well_resistor_width");
                WizardField rpoSelO = findWizardField("rpo_select_overlap"); // F
                WizardField rpoCoS = findWizardField("rpo_co_space_in_nwrod"); // G
                WizardField coNwrodO = findWizardField("co_nwrod_overhang"); // E
                WizardField odNwrodO = findWizardField("od_nwrod_overhang"); // D
                WizardField rpoNwrodS = findWizardField("rpo_nwrod_space"); // c

                // Total values define RPO dimensions
                double cutEndNoScaled = /*F*/rpoSelO.value + /*G*/rpoCoS.value;
                double cutSpacingNoScaled = /*2xCut + spacing*/resistorSpacing + 2*contact_size.value;
                double wellFromnwdmyWidth = /*F+G*/cutEndNoScaled + /*cut spacing+2xcuts*/cutSpacingNoScaled
                    + /*E*/coNwrodO.value;
                double activeXNoScaled = wellFromnwdmyWidth + /*D*/odNwrodO.value;
                soxNoScaled = activeXNoScaled + rpoODPolyEx.value;
                double soyNoScaled = /*D*/odNwrodO.value + rpoODPolyEx.value;
                halfTotalL = scaledValue(wellRL.value /2 + soxNoScaled);
                halfTotalW = scaledValue(wellRW.value /2 + soyNoScaled);
                double activeWX = scaledValue(wellRL.value /2 + activeXNoScaled);
                double activeWY = scaledValue(wellRW.value /2 + /*D*/odNwrodO.value);

                // active
                nodesList.add(makeXmlNodeLayer(activeWX, activeWX, activeWY, activeWY, activeLayer,
                    Poly.Type.FILLED, true, true, -1));

                // well
                double halfW = scaledValue(wellRW.value /2);
                double halfWellLNoScaled = wellRL.value /2 + wellFromnwdmyWidth;
                double halfWellL = scaledValue(halfWellLNoScaled);
                if (i==Technology.N_TYPE)
                {
                    nodesList.add(makeXmlNodeLayer(halfWellL, halfWellL, halfW, halfW, nwellLayer,
                        Poly.Type.FILLED, true, true, -1));
                }

                // NWDMY-LVS
                double halfL = scaledValue(wellRL.value /2);
                addXmlNodeLayerInternal(nodesList, t, "NWDMY-LVS", halfL, halfTotalW, true, true, -1);

                cutEnd = scaledValue(wellRL.value /2+cutEndNoScaled);
                cutSpacing = scaledValue(cutSpacingNoScaled);

                // Metal1
                m1Distance = scaledValue(wellRL.value /2 + /*F*/rpoSelO.value);
                // metal left
                nodesList.add(makeXmlNodeLayer(halfWellL, -1, -m1Distance, -1, halfW, -1, halfW, 1, m1Layer,
                    Poly.Type.FILLED, true, true, 0));
                // right metal
                nodesList.add(makeXmlNodeLayer(-m1Distance, 1, halfWellL, 1, halfW, -1, halfW, 1, m1Layer,
                    Poly.Type.FILLED, true, true, 1));

                // Select
                double deltaFromActve = /*DodNwrodO.value - */ /*C*/rpoNwrodS.value + /*F*/rpoSelO.value;
                selectY = scaledValue(wellRW.value /2 + deltaFromActve); // Y end of well + F + C value
                selectX = scaledValue(halfWellLNoScaled + deltaFromActve); // X end of well + F + CO value
                // Left
                nodesList.add(makeXmlNodeLayer(selectX, -1, -halfL, -1, selectY, -1, selectY, 1, selectLayer,
                    Poly.Type.FILLED, true, true, 0));
                // right
                nodesList.add(makeXmlNodeLayer(-halfL, -1, selectX, 1, selectY, -1, selectY, 1, selectLayer,
                    Poly.Type.FILLED, true, true, 0));

                // m1 left port
                port = makeXmlPrimitivePort("left-rpo", 0, 180, 0, minFullSize,
                    -(cutEnd + cutSpacing), -1, -cutEnd, -1, -halfW, -1, halfW, 1, portNames);
                nodePorts.add(port);
                // right port
                port = makeXmlPrimitivePort("right-rpo", 0, 180, 1, minFullSize,
                    cutEnd, 1, (cutEnd + cutSpacing), 1, -halfW, -1, halfW, 1, portNames);
                nodePorts.add(port);

                // RPO in 5 pieces to represent the two holes for the contacts
                double holeStartX = scaledValue(halfWellLNoScaled + /*C*/rpoNwrodS.value);
                double holeStartY = scaledValue(wellRW.value /2 + /*C*/rpoNwrodS.value);
                if (rpoLayer != null)
                {
                    // left piece
                    nodesList.add(makeXmlNodeLayer(halfTotalL, -1, -holeStartX, -1, halfTotalW, -1, halfTotalW, 1,
                        rpoLayer, Poly.Type.FILLED, true, true, -1));
                    // right piece
                    nodesList.add(makeXmlNodeLayer(-holeStartX, -1, halfTotalL, -1, halfTotalW, -1, halfTotalW, 1,
                        rpoLayer, Poly.Type.FILLED, true, true, -1));
                    // center bottom
                   nodesList.add(makeXmlNodeLayer(holeStartX, -1, holeStartX, 1, halfTotalW, -1, -holeStartY, -1,
                       rpoLayer, Poly.Type.FILLED, true, true, -1));
                    // center top
                    nodesList.add(makeXmlNodeLayer(holeStartX, -1, holeStartX, 1, -holeStartY, -1, halfTotalW, 1,
                       rpoLayer, Poly.Type.FILLED, true, true, -1));
                    // center
                    nodesList.add(makeXmlNodeLayer(m1Distance, m1Distance, holeStartY, holeStartY, rpoLayer,
                        Poly.Type.FILLED, true, true, -1));
                }
                else
                {
                    System.out.println("Error: layer rpo doesn't exist");
                }

                // Cuts
                cutEnd2 = cutEnd+cutSpacing;
                double cutEndY = scaledValue(wellRW.value /2 - coNwrodO.value); // E should also be applied along Y
                // left
                nodesList.add(makeXmlMulticut(cutEnd2, -1, -cutEnd, -1, cutEndY, -1, cutEndY, 1,
                    diffConLayer, contSize, contArraySpacing, contArraySpacing));
                // right
                nodesList.add(makeXmlMulticut(-cutEnd, 1, cutEnd2, 1, cutEndY, -1, cutEndY, 1,
                    diffConLayer, contSize, contArraySpacing, contArraySpacing));

                sox = scaledValue(soxNoScaled);
                soy = scaledValue(soyNoScaled);
                n = makeXmlPrimitive(t.nodeGroups, name + "-Well-RPO-Resistor", wrFunc, 0, 0, 0, 0,
                    new SizeOffset(sox, sox, soy, soy), nodesList, nodePorts, null, false);
                g.addPinOrResistor(n, name + "-RWell");
            }
        }

        /*** Palette Elements ***/
        allGroups.add(transPalette[0]); allGroups.add(transPalette[1]);
        allGroups.add(diffPalette[0]); allGroups.add(diffPalette[1]);
        allGroups.add(wellPalette[0]); allGroups.add(wellPalette[1]);
        allGroups.addAll(polysGroup);

        /*** GDS Values ***/
        makeLayerGDS(t, diffPLayer, String.valueOf(diff_layer));
        makeLayerGDS(t, diffNLayer, String.valueOf(diff_layer));
        makeLayerGDS(t, pplusLayer, String.valueOf(pplus_layer));
        makeLayerGDS(t, nplusLayer, String.valueOf(nplus_layer));
        makeLayerGDS(t, nwellLayer, String.valueOf(nwell_layer));
        makeLayerGDS(t, polyConLayer, String.valueOf(contact_layer));
        makeLayerGDS(t, diffConLayer, String.valueOf(contact_layer));
        makeLayerGDS(t, polyLayer, String.valueOf(poly_layer));
        makeLayerGDS(t, polyGateLayer, String.valueOf(poly_layer));

        /*** Layer Rules ***/
        for (Xml.Layer l : diffLayers)
        {
            makeLayerRuleMinWid(t, l, diff_width);
            makeLayersRule(t, l, DRCTemplate.DRCRuleType.SPACING, diff_spacing.rule, diff_spacing.value);
        }

        WizardField[] plus_diff = {pplus_overhang_diff, nplus_overhang_diff};
        WizardField[] plus_width = {pplus_width, nplus_width};
        WizardField[] plus_spacing = {pplus_spacing, nplus_spacing};

        for (int i = 0; i < plusLayers.length; i++)
        {
            makeLayerRuleMinWid(t, plusLayers[i], plus_width[i]);
            makeLayersRuleSurround(t, plusLayers[i], diffLayers[i], plus_diff[i].rule, plus_diff[i].value);
            makeLayersRule(t, plusLayers[i], DRCTemplate.DRCRuleType.SPACING, plus_spacing[i].rule, plus_spacing[i].value);
        }

        Xml.Layer[] wells = {pwellLayer, nwellLayer};

        for (Xml.Layer w : wells)
        {
            makeLayerRuleMinWid(t, w, nwell_width);
            makeLayersRuleSurround(t, w, diffPLayer, nwell_overhang_diff_p.rule, nwell_overhang_diff_p.value);
            makeLayersRuleSurround(t, w, diffNLayer, nwell_overhang_diff_n.rule, nwell_overhang_diff_n.value);
            makeLayersRule(t, w, DRCTemplate.DRCRuleType.SPACING, nwell_spacing.rule, nwell_spacing.value);
        }

        Xml.Layer[] polys = {polyLayer, polyGateLayer};
        for (Xml.Layer w : polys)
        {
            makeLayerRuleMinWid(t, w, poly_width);
            makeLayersRule(t, w, DRCTemplate.DRCRuleType.SPACING, poly_spacing.rule, poly_spacing.value);
        }

        // Gate contact spacing rules
        makeLayersRuleSpacing(t, diffConLayer, polyGateLayer, gate_contact_spacing.rule, gate_contact_spacing.value);
        makeLayersRuleSpacing(t, polyConLayer, polyGateLayer, gate_contact_spacing.rule, gate_contact_spacing.value);
    }

    private void addContactsOrCapacitors(Xml.Technology t, List<Contact> contacts, List<Xml.Layer> metalLayers,
                                         PaletteGroup[] diffPalette, PaletteGroup[] wellPalette,
                                         PaletteGroup polyGroup, boolean capacitor)
    {
        List<String> portArcNames = new ArrayList<String>(0);
        List<String> portNames = new ArrayList<String>(2);

        // Typical port names in capacitors
        portNames.add("a");
        portNames.add("b");

        // generic contacts
        String name = null;
        Xml.Layer polyLayer = t.findLayer(poly_layer.name);
        Xml.Layer polyConLayer = t.findLayer("Poly-Cut");
        Xml.Layer diffConLayer = t.findLayer(diff_layer.name+"-Cut");
        Xml.Layer nwellLayer = t.findLayer(nwell_layer.name);
        Xml.Layer pwellLayer = t.findLayer("P-Well");
        Xml.Layer diffNLayer = t.findLayer("N-"+ diff_layer.name);
        Xml.Layer diffPLayer = t.findLayer("P-"+ diff_layer.name);
        Xml.Layer[] diffLayers = {diffPLayer, diffNLayer};
        Xml.Layer pol2yLayer = t.findLayer(poly2_layer.name);
        double contSize = scaledValue(contact_size.value);
        double contSpacing = scaledValue(contact_spacing.value);
        double contArraySpacing = scaledValue(contact_array_spacing.value);

        for (Contact c : contacts)
        {
            Xml.Layer ly = null, lx = null;
            Xml.Layer conLay = diffConLayer;
            PaletteGroup g = null;
            ContactNode metalLayer = c.layers.get(0);
            ContactNode otherLayer = c.layers.get(1);
            String extraName = "";

            if (!TextUtils.isANumber(metalLayer.layer)) // horizontal must be!
            {
                assert (TextUtils.isANumber(otherLayer.layer));
                metalLayer = c.layers.get(1);
                otherLayer = c.layers.get(0);
            }

            int m1 = Integer.valueOf(metalLayer.layer);
            ly = metalLayers.get(m1-1);
            String layerName = otherLayer.layer;
            if (layerName.equals(diffLayers[0].name))
            {
                lx = diffLayers[0];
                g = diffPalette[0];
                extraName = "P";
            }
            else if (layerName.equals(diffLayers[1].name))
            {
                lx = diffLayers[1];
                g = diffPalette[1];
                extraName = "N";
            }
            else if (layerName.equals(polyLayer.name))
            {
                lx = polyLayer;
                conLay = polyConLayer;
                g = polyGroup;
            }
            else if (getSecondPolyFlag() && layerName.equals(pol2yLayer.name))
            {
                lx = pol2yLayer;
                conLay = polyConLayer;
                g = polyGroup;
            }
            else
                assert(false); // it should not happen
            double h1x = scaledValue(contact_size.value /2 + metalLayer.valueX.value);
            double h1y = scaledValue(contact_size.value /2 + metalLayer.valueY.value);
            double h2x = scaledValue(contact_size.value /2 + otherLayer.valueX.value);
            double h2y = scaledValue(contact_size.value /2 + otherLayer.valueY.value);
            double longX = (Math.abs(metalLayer.valueX.value - otherLayer.valueX.value));
            double longY = (Math.abs(metalLayer.valueY.value - otherLayer.valueY.value));

            PrimitiveNode.Function func = (!capacitor) ? PrimitiveNode.Function.CONTACT :
                PrimitiveNode.Function.CAPAC;
            Xml.NodeLayer[] nodes = new Xml.NodeLayer[c.layers.size() + 1]; // all plus cut
            int count = 0;

            // cut
            nodes[count++] = makeXmlMulticut(conLay, contSize, contSpacing, contArraySpacing);
            // metal
            nodes[count++] = makeXmlNodeLayer(h1x, h1x, h1y, h1y, ly, Poly.Type.FILLED); // layer1
            // active or poly
            nodes[count++] = makeXmlNodeLayer(h2x, h2x, h2y, h2y, lx, Poly.Type.FILLED); // layer2

            Xml.Layer otherLayerPort = lx;

            for (int i = 2; i < c.layers.size(); i++) // rest of layers. Either select or well.
            {
                ContactNode node = c.layers.get(i);
                Xml.Layer lz = t.findLayer(node.layer);

                if ((lz == pwellLayer && lx == diffLayers[0]) ||
                    (lz == nwellLayer && lx == diffLayers[1])) // well contact
                {
                    otherLayerPort = lz;
                    if (lz == pwellLayer)
                    {
                        g = wellPalette[0];
                        func = getWellContactFunction(Technology.P_TYPE);
                        extraName = "PW"; // W for well
                    }
                    else // nwell
                    {
                        g = wellPalette[1];
                        func = getWellContactFunction(Technology.N_TYPE);
                        extraName = "NW"; // W for well
                    }
                }
                if (pSubstrateProcess && lz == pwellLayer)
                    continue; // skip this layer

                double h3x = scaledValue(contact_size.value /2 + node.valueX.value);
                double h3y = scaledValue(contact_size.value /2 + node.valueY.value);
                nodes[count++] = makeXmlNodeLayer(h3x, h3x, h3y, h3y, lz, Poly.Type.FILLED);

                // This assumes no well is defined
                double longXLocal = (Math.abs(node.valueX.value - otherLayer.valueX.value));
                double longYLocal = (Math.abs(node.valueY.value - otherLayer.valueY.value));
                if (DBMath.isGreaterThan(longXLocal, longX))
                    longX = longXLocal;
                if (DBMath.isGreaterThan(longYLocal, longY))
                    longY = longYLocal;
            }
            longX = scaledValue(longX);
            longY = scaledValue(longY);

            // arc prt names now after determing wheter is a diff or well contact
            portArcNames.clear();
//                if (!pSubstrateProcess || otherLayerPort == pwellLayer)
            portArcNames.add(otherLayerPort.name);
            portArcNames.add(ly.name); // always should represent the metal1
            name = ly.name + "-" + otherLayerPort.name;

            // some primitives might not have prefix. "-" should not be in the prefix to avoid
            // being displayed in the palette
            String p = (c.prefix == null || c.prefix.equals("")) ? "" : c.prefix + "-";
            Xml.PrimitiveNodeGroup png = (!capacitor) ?
                makeXmlPrimitiveCon(t.nodeGroups, p + name, func, -1, -1,
                    new SizeOffset(longX, longX, longY, longY), portArcNames, nodes) :    // contact
                makeXmlCapacitor(t.nodeGroups, p + name, func, -1, -1,
                    new SizeOffset(longX, longX, longY, longY), portNames, portArcNames, nodes);    // capacitor
            g.addElement(png, p + extraName);
        }
    }
    /***************************************************************************************************
     * More Flexible Contacts, no multicuts
     ***************************************************************************************************/
    private void addGenericContacts(Xml.Technology t, Map<String,List<Contact>> contacts, List<PaletteGroup> extraPaletteList)
    {
        List<String> portNames = new ArrayList<String>(0);

        for (Map.Entry<String,List<Contact>> e : contacts.entrySet())
        {
            // generic contacts
            for (Contact c : e.getValue())
            {
                // Assuming is that the last layer is the cut layer
                assert(c.layers.size() == 3);
                ContactNode aLayer = c.layers.get(0);
                ContactNode bLayer = c.layers.get(1);
                ContactNode cutLayer = c.layers.get(2);
                // Look for existing palette elemnent to place the contact in.
                PaletteGroup grp = null;
                for (ContactNode n : c.layers)
                {
                    for (LayerInfo info : extraLayers)
                    {
                        if (info.name.equals(n.layer))
                        {
                            grp = info.grp; break; // found
                        }
                        if (grp != null) break; // found
                    }
                    if (grp != null) break; // found
                }
                if (grp == null)
                {
                    grp = new PaletteGroup();
                    extraPaletteList.add(grp);
                }
                Xml.Layer la = t.findLayer(aLayer.layer);
                Xml.Layer lb = t.findLayer(bLayer.layer);
                String name = la.name + "-" + lb.name;
                double metalContSizeX = scaledValue(cutLayer.valueX.value/2);
                double metalContSizeY = scaledValue(cutLayer.valueY.value/2);
                Xml.Layer metalConLayer = t.findLayer(cutLayer.layer);
                double h1x = scaledValue(cutLayer.valueX.value /2 + aLayer.valueX.value);
                double h1y = scaledValue(cutLayer.valueY.value /2 + aLayer.valueY.value);
                double h2x = scaledValue(cutLayer.valueX.value /2 + bLayer.valueX.value);
                double h2y = scaledValue(cutLayer.valueY.value /2 + bLayer.valueY.value);
//                double longX = scaledValue(Math.abs(aLayer.valueX.value - bLayer.valueX.value));
//                double longY = scaledValue(Math.abs(aLayer.valueY.value - bLayer.valueY.value));
                portNames.clear();
                // only when it is zero or positive. Negative means no layer arc
                if (bLayer.valueX.value >= 0)
                    portNames.add(lb.name);
                if (aLayer.valueX.value >= 0)
                    portNames.add(la.name);

                // some primitives might not have prefix. "-" should not be in the prefix to avoid
                // being displayed in the palette
                String p = (c.prefix == null || c.prefix.equals("")) ? "" : c.prefix + "-";
                grp.addElement(makeXmlPrimitiveCon(t.nodeGroups, p + name, PrimitiveNode.Function.CONTACT, -1, -1,
                    /*new SizeOffset(longX, longX, longY, longY)*/null,
                    portNames,
                    makeXmlNodeLayer(h1x, h1x, h1y, h1y, la, Poly.Type.FILLED), // layer1
                    makeXmlNodeLayer(h2x, h2x, h2y, h2y, lb, Poly.Type.FILLED), // layer2
                    makeXmlNodeLayer(metalContSizeX, metalContSizeX, metalContSizeY, metalContSizeY,
                        metalConLayer, Poly.Type.FILLED)), // cut
                    c.prefix); // contact
            }
        }
    }

    /***************************************************************************************************
     * PrimitiveNodeGroup Comparator
     ***************************************************************************************************/
    /**
     * A comparator object for sorting NodeGroups
     * Created once because it is used often.
     */
    private static final PrimitiveNodeGroupSort primitiveNodeGroupSort = new PrimitiveNodeGroupSort();

    /**
     * Comparator class for sorting PrimitiveNodeGroups by their name.
     */
    public static class PrimitiveNodeGroupSort implements Comparator<Xml.PrimitiveNodeGroup>
    {
        /**
         * Method to compare two PrimitiveNodeGroups by their name.
         * @param l1 one PrimitiveNodeGroup.
         * @param l2 another PrimitiveNodeGroup.
         * @return an integer indicating their sorting order.
         */
        public int compare(Xml.PrimitiveNodeGroup l1, Xml.PrimitiveNodeGroup l2)
        {
            // Sorting by first element
            Xml.PrimitiveNode n1 = l1.nodes.get(0);
            Xml.PrimitiveNode n2 = l2.nodes.get(0);
            return n1.name.compareTo(n2.name);
        }
    }

    /***************************************************************************************************
     * NodeLayer Comparator
     ***************************************************************************************************/
    /**
     * A comparator object for sorting NodeLayers
     * Created once because it is used often.
     */
    private static final NodeLayerSort nodeLayerSort = new NodeLayerSort();

    /**
     * Comparator class for sorting PrimitiveNodeGroups by their name.
     */
    public static class NodeLayerSort implements Comparator<Xml.NodeLayer>
    {
        /**
         * Method to compare two NodeLayers by their name.
         * @param l1 one NodeLayer.
         * @param l2 another NodeLayer.
         * @return an integer indicating their sorting order.
         */
        public int compare(Xml.NodeLayer l1, Xml.NodeLayer l2)
        {
            return l1.layer.compareTo(l2.layer);
        }
    }
}
