// Copyright 2000-2005, FreeHEP.
package hep.graphics.heprep.ref;

import java.awt.*;
import java.io.*;
import java.util.*;

import org.freehep.util.ScientificFormat;
import org.freehep.swing.ColorConverter;

import hep.graphics.heprep.*;
import hep.graphics.heprep.xml.*;
import hep.graphics.heprep.util.*;

/**
 *
 * @author M.Donszelmann
 *
 * @version $Id: DefaultHepRepAttValue.java,v 1.40 2005/02/15 19:33:06 duns Exp $
 */

public class DefaultHepRepAttValue implements HepRepAttValue, Serializable {
    public static final String cvsId = "$Id: DefaultHepRepAttValue.java,v 1.40 2005/02/15 19:33:06 duns Exp $";

    private static final ScientificFormat scientific = new ScientificFormat();
    private String name;
    private String lowerCaseName;

    // values implemented as separate items, so that they do not take up unnecessary space for an Object
    // only ONE of these is filled
    private String lowerCaseString;
    private Object objectValue;
    private long longValue;
    private double doubleValue;
    private boolean booleanValue;

    private int type;
    private int showLabel;

    private DefaultHepRepAttValue() {
        // Un-initialized, for clone
    }

    void replace(DefaultHepRepAttValue attValue) {
        name = attValue.name;
        lowerCaseName = attValue.lowerCaseName;
        
        lowerCaseString = attValue.lowerCaseString;
        objectValue = attValue.objectValue;
        longValue = attValue.longValue;
        doubleValue = attValue.doubleValue;
        booleanValue = attValue.booleanValue;
        type = attValue.type;
        showLabel = attValue.showLabel;
    }

    public DefaultHepRepAttValue(String name, String value, int showLabel) {
        init(name, TYPE_STRING, showLabel);
        objectValue = (value == null) ? null : value.intern();
        lowerCaseString = (value == null) ? null : value.toLowerCase().intern();
    }

    public DefaultHepRepAttValue(String name, Color value, int showLabel) {
        init(name, TYPE_COLOR, showLabel);
        objectValue = value;
    }

    public DefaultHepRepAttValue(String name, long value, int showLabel) {
        init(name, TYPE_LONG, showLabel);
        longValue = value;
    }

    public DefaultHepRepAttValue(String name, int value, int showLabel) {
        init(name, TYPE_INT, showLabel);
        longValue = value;
    }

    public DefaultHepRepAttValue(String name, double value, int showLabel) {
        init(name, TYPE_DOUBLE, showLabel);
        doubleValue = value;
    }

    public DefaultHepRepAttValue(String name, boolean value, int showLabel) {
        init(name, TYPE_BOOLEAN, showLabel);
        booleanValue = value;
    }

    public DefaultHepRepAttValue(String name, String value, String type, int showLabel) {
        int t = toType(type);
        init(name, t, showLabel);
        switch (t) {
            case TYPE_STRING:
                objectValue = value.intern();
                lowerCaseString = value.toLowerCase().intern();
                break;
            case TYPE_COLOR:
                objectValue = HepRepColor.get(value);
                break;
            case TYPE_LONG:
                longValue = Long.decode(value).longValue();
                break;
            case TYPE_INT:
                longValue = Integer.decode(value).intValue();
                break;
            case TYPE_DOUBLE:
                doubleValue = HepRepUtil.decodeNumber(value);
                break;
            case TYPE_BOOLEAN:
                booleanValue = Boolean.valueOf(value).booleanValue();
                break;
            default:
                System.err.println("Unknown type in DefaultHepRepAttValue: '"+type+"'");
                objectValue = value;
                break;
        }
    }

    private void init(String name, int type, int showLabel) {
        this.name = name.intern();
        this.lowerCaseName = name.toLowerCase().intern();
        this.type = type;
        this.showLabel = showLabel;
    }

    public HepRepAttValue copy() throws CloneNotSupportedException {
        DefaultHepRepAttValue copy = new DefaultHepRepAttValue();
        copy.init(name, type, showLabel);
        copy.lowerCaseString = lowerCaseString;
        copy.objectValue = objectValue;
        copy.longValue = longValue;
        copy.doubleValue = doubleValue;
        copy.booleanValue = booleanValue;
        return copy;
    }

    public String getName() {
        return name;
    }

    public String getLowerCaseName() {
        return lowerCaseName;
    }

    public int getType() {
        return type;
    }

    // Use the toString method of object, so that this is well defined
    // for all data types.
    public String getTypeName() {
        return toString(type);
    }

    private static String labelStrings[] = {"NAME", "DESC", "VALUE", "EXTRA"};
    private static Map labelTable;
    static {
        labelTable = new HashMap(5);
        // FIXME: FREEHEP-366
        labelTable.put("NONE", new Integer(HepRepAttValue.SHOW_NONE));
        labelTable.put("NAME", new Integer(HepRepAttValue.SHOW_NAME));
        labelTable.put("DESC", new Integer(HepRepAttValue.SHOW_DESC));
        labelTable.put("VALUE", new Integer(HepRepAttValue.SHOW_VALUE));
        labelTable.put("EXTRA", new Integer(HepRepAttValue.SHOW_EXTRA));
    }

    public static int toShowLabel(String labelString) {
        int showLabel = SHOW_NONE;
        if (labelString != null) {
            StringTokenizer st = new StringTokenizer(labelString, ", ");
            while (st.hasMoreTokens()) {
                String label = st.nextToken();
                Integer number = (Integer)labelTable.get(label);
                if (number != null) {
                    showLabel += number.intValue();
                } else {
                    showLabel += Integer.decode(label).intValue();
                }
            }
        }
        return showLabel;
    }

    public static String toShowLabel(int showLabel) {
        String label = null;
        if (showLabel == HepRepAttValue.SHOW_NONE) {
            label = "NONE";
        } else {
            for (int i=0; i<16; i++) {
                if (((showLabel >> i) & 0x0001) == 0x0001) {
                    if (label == null) {
                        label = "";
                    } else {
                        label += ", ";
                    }
                    if (i < labelStrings.length) {
                        label += labelStrings[i];
                    } else {
                        label += "0x"+Integer.toHexString(0x0001 << i);
                    }
                }
            }
        }
        return label;
    }

    public static String toString(int type) {
        switch(type) {
            case TYPE_STRING: return("String");
            case TYPE_COLOR: return("Color");
            case TYPE_LONG: return("long");
            case TYPE_INT: return("int");
            case TYPE_DOUBLE: return("double");
            case TYPE_BOOLEAN: return("boolean");
            default: throw new RuntimeException("Unknown type stored in HepRepAttDef: '"+type+"'");
        }
    }

    private static final Map stringToType;
    static {
        stringToType = new HashMap(6);
        stringToType.put("String",      new Integer(TYPE_STRING));
        stringToType.put("Color",       new Integer(TYPE_COLOR));
        stringToType.put("long",        new Integer(TYPE_LONG));
        stringToType.put("int",         new Integer(TYPE_INT));
        stringToType.put("double",      new Integer(TYPE_DOUBLE));
        stringToType.put("boolean",     new Integer(TYPE_BOOLEAN));
    }

    /**
     * @return code for type or -1 if unknown
     */
    public static int toType(String type) {
        Integer code = (type != null) ? (Integer)stringToType.get(type) : null;
        return (code == null) ? TYPE_UNKNOWN : code.intValue();
    }

    private static final Map types;
    static {
        try {
            XMLHepRepReader.readDefaults();
        } catch (IOException ioe) {
            System.err.println("Problem reading HepRep Defaults "+ioe);
        }
        
        // add all defaults to types.
        Set attValues = HepRepDefaults.getAttValues();
        types = new HashMap(attValues.size());
        for (Iterator i=attValues.iterator(); i.hasNext(); ) {
            HepRepAttValue attValue = (HepRepAttValue)i.next();
            types.put(attValue.getLowerCaseName(), attValue.getTypeName());
        }
    }

    /**
     * Add a new type for guessing
     */
    public static void addGuessedType(String name, String type) {
        types.put(name.toLowerCase(), type);
    }

    /**
     * Returns type, unless type is null, in which case it guesses the type from the name,
     * and if not found from the value. If found from the value a message is printed
     * and the new association is added to the name table. If not found, the type defaults
     * to String and is also added to the table, but no message is printed.
     */
    public static String guessType(String name, String value, String type) {
        if (type != null) return type;
        
        type = guessTypeFromName(name);

        if (type == null) {            
            type = guessTypeFromValue(value);

            if (!type.equals("String")) {
                System.out.println("Guessed type for '"+name+"' to be '"+type+"'.");
            }
            types.put(name.toLowerCase(), type);
        }
        return type;
    }
    
    /**
     * Returns type from a guess by name, or null if it cannot be guessed.
     */
    public static String guessTypeFromName(String name) {         
        return (String)types.get(name.toLowerCase());

    }

    /**
     * Returns type, guessed from the value. Defaults to String.
     */
    public static String guessTypeFromValue(String value) {
        if (value == null) return "String";
        try {
            // guess long and ints to be double to allow for flexibility...
            Double.valueOf(value);
            return "double";
        } catch (NumberFormatException e) {
        }
        if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("true")) {
            return "boolean";
        }
        try {
            ColorConverter cc = new ColorConverter();
            cc.stringToColor(value);
            return "Color";
        } catch (ColorConverter.ColorConversionException e) {
        }
        return "String";
    }

    public int showLabel() {
        return showLabel;
    }

    public String getString() throws HepRepTypeException {
        try {
            return (String)objectValue;
        } catch (ClassCastException cce) {
            throw new HepRepTypeException("Attribute Value for '"+getName()+
                                          "' with value '"+getAsString()+
                                          "' of type '"+getTypeName()+
                                          "' cannot be converted to type 'String'");
        }
    }

    public String getLowerCaseString() throws HepRepTypeException {
        try {
            return lowerCaseString;
        } catch (ClassCastException cce) {
            throw new HepRepTypeException("Attribute Value for '"+getName()+
                                          "' with value '"+getAsString()+
                                          "' of type '"+getTypeName()+
                                          "' cannot be converted to type 'String'");
        }
    }

    public Color getColor() throws HepRepTypeException {
        try {
            return (Color)objectValue;
        } catch (ClassCastException cce) {
            throw new HepRepTypeException("Attribute Value for '"+getName()+
                                          "' with value '"+getAsString()+
                                          "' of type '"+getTypeName()+
                                          "' cannot be converted to type 'Color'");
        }
    }

    public long getLong() throws HepRepTypeException {
        if ((type != TYPE_LONG) && (type != TYPE_INT)) {
            throw new HepRepTypeException("Attribute Value for '"+getName()+
                                          "' with value '"+getAsString()+
                                          "' of type '"+getTypeName()+
                                          "' cannot be converted to type 'long'");
        }
        return longValue;
    }

    public int getInteger() throws HepRepTypeException {
        if (type != TYPE_INT) {
            throw new HepRepTypeException("Attribute Value for '"+getName()+
                                          "' with value '"+getAsString()+
                                          "' of type '"+getTypeName()+
                                          "' cannot be converted to type 'int'");
        }
        return (int)longValue;
    }

    public double getDouble() throws HepRepTypeException {
        if ((type != TYPE_DOUBLE) && (type != TYPE_LONG) && (type != TYPE_INT)) {
            throw new HepRepTypeException("Attribute Value for '"+getName()+
                                          "' with value '"+getAsString()+
                                          "' of type '"+getTypeName()+
                                          "' cannot be converted to type 'double'");
        }
        return (type == TYPE_DOUBLE) ? doubleValue : longValue;
    }

    public boolean getBoolean() throws HepRepTypeException {
        if ((type != TYPE_BOOLEAN) && (type != TYPE_LONG) && (type != TYPE_INT))  {
            throw new HepRepTypeException("Attribute Value for '"+getName()+"'and value+'"+objectValue+"' of type'"+getTypeName()+"' cannot be converted to type 'boolean'");
        }
        return (type == TYPE_BOOLEAN) ? booleanValue : longValue != 0;
    }

    public String getAsString() {
        return getAsString(this);
    }

    public static String getAsString(HepRepAttValue attValue) {
        switch(attValue.getType()) {
            case TYPE_STRING:
                return attValue.getString();
            case TYPE_COLOR:
                return HepRepColor.get(attValue.getColor());
            case TYPE_LONG:
                return Long.toString(attValue.getLong());
            case TYPE_INT:
                return Integer.toString(attValue.getInteger());
            case TYPE_DOUBLE:
                return scientific.format(attValue.getDouble());
            case TYPE_BOOLEAN:
                return (attValue.getBoolean()) ? "true" : "false";
            default:
                return "Unknown typecode: "+attValue.getType();
        }
    }

    public String toString() {
        return getClass()+"["+
               "name(lcase)="+getLowerCaseName()+", "+
               "value="+getAsString()+", "+
               "showLabel="+toShowLabel(showLabel())+"]";
    }

/* Disabled for FREEHEP-386
    public boolean equals(Object o) {
        if (o == this) return true;
        if (o instanceof HepRepAttValue) {
            HepRepAttValue attValue = (HepRepAttValue)o;
            boolean r = (attValue.getLowerCaseName().equals(getLowerCaseName()) &&
                        (attValue.getType() == getType()) &&
                        (attValue.showLabel() == showLabel()));
            
            if (r) {
                switch(attValue.getType()) {
                    case TYPE_STRING:
                        r = r && (attValue.getString().equals(getString()));
                        break;
                    case TYPE_COLOR:
                        r = r && (attValue.getColor().equals(getColor()));
                        break;
                    case TYPE_LONG:
                        r = r && (attValue.getLong() == getLong());
                        break;
                    case TYPE_INT:
                        r = r && (attValue.getInteger() == getInteger());
                        break;
                    case TYPE_DOUBLE:
// FREEHEP-386
//                        r = r && (attValue.getDouble() == getDouble());
                        break;
                    case TYPE_BOOLEAN:
                        r = r && (attValue.getBoolean() == getBoolean());
                        break;
                    default:
                        r = false;
                        break;
                }
            }
            if (HepRepUtil.debug() && !r) {
                System.out.println(this+" != "+attValue);
            }
            return r;
        }
        return false;
    }

    public int hashCode() {
        long hash = getLowerCaseName().hashCode();
        hash |= getType();
        hash |= showLabel();
        switch(getType()) {
            default:
            case TYPE_STRING:
                hash |= getString().hashCode();
                break;
            case TYPE_COLOR:
                hash |= getColor().hashCode();
                break;
            case TYPE_LONG:
                hash |= getLong();
                break;
            case TYPE_INT:
                hash |= getInteger();
                break;
            case TYPE_DOUBLE:
                hash |= Double.doubleToLongBits(getDouble());
                break;
            case TYPE_BOOLEAN:
                hash |= getBoolean() ? 1 : 2;
                break;
        }
        return (int)hash;
    }
*/
}

