/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2002-2005 Julian Hyde
// Copyright (C) 2005-2011 Pentaho and others
// Copyright (C) 2006-2007 Cincom Systems, Inc.
// All Rights Reserved.
*/

package mondrian.gui;

import org.apache.log4j.Logger;

import java.lang.reflect.Field;
import java.util.List;

/**
 * @author sean
 */
public class PropertyTableModel extends javax.swing.table.AbstractTableModel {

    private static final Logger LOGGER =
        Logger.getLogger(PropertyTableModel.class);

    private Object parentTarget; // parent of target
    private String factTable;   // selected fact table
    private String factTableSchema;   // selected fact table schema

    // List of names  for this object's siblings already existing in parent'
    private List<String> names;

    private String errorMsg = null;
        // error msg when property value could not be set.

    String[] propertyNames;
    Object target;
    Workbench workbench;

    public PropertyTableModel(Workbench wb, Object t, String[] pNames) {
        super();
        workbench = wb;
        propertyNames = pNames;
        target = t;
    }

    public String getColumnName(int i) {
        if (i == 0) {
            return workbench.getResourceConverter().getString(
                "propertyTableModel.attribute", "Attribute");
        } else if (i == 1) {
            return workbench.getResourceConverter().getString(
                "propertyTableModel.value", "Value");
        }

        return workbench.getResourceConverter().getString(
            "propertyTableModel.unknown", "?");
    }

    // get property name for given row no.
    public String getRowName(int i) {
        String pName = propertyNames[i];
        int j = -1;
        if ((j = pName.indexOf('|')) != -1) {  //"|"
            return pName.substring(0, j).trim();
        } else {
            return propertyNames[i];
        }
    }

    public boolean isCellEditable(int row, int col) {
        if (col == 1) {
            Object cellObj = getValueAt(row, col);
            if (cellObj instanceof MondrianGuiDef.Join) {
                return false;
            } else {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns the number of columns in the model. A
     * <code>JTable</code> uses this method to determine how many columns it
     * should create and display by default.
     *
     * @return the number of columns in the model
     * @see #getRowCount
     */
    public int getColumnCount() {
        return 2; //that's 'Property' and 'Value'
    }

    /**
     * Returns the number of rows in the model. A
     * <code>JTable</code> uses this method to determine how many rows it
     * should display.  This method should be quick, as it
     * is called frequently during rendering.
     *
     * @return the number of rows in the model
     * @see #getColumnCount
     */
    public int getRowCount() {
        return propertyNames.length;
    }

    /**
     * Returns the value for the cell at <code>columnIndex</code> and
     * <code>rowIndex</code>.
     *
     * @param rowIndex    the row whose value is to be queried
     * @param columnIndex the column whose value is to be queried
     * @return the value Object at the specified cell
     */
    public Object getValueAt(int rowIndex, int columnIndex) {
        if (columnIndex == 0) {
            return propertyNames[rowIndex];
        } else {
            try {
                String pName = propertyNames[rowIndex];
                if ((pName.indexOf('|')) != -1) {
                    //"formula | formulaElement.cdata" This is for special cases
                    // where more than one field refers to the same value.  For
                    // eg. calculated memeber's formula and formulaelement.cdata
                    // refers to the same formula string.  These cases arise to
                    // handle xml standards where an attribute can also appear
                    // as an xml tag.
                    Object obj = null;

                    // split field names on | to form an array of property names
                    // strings that are optional.
                    String[] pNames = pName.split(
                        "\\|",
                        0);
                    for (int j = 0; j < pNames.length; j++) {
                        if ((pNames[j].indexOf('.')) != -1) {
                            // Split string on . to form an array of property
                            // name within the property name.
                            String[] pNamesField = pNames[j].trim().split(
                                "\\.",
                                0);
                            if (pNamesField.length > 1) {
                                Field f =
                                    target.getClass().getField(
                                        pNamesField[0].trim());
                                obj = f.get(target);
                                if (obj != null) {
                                    Field f2 = obj.getClass().getField(
                                        pNamesField[1].trim());
                                    Object obj2 = f2.get(obj);
                                    return obj2;
                                }
                            }
                            return null;
                        } else {
                            Field f =
                                target.getClass().getField(pNames[j].trim());
                            obj = f.get(target);
                            if (obj != null) {
                                return obj;
                            }
                        }
                    }
                    return obj;
                } else {
                    // default case where one field refers to one value.
                    Field f =
                        target.getClass().getField(propertyNames[rowIndex]);

                    Object obj = f.get(target);
                    return obj;
                }
            } catch (Exception ex) {
                LOGGER.error("getValueAt(row, index)", ex);
                return "#ERROR";
            }
        }
    }

    public void setValueAt(Object value, int rowIndex, int columnIndex) {
        setErrorMsg(null);
        try {
            String pName = propertyNames[rowIndex];
            int i = -1;
            if ((i = pName.indexOf('|')) != -1) {
                //"formula | formulaElement.cdata"
                // save value in the first field name
                Field f =
                    target.getClass().getField(
                        propertyNames[rowIndex].substring(0, i).trim());
                f.set(target, value);
                // Delete the value from second and remaining field names. split
                // field names on | to form an array of property names strings
                // that are optional.
                String[] pNames =
                    pName.split(
                        "\\|",
                        0);
                for (int j = 1; j < pNames.length; j++) {
                    // Split string on . to form an array of property name
                    // within the property name.
                    String[] pNamesField =
                        pNames[j].trim().split(
                            "\\.",
                            0);
                    Field f2 =
                        target.getClass().getField(pNamesField[0].trim());
                    f2.set(target, null);
                }

//            } else if ((target instanceof MondrianGuiDef.UserDefinedFunction)
//                && (pName.equals("script")))
//            {
//                ((MondrianGuiDef.UserDefinedFunction) target).script =
//                    (MondrianGuiDef.Script) value;
//            } else if ((target instanceof MondrianGuiDef.MemberFormatter)
//                && (pName.equals("script")))
//            {
//                ((MondrianGuiDef.MemberFormatter) target).script =
//                    (MondrianGuiDef.Script) value;
//            } else if ((target instanceof MondrianGuiDef.CellFormatter)
//                && (pName.equals("script")))
//            {
//                ((MondrianGuiDef.CellFormatter) target).script =
//                    (MondrianGuiDef.Script) value;
//            } else if ((target instanceof MondrianGuiDef.PropertyFormatter)
//                && (pName.equals("script")))
//            {
//                ((MondrianGuiDef.PropertyFormatter) target).script =
//                    (MondrianGuiDef.Script) value;

            } else if ((target instanceof MondrianGuiDef.Level)
                && (pName.equals("ordinalExp")))
            {
                ((MondrianGuiDef.Level) target).ordinalExp.expressions[0] =
                    (MondrianGuiDef.SQL) value;
            } else if ((target instanceof MondrianGuiDef.Level)
                && (pName.equals("captionExp")))
            {
                ((MondrianGuiDef.Level) target).captionExp.expressions[0] =
                    (MondrianGuiDef.SQL) value;
            } else if ((target instanceof MondrianGuiDef.Table
                        && pName.equals("name"))
                       || (target instanceof MondrianGuiDef.Hierarchy
                           && pName.equals("primaryKeyTable"))
                       || (target instanceof MondrianGuiDef.Level
                           && pName.equals("table")))
            {
                // updating all table values
                if (value != null) {
                    // split and save only if value exists
                    String[] aValues =
                        ((String) value).split(JdbcMetaData.LEVEL_SEPARATOR);
                    if (aValues.length == 2) {
                        if (target instanceof MondrianGuiDef.Table) {
                            ((MondrianGuiDef.Table) target).name = aValues[1];
                            ((MondrianGuiDef.Table) target).schema = aValues[0];
                            // to refresh the value in schema field also
                            // alongwith table name
                            fireTableDataChanged();
                        } else {
                            Field f =
                                target.getClass().getField(
                                    propertyNames[rowIndex]);
                            f.set(target, aValues[1]);
                        }
                    } else {
                        // Avoids table="" to be set on schema
                        Field f =
                            target.getClass().getField(propertyNames[rowIndex]);
                        setFieldValue(f, value);
                    }
                }

            } else if ((target instanceof MondrianGuiDef.Dimension
                        && pName.equals("foreignKey"))
                       || (target instanceof MondrianGuiDef.DimensionUsage
                           && pName.equals("foreignKey"))
                       || (target instanceof MondrianGuiDef.Measure
                           && pName.equals("column"))
                       || (target instanceof MondrianGuiDef.Hierarchy
                           && pName.equals("primaryKey"))
                       || (target instanceof MondrianGuiDef.Level
                           && pName.equals("column"))
                       || (target instanceof MondrianGuiDef.Level
                           && pName.equals("nameColumn"))
                       || (target instanceof MondrianGuiDef.Level
                           && pName.equals("ordinalColumn"))
                       || (target instanceof MondrianGuiDef.Level
                           && pName.equals("parentColumn"))
                       || (target instanceof MondrianGuiDef.Level
                           && pName.equals("captionColumn"))
                       || (target instanceof MondrianGuiDef.Closure
                           && pName.equals("parentColumn"))
                       || (target instanceof MondrianGuiDef.Closure
                           && pName.equals("childColumn"))
                       || (target instanceof MondrianGuiDef.Property
                           && pName.equals("column")))
            {
                // updating all column values
                if (value != null) {
                    // split and save only if value exists
                    String[] aValues =
                        ((String) value).split(JdbcMetaData.LEVEL_SEPARATOR);
                    Field f =
                        target.getClass().getField(propertyNames[rowIndex]);
                    // Avoids *Column="" to be set on schema.  Also remove
                    // column data type with the final split on a dash.
                    value = aValues[aValues.length - 1].split(" - ")[0];
                    setFieldValue(f, value);
                }

            } else {
                if (propertyNames[rowIndex].equals("name")
                    && (!(target instanceof MondrianGuiDef.Table))
                    && (!value.equals(
                        target.getClass().getField(
                            propertyNames[rowIndex]).get(target)))
                    && duplicateName(value))
                {
                    setErrorMsg(
                        workbench.getResourceConverter().getFormattedString(
                            "propertyTableModel.duplicateValue.error",
                            "Error setting name property. {0} already exists",
                            value.toString()));
                } else {
                    Field f =
                        target.getClass().getField(propertyNames[rowIndex]);
                    // Avoids property to be set on schema with an empty value.
                    setFieldValue(f, value);
                }
            }
        } catch (Exception ex) {
            LOGGER.error("setValueAt(aValue, row, index)", ex);
        }
    }

    private void setFieldValue(Field aField, Object aValue)
        throws IllegalAccessException
    {
        if (aValue != null && aValue.toString().trim().length() == 0) {
            aField.set(target, null);
        } else {
            aField.set(target, aValue);
        }
    }

    public Object getValue() {
        return target;
    }

    public Object getParentTarget() {
        return parentTarget;
    }

    public void setParentTarget(Object parentTarget) {
        this.parentTarget = parentTarget;
    }

    public String getFactTable() {
        return factTable;
    }

    public void setFactTable(String factTable) {
        this.factTable = factTable;
    }

    public String getFactTableSchema() {
        return factTableSchema;
    }

    public void setFactTableSchema(String factTableSchema) {
        this.factTableSchema = factTableSchema;
    }

    private boolean duplicateName(Object aValue) {
        return (names != null && names.contains(aValue));
    }

    public List<String> getNames() {
        return names;
    }

    public void setNames(List<String> names) {
        this.names = names;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

// End PropertyTableModel.java
