/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: GraphicsPreferences.java
 *
 * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.user;

import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.id.LayerId;
import com.sun.electric.database.text.PrefPackage;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;

import java.awt.Color;
import java.io.Serializable;
import java.util.Arrays;
import java.util.prefs.Preferences;

/**
 *
 */
public class GraphicsPreferences extends PrefPackage {
    // In TECH_NODE
    private static final String KEY_TRANSPARENT_COLOR = "TransparentLayer";

    private static final String KEY_PATTERN_DISPLAY = "UsePatternDisplayFor";
    private static final String KEY_PATTERN_PRINTER = "UsePatternPrinterFor";
    private static final String KEY_OUTLINE = "OutlinePatternFor";
    private static final String KEY_TRANSPARENT = "TransparentLayerFor";
    private static final String KEY_COLOR = "ColorFor";
    private static final String KEY_OPACITY = "OpacityFor";
    private static final String KEY_PATTERN = "PatternFor";
    private static final String KEY_TRANSPARENCY_MODE = "3DTransparencyModeOf";
    private static final String KEY_TRANSPARENCY_FACTOR = "3DTransparencyFactorOf";

    private static final String KEY_TEXT_VISIBILITY = "TextVisibility";

    public static final int RGB_MASK = 0xFFFFFF;

	/**
     * How to display ports.
	 * 0: full port names (the default).
	 * 1: short port names (stopping at the first nonalphabetic character).
	 * 2: ports drawn as crosses.
     */
    @IntegerPref(node = USER_NODE, key = "PortDisplayLevel", factory = DEF_PORT_DISPLAY_LEVEL)
    public int portDisplayLevel;
    private static final int DEF_PORT_DISPLAY_LEVEL = 0;
	/**
     * How to display exports.
	 * 0: full export names (the default).
	 * 1: short export names (stopping at the first nonalphabetic character).
	 * 2: exports drawn as crosses.
     */
    @IntegerPref(node = USER_NODE, key = "ExportDisplayLevel", factory = DEF_EXPORT_DISPLAY_LEVEL)
    public int exportDisplayLevel;
    private static final int DEF_EXPORT_DISPLAY_LEVEL = 0;

	/** The default font to use on the display. The default is "SansSerif". */
    @StringPref(node = USER_NODE, key = "DefaultFont", factory = FACTORY_DEFAULT_FONT)
    public String defaultFont;
    public static final String FACTORY_DEFAULT_FONT = "SansSerif";
    
    /** Whether to show temporary names on nodes and arcs. The default is false. */
    @BooleanPref(node = USER_NODE, key = "ShowTempNames", factory = false)
    public boolean showTempNames;

    private transient final TechPool techPool;
    private final Color[] defaultColors;
    private final boolean[] textVisibility;
    private final TechData[] techData;

    private class TechData implements Serializable {
        private final Technology tech;
        private final Color[] transparentColors;
        private transient Color[] colorMap;
        private final EGraphics[] layerGraphics;

        private TechData(Technology tech, Preferences techPrefs) {
            this.tech = tech;
            assert techPool.getTech(tech.getId()) == tech;
            transparentColors = tech.getFactoryTransparentLayerColors();
            for (int i = 0; i < transparentColors.length; i++) {
                String key = getKey(KEY_TRANSPARENT_COLOR + (i+1) + "For", tech.getId());
                int factoryRgb = transparentColors[i].getRGB();
                int rgb = techPrefs.getInt(key, factoryRgb);
                if (rgb == factoryRgb) continue;
                transparentColors[i] = new Color(rgb);
            }
            layerGraphics = new EGraphics[tech.getNumLayers()];
            for (int layerIndex = 0; layerIndex < layerGraphics.length; layerIndex++) {
                Layer layer = tech.getLayer(layerIndex);
                LayerId layerId = layer.getId();
                EGraphics factoryGraphics = layer.getFactoryGraphics();
                EGraphics graphics = factoryGraphics;

                // TECH_NODE
                String keyPatternDisplay = getKeyIn(KEY_PATTERN_DISPLAY, layerId);
                boolean factoryPatternDisplay = factoryGraphics.isPatternedOnDisplay();
                boolean patternDisplay = techPrefs.getBoolean(keyPatternDisplay, factoryPatternDisplay);
                graphics = graphics.withPatternedOnDisplay(patternDisplay);

                String keyPatternPrinter = getKeyIn(KEY_PATTERN_PRINTER, layerId);
                boolean factoryPatternPrinter = factoryGraphics.isPatternedOnPrinter();
                boolean patternPrinter = techPrefs.getBoolean(keyPatternPrinter, factoryPatternPrinter);
                graphics = graphics.withPatternedOnPrinter(patternPrinter);

                String keyOutline = getKeyIn(KEY_OUTLINE, layerId);
                EGraphics.Outline factoryOutline = factoryGraphics.getOutlined();
                int outlineIndex = techPrefs.getInt(keyOutline, factoryOutline.getIndex());
                EGraphics.Outline outline = EGraphics.Outline.findOutline(outlineIndex);
                graphics = graphics.withOutlined(outline);

                String keyTransparent = getKeyIn(KEY_TRANSPARENT, layerId);
                int factoryTransparent = factoryGraphics.getTransparentLayer();
                int transparent = techPrefs.getInt(keyTransparent, factoryTransparent);
                graphics = graphics.withTransparentLayer(transparent);

                String keyColor = getKeyIn(KEY_COLOR, layerId);
                int factoryColorRgb = factoryGraphics.getRGB();
                int colorRgb = techPrefs.getInt(keyColor, factoryColorRgb);
                graphics = graphics.withColor(new Color(colorRgb));

                String keyOpacity = getKeyIn(KEY_OPACITY, layerId);
                double factoryOpacity = factoryGraphics.getOpacity();
                double opacity = techPrefs.getDouble(keyOpacity, factoryOpacity);
                graphics = graphics.withOpacity(opacity);

                String keyPattern = getKeyIn(KEY_PATTERN, layerId);
                String factoryPatternStr = factoryGraphics.getPatternString();
                String patternStr = techPrefs.get(keyPattern, factoryPatternStr);
                graphics = graphics.withPattern(patternStr);

                String keyTransparencyMode = getKey(KEY_TRANSPARENCY_MODE, layerId);
                String factoryTransparencyModeStr = factoryGraphics.getTransparencyMode().name();
                String transparencyModeStr = techPrefs.get(keyTransparencyMode, factoryTransparencyModeStr);
                EGraphics.J3DTransparencyOption transparencyMode = EGraphics.J3DTransparencyOption.valueOf(transparencyModeStr);
                graphics = graphics.withTransparencyMode(transparencyMode);

                String keyTransparencyFactor = getKey(KEY_TRANSPARENCY_FACTOR, layerId);
                double factoryTransparenceyFactor = factoryGraphics.getTransparencyFactor();
                double transparencyFactor = techPrefs.getDouble(keyTransparencyFactor, factoryTransparenceyFactor);
                graphics = graphics.withTransparencyFactor(transparencyFactor);

                layerGraphics[layerIndex] = graphics;
            }
        }

        private TechData(TechData that, Color[] transparentColors, EGraphics[] layerGraphics) {
            this.tech = that.tech;
            this.transparentColors = transparentColors;
            this.layerGraphics = layerGraphics;
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) return true;
            if (o instanceof TechData) {
                TechData that = (TechData)o;
                return this.tech == that.tech &&
                        Arrays.equals(this.transparentColors, that.transparentColors) &&
                        Arrays.equals(this.layerGraphics, that.layerGraphics);
            }
            return false;
        }

        @Override
        public int hashCode() { return 0; }

        private void putPrefs(Preferences techPrefs, boolean removeDefaults, TechData oldTd) {
            if (oldTd != null && oldTd.tech != tech)
                oldTd = null;

            if (oldTd == null || transparentColors != oldTd.transparentColors) {
                Color[] factoryColors = tech.getFactoryTransparentLayerColors();
                for (int i = 0; i < factoryColors.length; i++) {
                    String key = getKey(KEY_TRANSPARENT_COLOR + (i+1) + "For", tech.getId());
                    int factoryRgb = factoryColors[i].getRGB();
                    int rgb = transparentColors[i].getRGB();
                    if (removeDefaults && rgb == factoryRgb)
                        techPrefs.remove(key);
                    else
                        techPrefs.putInt(key, rgb);
                }
            }

            assert layerGraphics.length == tech.getNumLayers();
            for (int layerIndex = 0; layerIndex < layerGraphics.length; layerIndex++) {
                Layer layer = tech.getLayer(layerIndex);
                LayerId layerId = layer.getId();
                EGraphics graphics = layerGraphics[layerIndex];
                if (oldTd == null || graphics != oldTd.layerGraphics[layerIndex]) {
                    EGraphics factoryGraphics = layer.getFactoryGraphics();

                    String keyPatternDisplay = getKeyIn(KEY_PATTERN_DISPLAY, layerId);
                    if (removeDefaults && graphics.isPatternedOnDisplay() == factoryGraphics.isPatternedOnDisplay())
                        techPrefs.remove(keyPatternDisplay);
                    else
                        techPrefs.putBoolean(keyPatternDisplay, graphics.isPatternedOnDisplay());

                    String keyPatternPrinter = getKeyIn(KEY_PATTERN_PRINTER, layerId);
                    if (removeDefaults && graphics.isPatternedOnPrinter() == factoryGraphics.isPatternedOnPrinter())
                        techPrefs.remove(keyPatternPrinter);
                    else
                        techPrefs.putBoolean(keyPatternPrinter, graphics.isPatternedOnPrinter());

                    String keyOutline = getKeyIn(KEY_OUTLINE, layerId);
                    if (removeDefaults && graphics.getOutlined() == factoryGraphics.getOutlined())
                        techPrefs.remove(keyOutline);
                    else
                        techPrefs.putInt(keyOutline, graphics.getOutlined().getIndex());

                    String keyTransparent = getKeyIn(KEY_TRANSPARENT, layerId);
                    if (removeDefaults && graphics.getTransparentLayer() == factoryGraphics.getTransparentLayer())
                        techPrefs.remove(keyTransparent);
                    else
                        techPrefs.putInt(keyTransparent, graphics.getTransparentLayer());

                    String keyColor = getKeyIn(KEY_COLOR, layerId);
                    if (removeDefaults && graphics.getColor().equals(factoryGraphics.getColor()))
                        techPrefs.remove(keyColor);
                    else
                        techPrefs.putInt(keyColor, graphics.getRGB());

                    String keyOpacity = getKeyIn(KEY_OPACITY, layerId);
                    if (removeDefaults && graphics.getOpacity() == factoryGraphics.getOpacity())
                        techPrefs.remove(keyOpacity);
                    else
                        techPrefs.putDouble(keyOpacity, graphics.getOpacity());

                    String keyPattern = getKeyIn(KEY_PATTERN, layerId);
                    if (removeDefaults && Arrays.equals(graphics.getPattern(), factoryGraphics.getPattern()))
                        techPrefs.remove(keyPattern);
                    else
                        techPrefs.put(keyPattern, graphics.getPatternString());

                    String keyTrancparencyMode = getKey(KEY_TRANSPARENCY_MODE, layerId);
                    if (removeDefaults && graphics.getTransparencyMode() == factoryGraphics.getTransparencyMode())
                        techPrefs.remove(keyTrancparencyMode);
                    else
                        techPrefs.put(keyTrancparencyMode, graphics.getTransparencyMode().name());

                    String keyTrancparencyFactor = getKey(KEY_TRANSPARENCY_FACTOR, layerId);
                    if (removeDefaults && graphics.getTransparencyFactor() == factoryGraphics.getTransparencyFactor())
                        techPrefs.remove(keyTrancparencyFactor);
                    else
                        techPrefs.putDouble(keyTrancparencyFactor, graphics.getTransparencyFactor());
                }
            }
        }

        private TechData withTransparentColor(int transparentLayer, Color transparentColor) {
            if (transparentLayer < 0 || transparentLayer >= transparentColors.length)
                throw new IllegalArgumentException();
            if (transparentColor.equals(transparentColors[transparentLayer])) return this;
            Color[] newTransparentColors = transparentColors.clone();
            newTransparentColors[transparentLayer] = transparentColor;
            return new TechData(this, newTransparentColors, layerGraphics);
        }

        private TechData withTransparentColors(Color[] transparentColors) {
            if (Arrays.equals(this.transparentColors, transparentColors)) return this;
            Color[] factoryColors = tech.getFactoryTransparentLayerColors();
            if (transparentColors.length < factoryColors.length)
                throw new IllegalArgumentException();
            transparentColors = transparentColors.clone();
            for (Color color: transparentColors) {
                if (color.getAlpha() != 255)
                    throw new IllegalArgumentException();
            }
            return new TechData(this, transparentColors, layerGraphics);
        }

        private TechData withGraphics(Layer layer, EGraphics graphics) {
            assert layer.getTechnology() == tech;
            int layerIndex = layer.getIndex();
            EGraphics currentGraphics = layerGraphics[layerIndex];
            if (currentGraphics.equals(graphics)) return this;
            EGraphics[] newLayerGraphics = layerGraphics.clone();
            newLayerGraphics[layerIndex] = graphics;
            return new TechData(this, transparentColors, newLayerGraphics);
        }

        /**
         * Returns the color map for transparent layers in this technology.
         * @return the color map for transparent layers in this technology.
         * The number of entries in this map equals 2 to the power "getNumTransparentLayers()".
         */
        private Color[] getColorMap() {
            if (colorMap == null)
                colorMap = Technology.makeColorMap(transparentColors);
            return colorMap.clone();
        }

//        private Color [] makeColorMap() {
//            int numEntries = 1 << transparentColors.length;
//            Color [] map = new Color[numEntries];
//            for (int i = 0; i < numEntries; i++) {
//                int r=200, g=200, b=200;
//                boolean hasPrevious = false;
//                for (int j = 0; j < transparentColors.length; j++) {
//                    if ((i & (1<<j)) == 0) continue;
//                    Color layerColor = transparentColors[j];
//                    if (hasPrevious) {
//                        // get the previous color
//                        double [] lastColor = new double[3];
//                        lastColor[0] = r / 255.0;
//                        lastColor[1] = g / 255.0;
//                        lastColor[2] = b / 255.0;
//                        normalizeColor(lastColor);
//
//                        // get the current color
//                        double [] curColor = new double[3];
//                        curColor[0] = layerColor.getRed() / 255.0;
//                        curColor[1] = layerColor.getGreen() / 255.0;
//                        curColor[2] = layerColor.getBlue() / 255.0;
//                        normalizeColor(curColor);
//
//                        // combine them
//                        for(int k=0; k<3; k++) curColor[k] += lastColor[k];
//                        normalizeColor(curColor);
//                        r = (int)(curColor[0] * 255.0);
//                        g = (int)(curColor[1] * 255.0);
//                        b = (int)(curColor[2] * 255.0);
//                    } else {
//                        r = layerColor.getRed();
//                        g = layerColor.getGreen();
//                        b = layerColor.getBlue();
//                        hasPrevious = true;
//                    }
//                }
//                map[i] = new Color(r, g, b);
//            }
//            return map;
//        }
//
//        /**
//         * Method to normalize a color stored in a 3-long array.
//         * @param a the array of 3 doubles that holds the color.
//         * All values range from 0 to 1.
//         * The values are adjusted so that they are normalized.
//         */
//        private void normalizeColor(double [] a)
//        {
//            double mag = Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
//            if (mag < 1.0e-11f) return;
//            a[0] /= mag;
//            a[1] /= mag;
//            a[2] /= mag;
//        }
    }

    public GraphicsPreferences(boolean factory) {
        this(factory, TechPool.getThreadTechPool());
    }

    public GraphicsPreferences(boolean factory, TechPool techPool) {
        super(factory);
        this.techPool = techPool;
        int maxTechIndex = -1;
        for (Technology tech: techPool.values())
            maxTechIndex = Math.max(maxTechIndex, tech.getId().techIndex);
        techData = new TechData[maxTechIndex+1];

        Preferences prefRoot = factory ? getFactoryPrefRoot() : getPrefRoot();
        Preferences techPrefs = prefRoot.node(TECH_NODE);
        Preferences userPrefs = prefRoot.node(USER_NODE);

        for (Technology tech: techPool.values())
            techData[tech.getId().techIndex] = new TechData(tech, techPrefs);

        User.ColorPrefType[] colorPrefTypes = User.ColorPrefType.class.getEnumConstants();
        defaultColors = new Color[colorPrefTypes.length];
        for (int i = 0; i < colorPrefTypes.length; i++) {
            User.ColorPrefType e = colorPrefTypes[i];
            Color factoryColor = e.getFactoryDefaultColor();
            int factoryRgb = factoryColor.getRGB();
            int rgb = userPrefs.getInt(e.getPrefKey(), factoryRgb);
            Color color = rgb == factoryRgb ? factoryColor : new Color(rgb);
            defaultColors[i] = color;
        }

        TextDescriptor.TextType[] textTypes = TextDescriptor.TextType.class.getEnumConstants();
        textVisibility = new boolean[textTypes.length];
        for (int i = 0; i < textTypes.length; i++)
            textVisibility[i] = userPrefs.getBoolean(textTypes[i].getKey(KEY_TEXT_VISIBILITY), true);
    }

    @Override
    public void putPrefs(Preferences prefRoot, boolean removeDefaults) {
        putPrefs(prefRoot, removeDefaults, null);
    }

    public void putPrefs(Preferences prefRoot, boolean removeDefaults, GraphicsPreferences oldGp) {
        super.putPrefs(prefRoot, removeDefaults);

        if (oldGp != null && oldGp.techPool != techPool)
            oldGp = null;

        Preferences techPrefs = prefRoot.node(TECH_NODE);
        Preferences userPrefs = prefRoot.node(USER_NODE);
        for (int techIndex = 0; techIndex < techData.length; techIndex++) {
            TechData td = techData[techIndex];
            if (td == null) continue;
            TechData oldTd = oldGp != null ? oldGp.techData[techIndex] : null;
            td.putPrefs(techPrefs, removeDefaults, oldTd);
        }

        if (oldGp == null || defaultColors != oldGp.defaultColors) {
            User.ColorPrefType[] colorPrefTypes = User.ColorPrefType.class.getEnumConstants();
            for (int i = 0; i < colorPrefTypes.length; i++) {
                User.ColorPrefType t = colorPrefTypes[i];
                if (removeDefaults && defaultColors[i].equals(t.getFactoryDefaultColor()))
                    userPrefs.remove(t.getPrefKey());
                else
                    userPrefs.putInt(t.getPrefKey(), defaultColors[i].getRGB());
            }
        }

        if (oldGp == null || textVisibility != oldGp.textVisibility) {
            TextDescriptor.TextType[] textTypes = TextDescriptor.TextType.class.getEnumConstants();
            for (int i = 0; i < textTypes.length; i++) {
                TextDescriptor.TextType t = textTypes[i];
                String key = t.getKey(KEY_TEXT_VISIBILITY);
                if (removeDefaults && textVisibility[i])
                    userPrefs.remove(key);
                else
                    userPrefs.putBoolean(key, textVisibility[i]);
            }
        }
    }

    public GraphicsPreferences withTransparentLayerColors(Technology tech, Color[] tranparentColors) {
        TechData td = getTechData(tech);
        return withTechData(td.withTransparentColors(tranparentColors));
    }

    public GraphicsPreferences withTransparentLayerColor(Technology tech, int transparentLayer, Color tranparentColor) {
        TechData td = getTechData(tech);
        return withTechData(td.withTransparentColor(transparentLayer, tranparentColor));
    }

    public GraphicsPreferences withGraphics(Layer layer, EGraphics graphics) {
        TechData td = getTechData(layer.getTechnology());
        return withTechData(td.withGraphics(layer, graphics));
    }

    public GraphicsPreferences withColor(User.ColorPrefType t, Color color) {
        if (color.equals(defaultColors[t.ordinal()])) return this;
        Color[] newDefaultColors = defaultColors.clone();
        newDefaultColors[t.ordinal()] = color;
        return (GraphicsPreferences)withField("defaultColors", newDefaultColors);
    }

    public GraphicsPreferences withFactoryColor(User.ColorPrefType t) { return withColor(t, t.getFactoryDefaultColor()); }

    public GraphicsPreferences withTextVisibilityOn(TextDescriptor.TextType t, boolean b) {
        if (b == textVisibility[t.ordinal()]) return this;
        boolean[] newTextVisibility = textVisibility.clone();
        newTextVisibility[t.ordinal()] = b;
        return (GraphicsPreferences)withField("textVisibility", newTextVisibility);
    }

    public GraphicsPreferences withPortDisplayLevel(int portDisplayLevel) {
        if (portDisplayLevel == this.portDisplayLevel) return this;
        return (GraphicsPreferences)withField("portDisplayLevel", Integer.valueOf(portDisplayLevel));
    }

    public GraphicsPreferences withExportDisplayLevel(int exportDisplayLevel) {
        if (exportDisplayLevel == this.exportDisplayLevel) return this;
        return (GraphicsPreferences)withField("exportDisplayLevel", Integer.valueOf(exportDisplayLevel));
    }

    public GraphicsPreferences withDisplayLevelReset() {
        return withPortDisplayLevel(DEF_PORT_DISPLAY_LEVEL).withExportDisplayLevel(DEF_EXPORT_DISPLAY_LEVEL);
    }

    public GraphicsPreferences withDefaultFont(String defaultFont) {
        if (defaultFont.equals(this.defaultFont)) return this;
        return (GraphicsPreferences)withField("defaultFont", defaultFont);
    }

    public GraphicsPreferences withShowTempNames(boolean showTempNames) {
        if (showTempNames == this.isShowTempNames()) return this;
        return (GraphicsPreferences)withField("showTempNames", Boolean.valueOf(showTempNames));
    }

	/**
	 * Returns the number of transparent layers in specified technology.
	 * Informs the display system of the number of overlapping or transparent layers
	 * in use.
     * @param tech specified Technology
	 * @return the number of transparent layers in specified technology.
	 * There may be 0 transparent layers in technologies that don't do overlapping,
	 * such as Schematics.
	 */
	public int getNumTransparentLayers(Technology tech) { return getTechData(tech).transparentColors.length; }

	/**
	 * Method to return the colors for the transparent layers in specified Technology.
     * @param tech specified Technoology
	 * @return the factory for the transparent layers in this Technology.
	 */
    public Color[] getTransparentLayerColors(Technology tech) { return getTechData(tech).transparentColors.clone(); }

	/**
	 * Returns the color map for transparent layers in specified technology.
     * @param tech specified Technology
	 * @return the color map for transparent layers in specified technology.
	 * The number of entries in this map equals 2 to the power "getNumTransparentLayers()".
	 */
	public Color [] getColorMap(Technology tech) { return getTechData(tech).getColorMap(); }

	/**
	 * Method to return the graphics description of specified Layer.
     * @param layer specified Layer
	 * @return the graphics description of specified Layer.
	 */
    public EGraphics getGraphics(Layer layer) { return getTechData(layer.getTechnology()).layerGraphics[layer.getIndex()]; }

	/**
	 * Method to get the color of a given special layer type on the display.
	 * @param t layer type in question.
	 * @return color of the special layer type.
	 */
    public Color getColor(User.ColorPrefType t) { return defaultColors[t.ordinal()]; }

	/**
	 * Method to tell whether to draw text of particular text type.
	 * The default is "true".
     * @param t specified text type.
	 * @return true if the system should text of specified type.
	 */
    public boolean isTextVisibilityOn(TextDescriptor.TextType t) { return textVisibility[t.ordinal()]; }
    
    /**
     * Whether to show temporary names on nodes and arcs.
     * true to show temporary names.
     * The default is false.
     * @return true to show temporary names on nodes and arcs.
     */
    public boolean isShowTempNames() {
        return showTempNames;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (o instanceof GraphicsPreferences) {
            GraphicsPreferences that = (GraphicsPreferences)o;
            return this.techPool == that.techPool &&
                    Arrays.equals(this.techData, that.techData) &&
                    Arrays.equals(this.defaultColors, that.defaultColors) &&
                    Arrays.equals(this.textVisibility, that.textVisibility) &&
                    this.portDisplayLevel == that.portDisplayLevel &&
                    this.exportDisplayLevel == that.exportDisplayLevel &&
                    this.defaultFont.equals(that.defaultFont) &&
                    this.isShowTempNames() == that.isShowTempNames();
        }
        return false;
    }

    @Override
    public int hashCode() { return 0; }

    private GraphicsPreferences withTechData(TechData td) {
        int techIndex = td.tech.getId().techIndex;
        if (td == techData[techIndex]) return this;
        TechData[] newTechData = techData.clone();
        newTechData[techIndex] = td;
        return (GraphicsPreferences)withField("techData", newTechData);
    }

//    public GraphicsPreferences withLayersReset() {
//        if (defaultGraphics.isEmpty()) return this;
//        return new GraphicsPreferences(this,
//                new HashMap<LayerId,EGraphics>(),
//                defaultColors);
//    }

    private TechData getTechData(Technology tech) {
        TechData td = techData[tech.getId().techIndex];
        assert td.tech == tech;
        return td;
    }

    protected String getKeyIn(String what, LayerId layerId) {
        int len = what.length() + layerId.fullName.length() + 1;
        StringBuilder sb = new StringBuilder(len);
        sb.append(what);
        sb.append(layerId.name);
        sb.append("In");
        sb.append(layerId.techId.techName);
        assert sb.length() == len;
        return sb.toString();
    }
}
