/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LibData.java
 * Written by Jonathan Gainsley, Sun Microsystems.
 *
 * 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.simulation.sctiming;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.io.PrintStream;
import java.io.FileOutputStream;


/**
 * LibertyObjects.java
 * Jonathan Gainsley
 * 24 Oct 2006
 *
 * Objects to store parsed data from LibertyParser
 */

public class LibData
{
    public Group library;

    private static final int MAX_LINE_LENGTH = 80; // when writing to disk

    /**
     * Create a new LibData object to hold Liberty format data.
     */
    public LibData() {}

    /**
     * Write the data stored in this object to a Liberty format file.
     * @param filename the file to write to
     */
    public void write(String filename) {
        try {
            write(new PrintStream(new FileOutputStream(filename)));
        } catch (java.io.FileNotFoundException e) {
            System.out.println("LibData: Error writing to file "+filename+": "+e.getMessage());
        }
    }

    /**
     * Write the data stored in this object to a Liberty format file.
     * @param out the output stream to write to
     */
    public void write(PrintStream out) {
        if (library == null) {
            System.out.println("LibData: No Data to write");
            return;
        }
        library.write(out, 0);
    }

    /**
     * Get the library from the liberty file read.
     * Note that there is only one library per file.
     * @return the library data
     */
    public Group getLibrary() {
        return library;
    }

    /**
     * Set the library for the liberty file.
     * Note that there is only one library per file.
     */
    public void setLibrary(Group group) {
        this.library = group;
    }

    // ---------------------------------------------------------
    // Liberty Object Definitions
    // ---------------------------------------------------------

    /**
     * Head object
     */
    public static class Head {
        public String name;
        int lineno;
        String filename;
        List<Value> valuesList;

        public Head(String name, List attrList) {
            this.name = name;
            valuesList = new ArrayList<Value>();
            for (Object obj : attrList) {
                if (obj instanceof Value)
                    valuesList.add((Value)obj);
            }
        }

        public List<Value> getValues() { return valuesList; }
    }

    /**
     * Group object
     */
    public static class Group {
        private String type;
        private List<String> names;
        private Group owner;

        Map<String,Attribute> attrList;
        Map<String,Define> defineList;
        List<Group> groupList;  // multiple groups of the same type and no name may exist

        public Group(String type, String name, Group owner) {
            this.type = type;
            this.names = new ArrayList<String>();
            if (name != null)
                this.names.add(name);
            this.owner = owner;
            this.attrList = new LinkedHashMap<String,Attribute>();
            this.defineList = new LinkedHashMap<String,Define>();
            this.groupList = new ArrayList<Group>();
            if (owner != null)
                owner.putGroup(this);
        }

        public Group(Head h, Group owner) {
            this.type = h.name;
            this.names = new ArrayList<String>();
            this.owner = owner;
            this.attrList = new LinkedHashMap<String,Attribute>();
            this.defineList = new LinkedHashMap<String,Define>();
            this.groupList = new ArrayList<Group>();
            for (Value a : h.getValues()) {
                names.add(a.value.toString());
            }
            if (owner != null)
                owner.putGroup(this);
        }

        public String getType() { return type; }
        public String getName() { if (names.size() > 0) return names.get(0); return null; }
        public List<String> getNames() { return names; }
        public Group getOwner() { return owner; }
        public String getHName() {
            String parentName = owner == null ? "" : owner.getHName()+".";
            String thisname = type + (names.size() == 0 ? "" : "-"+names.get(0));
            return parentName + thisname;
        }

        public void putAttribute(Attribute a) {
            if (attrList.containsKey(a.name)) {
                System.out.println("Duplicate Attribute name "+a.name+" in group "+getHName());
            }
            attrList.put(a.name, a);
        }

        public void putGroup(Group g) {
            groupList.add(g);
        }

        public void putDefine(Define d) {
            if (defineList.containsKey(d.name)) {
                System.out.println("Duplicate Define name "+d.name+" in group "+getHName());
            }
            defineList.put(d.name, d);
        }

        public Attribute getAttribute(String name) {
            return attrList.get(name);
        }

        public void putAttribute(String name, double value) {
            Value val = new Value(ValueType.DOUBLE, new Double(value));
            putAttribute(new Attribute(name, val));
        }

        public void putAttribute(String name, String value) {
            Value val = new Value(ValueType.STRING, value);
            putAttribute(new Attribute(name, val));
        }

        public void putAttribute(String name, int value) {
            Value val = new Value(ValueType.INT, new Integer(value));
            putAttribute(new Attribute(name, val));
        }

        public void putAttribute(String name, List<String> values) {
            List<Value> vals = new ArrayList<Value>();
            for (String s : values) vals.add(new Value(ValueType.STRING, s));
            Head h = new Head(name, vals);
            putAttribute(new Attribute(name, h));
        }

        public void putAttributeComplex(String name, String value) {
            List<String> vals = new ArrayList<String>();
            vals.add(value);
            putAttribute(name, vals);
        }

        /**
         * Get all groups in this group
         * @return all groups in this group
         */
        public List<Group> getGroups() {
            return groupList;
        }

        /**
         * Get the group of the given type and name,
         * in this group.  Name can be null.
         * More than one group may exist; all matching
         * the criteria are returned.
         * @param type the group type: type(name) {}
         * @param name the group name
         * @return any group in this group matching the criteria
         */
        public List<Group> getGroups(String type, String name) {
            List<Group> groups = new ArrayList<Group>();
            if (type == null) return groups;
            for (Group g : groupList) {
                if (!type.equals(g.type)) continue;
                if (name == null && g.names.size() == 0) {
                    groups.add(g); continue;
                }
                if (name != null && g.names.contains(name)) {
                    groups.add(g);
                }
            }
            return groups;
        }

        public Define getDefine(String name) {
            return defineList.get(name);
        }

        public void removeGroup(Group group) {
            for (Group g : groupList) {
                if (g == group) {
                    groupList.remove(g);
                    return;
                }
            }
        }

        private void write(PrintStream out, int tabs) {
            prtabs(out, tabs);
            out.print(type+" (");
            for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
                out.print(it.next());
                if (it.hasNext()) out.print(", ");
            }
            out.println(") {");
            tabs++;
            for (Attribute attr : attrList.values()) {
                attr.write(out, tabs);
            }
            for (Define define : defineList.values()) {
                define.write(out, tabs);
            }
            for (Group group : groupList) {
                group.write(out, tabs);
            }
            tabs--;
            prtabs(out, tabs);
            out.println("}");
        }
    }

    /**
     * Attribute Types
     */
    public enum AttrType { SIMPLE, COMPLEX }

    /**
     * Attribute object
     */
    public static class Attribute {
        AttrType type;
        String name;
        List<Value> values;

        public Attribute(String name, Value value) {
            this.name = name;
            this.type = AttrType.SIMPLE;
            this.values = new ArrayList<Value>();
            values.add(value);
        }

        public Attribute(String name, Head complexAttributeValues) {
            this.name = name;
            this.type = AttrType.COMPLEX;
            this.values = complexAttributeValues.getValues();
        }

        public AttrType getType() { return type; }
        public String getName() { return name; }
        public List<Value> getValues() { return values; }

        /** Get the integer value. Returns null if not an integer */
        public Integer getInt() {
            if (values.size() == 0) return null;
            return values.get(0).getInt();
        }

        /** Get the double value. Returns null if not a double */
        public Double getDouble() {
            if (values.size() == 0) return null;
            return values.get(0).getDouble();
        }

        /** Get the String value. Returns null if not a String */
        public String getString() {
            if (values.size() == 0) return null;
            return values.get(0).getString();
        }

        /** Get the Boolean value. Returns null if not a Boolean */
        public Boolean getBoolean() {
            if (values.size() == 0) return null;
            return values.get(0).getBoolean();
        }

        private void write(PrintStream out, int tabs) {
            prtabs(out, tabs);
            out.print(name);
            int i = tabs*2 + name.length();
            switch(type) {
                case SIMPLE: out.print(" : "); i+=3; break;
                case COMPLEX: out.print(" ("); i+=2; break;
            }
            int start = i;
            for (Iterator<Value> it = values.iterator(); it.hasNext(); ) {
                Value v = it.next();
                String s = v.getValueAsString();
                if ((i+s.length()) > MAX_LINE_LENGTH) {
                    out.println(" \\");
                    i=start;
                    prtabs(out, start/2);
                }
                out.print(s);
                i += s.length();
                if (it.hasNext()) { out.print(", "); i+=2; }
            }
            if (type == AttrType.COMPLEX) out.print(")");
            out.println(" ;");
        }
    }

    /**
     * Value types
     */
    public enum ValueType { STRING, DOUBLE, BOOLEAN, INT, EXPR, UNDEFINED }

    /**
     * Value objects
     */
    public static class Value {
        ValueType type;
        Object value;

        public Value(ValueType type, Object value) {
            this.type = type;
            this.value = value;
            if (value instanceof String) {
                String s = (String)value;
                if (s.startsWith("\"") && s.endsWith("\"")) {
                    s = s.substring(1, s.length()-1);   // strip quotes
                    this.value = s;
                }
            }
        }

        public ValueType getType() { return type; }
        public Object getValue() { return value; }

        public static ValueType getType(String s) {
            if (s == null) return ValueType.UNDEFINED;
            try {
                return ValueType.valueOf(s.trim().toUpperCase());
            } catch (IllegalArgumentException e) {
                return ValueType.UNDEFINED;
            }
        }

        /** Get the integer value. Returns null if not an integer */
        public Integer getInt() {
            if (value == null || type != ValueType.DOUBLE) return null;
            return new Integer(((Double)value).intValue());
        }

        /** Get the double value. Returns null if not a double */
        public Double getDouble() {
            if (value == null || type != ValueType.DOUBLE) return null;
            return (Double)value;
        }

        /** Get the String value. Returns null if not a String */
        public String getString() {
            if (value == null || type != ValueType.STRING) return null;
            return (String)value;
        }

        /** Get the Boolean value. Returns null if not a Boolean */
        public Boolean getBoolean() {
            if (value == null || type != ValueType.BOOLEAN) return null;
            return (Boolean)value;
        }

        private String getValueAsString() {
            String s = value.toString().trim();
            if (s.indexOf(' ') != -1 || s.indexOf('/') != -1 || s.indexOf('\\') != -1 || s.indexOf("(") != -1) {
                if (!s.startsWith("\"") && !s.endsWith("\""))
                    return "\""+s+"\"";
            }
            return s;
        }
    }

    /**
     * Definition objects
     */
    public static class Define {
        String name;
        String groupType;
        ValueType valueType;
        String comment;
        Group owner;

        Define(String name, Group owner, ValueType valueType) {
            this.name = name;
            this.owner = owner;
            this.valueType = valueType;
            if (owner != null)
                owner.putDefine(this);
        }

        private void write(PrintStream out, int tabs) {

        }
    }

    /**
     * Error types
     */
    public enum Error {
        NO_ERROR,
        INTERNAL_SYSTEM_ERROR,
        INVALID_VALUE,
        INVALID_NAME,
        INVALID_OBJECTTYPE,
        INVALID_ATTRTYPE,
        UNUSABLE_OID,
        OBJECT_ALREADY_EXISTS,
        OBJECT_NOT_FOUND,
        SYNTAX_ERROR,
        TRACE_FILES_CANNOT_BE_OPENED,
        PIINIT_NOT_CALLED,
        SEMANTIC_ERROR,
        REFERENCE_ERROR,
        MAX_ERROR
    }

    /**
     * Get error string for the given type
     * @param error
     * @return the error string
     */
    public static String getMessage(Error error) {
        switch(error) {
            case NO_ERROR: return "";
            case INTERNAL_SYSTEM_ERROR: return "Internal System Error";
            case INVALID_VALUE: return "Invalid Value";
            case INVALID_NAME: return "Invalid Name";
            case INVALID_OBJECTTYPE: return "Invalid Object Type";
            case INVALID_ATTRTYPE: return "Invalid Attribute Type";
            case UNUSABLE_OID: return "Unusable Object Identifier?";
            case OBJECT_ALREADY_EXISTS: return "Object Already Exists";
            case OBJECT_NOT_FOUND: return "Object Not Found";
            case SYNTAX_ERROR: return "Syntax Error";
            case TRACE_FILES_CANNOT_BE_OPENED: return "Trace Files Cannot be Opened";
            case PIINIT_NOT_CALLED: return "PI init() not called";
            case SEMANTIC_ERROR: return "Semantic Error";
            case REFERENCE_ERROR: return "Reference Error";
            case MAX_ERROR: return "Maximum Error";
        }
        return "Unknown Error Number: "+error;
    }

    private static void prtabs(PrintStream out, int tabs) {
        for (int i=0; i<tabs; i++) out.print("  ");
    }
}
