/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: InvisibleLayerConfiguration.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.ui;

import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.menus.WindowMenu;
import com.sun.electric.util.TextUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class to manage saved collections of invisible layers.
 * Each configuration has a name, a list of Layers that are invisible, and an associated Technology
 * (all Layers must be in the same Technology).
 */
public class InvisibleLayerConfiguration
{
    public static final int NUM_CONFIGS = EGraphics.getMaxTransparentLayer()+1; // 13;
	private static InvisibleLayerConfiguration onlyOne = new InvisibleLayerConfiguration();

	/** Maps configuration name to a List of Technology-configurations */
	private Map<String,List<String>> configurations;

	/**
	 * Method to return the singleton of this class.
	 * @return the only instance of this class.
	 */
	public static InvisibleLayerConfiguration getOnly() { return onlyOne; }

    /**
	 * Constructor gets the saved configurations from the Preferences.
	 */
	private InvisibleLayerConfiguration()
	{
		configurations = new HashMap<String,List<String>>();
		String sc = User.savedConfigurations.getString();
		boolean [] overridden = new boolean[NUM_CONFIGS];
		for(;;)
		{
			int openPos = sc.indexOf('[');
			if (openPos < 0) break;
			int closePos = sc.indexOf(']');
			if (closePos < 0) break;
			String config = sc.substring(openPos+1, closePos);
			sc = sc.substring(closePos+1);

			String[] techParts = config.split("\t");
			if (techParts.length <= 1) continue;
			String cName = techParts[0];
			List<String> techPartsList = new ArrayList<String>();
			for(int i=1; i<techParts.length; i++)
			{
				if (techParts[i].startsWith("("))
				{
					int index = TextUtils.atoi(techParts[i].substring(1));
					overridden[index] = true;
				}
				techPartsList.add(techParts[i]);
			}
			configurations.put(cName, techPartsList);
		}
		for(int i=0; i<NUM_CONFIGS; i++)
		{
			if (overridden[i]) continue;
			String menuName = getDefaultHardwiredName(i);
			String value = "(" + i + ")";
			List<String> techPartsList = new ArrayList<String>();
			techPartsList.add(value);
			configurations.put(menuName, techPartsList);
		}
	}

	private String getDefaultHardwiredName(int index)
	{
		String menuName = "Set ";
		if (index == 0) menuName += "All"; else
			menuName += "M" + index;
		menuName += " Visible";
		return menuName;
	}

	public String getMenuName(int index)
	{
		String cName = findHardWiredConfiguration(index);
		if (cName == null) return getDefaultHardwiredName(index);
		return cName;
	}

	/**
	 * Method to save the configuration state to the Preferences.
	 */
	private void saveConfigurations()
	{
		StringBuffer sb = new StringBuffer();
		for(String cName : configurations.keySet())
		{
			List<String> techParts = configurations.get(cName);
			sb.append('[');
			sb.append(cName);
			for(String techPart : techParts)
			{
				sb.append('\t');
				sb.append(techPart);
			}
			sb.append(']');
		}
		User.savedConfigurations.setString(sb.toString());
		WindowMenu.setDynamicVisibleLayerMenus();
	}

	/**
	 * Method to tell whether a invisible layer configuration name exists.
	 * @param cName the name of the invisible layer configuration.
	 * @return true if there is a invisible layer configuration with that name.
	 */
	public boolean exists(String cName)
	{
		List<String> techParts = configurations.get(cName);
		return techParts != null;
	}

	/**
	 * Method to add a invisible layer configuration.
	 * @param cName the name of the new invisible layer configuration.
	 * @param hardWiredIndex the hard-wired value (from 0-9) for pre-bound configurations.
	 * @param tech the Technology in which these layers reside.
	 * @param layers the list of invisible layers in the configuration.
	 */
	public void addConfiguration(String cName, int hardWiredIndex, Technology tech, List<Layer> layers)
	{
		StringBuffer sb = new StringBuffer();
		if (hardWiredIndex >= 0) sb.append("(" + hardWiredIndex + ")");
		sb.append(tech.getTechName());
		for(Layer layer : layers)
		{
			sb.append(',');
			if (layer.getTechnology() != tech) sb.append(layer.getTechnology().getTechName() + ":");
			sb.append(layer.getName());
		}
		List<String> techParts = configurations.get(cName);
		List<String> newTechParts = new ArrayList<String>();
		boolean found = false;
		if (techParts != null)
		{
			for(String techPart : techParts)
			{
				// make a set of all invisible layers
				String[] iLayers = techPart.split(",");
				if (iLayers.length == 0) continue;
				String techName = iLayers[0];
				int endPos = techName.indexOf(')');
				if (endPos >= 0) techName = techName.substring(endPos+1);
				Technology thisTech = Technology.findTechnology(techName);
				if (thisTech == null) continue;
				if (thisTech == tech) { techPart = sb.toString(); found = true; }
				newTechParts.add(techPart);
			}
		}
		if (!found) newTechParts.add(sb.toString());
		configurations.put(cName, newTechParts);
		saveConfigurations();
	}

	/**
	 * Method to rename an invisible layer configuration.
	 * @param cName the name of the configuration to rename.
	 * @param newName the new configuration name.
	 */
	public void renameConfiguration(String cName, String newName)
	{
		List<String> techParts = configurations.get(cName);
		if (techParts == null) return;
		configurations.remove(cName);
		configurations.put(newName, techParts);
		saveConfigurations();
	}

	/**
	 * Method to delete an invisible layer configuration for a given Technology.
	 * @param cName the name of the invisible layer configuration to delete.
	 * @param tech the Technology to delete.
	 */
	public void deleteConfiguration(String cName, Technology tech)
	{
		List<String> techParts = configurations.get(cName);
		List<String> newTechParts = new ArrayList<String>();
		int hardIndex = -1;
		if (techParts != null)
		{
			for(String techPart : techParts)
			{
				String[] iLayers = techPart.split(",");
				if (iLayers.length == 0) continue;
				String techName = iLayers[0];
				int endPos = techName.indexOf(')');
				if (endPos >= 0)
				{
					hardIndex = TextUtils.atoi(techName.substring(1));
					techName = techName.substring(endPos+1);
				}
				Technology thisTech = Technology.findTechnology(techName);
				if (thisTech == tech) continue;
				newTechParts.add(techPart);
			}
		}
		if (newTechParts.size() == 0 && hardIndex >= 0)
		{
			String value = "(" + hardIndex + ")";
			newTechParts.add(value);
		}
		if (newTechParts.size() == 0) configurations.remove(cName); else
			configurations.put(cName, newTechParts);
		saveConfigurations();
	}

	/**
	 * Method to return the names of all invisible layer configurations.
	 * @return a List of invisible layer configuration names.
	 */
	public List<String> getConfigurationNames()
	{
		List<String> configs = new ArrayList<String>();
		for(String key : configurations.keySet())
			configs.add(key);
        Collections.sort(configs);
		return configs;
	}

	/**
	 * Method to get the Technologies associated with a invisible layer configuration.
	 * @param cName the name of the invisible layer configuration.
	 * @return a List of Technologies associated with that invisible layer configuration
	 * (may be empty).
	 */
	public List<Technology> getConfigurationTechnology(String cName)
	{
		List<Technology> techs = new ArrayList<Technology>();
		List<String> techParts = configurations.get(cName);
		if (techParts != null)
		{
			for(String techPart : techParts)
			{
				// make a set of all invisible layers
				String[] iLayers = techPart.split(",");
				if (iLayers.length != 0)
				{
					String techName = iLayers[0];
					int endPos = techName.indexOf(')');
					if (endPos >= 0) techName = techName.substring(endPos+1);
					Technology tech = Technology.findTechnology(techName);
					if (tech != null) techs.add(tech);
				}
			}
		}
		return techs;
	}

	/**
	 * Method to find the configuration that is hard-wired to a given index.
	 * @param index the index (from 0 to 9).
	 * @return the name of the configuration bound to that index (null if none).
	 */
	public String findHardWiredConfiguration(int index)
	{
		for(String cName : configurations.keySet())
		{
			List<String> techLayers = configurations.get(cName);
			if (techLayers == null) continue;
			for(String techLayer : techLayers)
			{
				if (techLayer.startsWith("("))
				{
					if (index != TextUtils.atoi(techLayer.substring(1))) continue;
					return cName;
				}
			}
		}
		return null;
	}

	/**
	 * Method to get the "hard wired" index of this visibility configuration name.
	 * Some visibility configurations are hard-wired to the SHIFT-0 through SHIFT-9
	 * keys, and these will return a value of 0 through 9.
	 * @param cName the name of the invisible layer configuration.
	 * @return the hard-wired index (-1 if none).
	 */
	public int getConfigurationHardwiredIndex(String cName)
	{
		List<String> techLayers = configurations.get(cName);
		if (techLayers != null)
		{
			// make a set of all invisible layers
			for(String techLayer : techLayers)
			{
				String[] iLayers = techLayer.split(",");
				if (iLayers.length != 0)
				{
					String techName = iLayers[0];
					if (techName.startsWith("("))
						return TextUtils.atoi(techName.substring(1));
				}
			}
		}
		return -1;
	}

	/**
	 * Method to return the invisible layers in an invisible layer configuration.
	 * @param cName the name of the invisible layer configuration.
	 * @return a Map from each Technology to a set of invisible Layers.
	 * Any technology not in the map has all layers visible.
	 */
	public Map<Technology,List<Layer>> getConfigurationValue(String cName)
	{
		Map<Technology,List<Layer>> invisibleLayers = new HashMap<Technology,List<Layer>>();
		List<String> techParts = configurations.get(cName);
		if (techParts != null)
		{
			for(String techPart : techParts)
			{
				// make a set of all invisible layers
				String[] iLayers = techPart.split(",");
				if (iLayers.length != 0)
				{
					String techName = iLayers[0];
					int endPos = techName.indexOf(')');
					if (endPos >= 0) techName = techName.substring(endPos+1);
					Technology tech = Technology.findTechnology(techName);
					if (tech == null) continue;
					for(int i=1; i<iLayers.length; i++)
					{
						String iLayer = iLayers[i];
						int colonPos = iLayer.indexOf(':');
						Technology findTech = tech;
						if (colonPos >= 0)
						{
							findTech = Technology.findTechnology(iLayer.substring(0, colonPos));
							iLayer = iLayer.substring(colonPos+1);
						}
						Layer lay = findTech.findLayer(iLayer);
						if (lay == null) continue;
						List<Layer> curLays = invisibleLayers.get(tech);
						if (curLays == null) invisibleLayers.put(tech, curLays = new ArrayList<Layer>());
						curLays.add(lay);
					}
				}
			}
		}
		return invisibleLayers;
	}
}
