/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.empire.struts2.jsp.tags;

import org.apache.empire.commons.StringUtils;
import org.apache.empire.data.Column;
import org.apache.empire.data.ColumnExpr;
import org.apache.empire.data.Record;
import org.apache.empire.data.RecordData;

@SuppressWarnings("serial")
public abstract class EmpireValueTagSupport extends EmpireTagSupport
{
    public static final String RECORD_ATTRIBUTE    = "record";
    public static final String BEAN_ITEM_ATTRIBUTE = "bean";
    public static final String PARENT_PROPERTY_ATTRIBUTE = "parentPropertyName";
    
    // Value
    private ColumnExpr column;
    private Object value;
    private String field;
    private String property;
    private String parentProperty;
    private RecordData record;
    
    @Override
    protected void resetParams()
    {
        // Value
        column = null;
        value = null;
        field = null;
        property = null;
        parentProperty = null;
        record = null;
        // Reset
        super.resetParams();
    }
    
    protected boolean hasValue()
    {
        return (property!=null || field!=null || value!=null);
    }
    
    protected RecordData getRecordData()
    {
        if ( record==null )
        {   // Set Record from Page Context
            Object recObj = pageContext.getAttribute(RECORD_ATTRIBUTE);
            if (recObj instanceof RecordData)
                record = (RecordData)recObj;
        }
        return record;
    }

    protected Record getRecord()
    {
        if (getRecordData() instanceof Record)
        {   // Yes, it's a Record
            if (!((Record)record).isValid())
            {   // Invalid Record 
                log.warn("The record supplied is not valid!");
                return null; 
            }
            return ((Record)record);
        }
        // Not a record
        return null;
    }
    
    protected Object getBean()
    {
        return pageContext.getAttribute(BEAN_ITEM_ATTRIBUTE);        
    }
    
    protected ColumnExpr getColumnExpr()
    {
        if (column!=null)
            return column;
        if (value instanceof ColumnExpr)
            return ((ColumnExpr)value);
        if (field!=null)
        {   // If record is not set, then try to read it from page context
            if (getRecordData()!=null)
            {   // get Column from field
                return record.getColumnExpr(record.getFieldIndex(field));
            }
        }
        return null; 
    }
    
    protected Column getColumn()
    {
        ColumnExpr column = getColumnExpr();
        if (column==null)
            return null; 
        // Get Update Column
        return column.getSourceColumn();        
    }
    
    protected String getControlType()
    {
        // Detect control type and readOnly state
        Column column = getColumn();
        if (column==null)
        {   // log.debug("No Column supplied. Unable to detect control type. Using default.");
            return "text"; 
        }
        return column.getControlType();
    }
    
    protected String getTagName(String suppliedName)
    {
        if (StringUtils.isNotEmpty(suppliedName))
            return suppliedName;
        if (property != null)
            return getFullPropertyName(property);
        if (field != null)
            return getFullPropertyName(field);
        if (value instanceof ColumnExpr)
            return getColumnPropertyName((ColumnExpr)value);
        if (column!=null)
            return getColumnPropertyName(column);
        // Not Name provided 
        log.error("Cannot detect name from value.");
        return "";
    }
    
    protected Object getValue()
    {
        if (value==null)
        {   // Try property and field first
            if (property != null)
            {   // Value from Property
                return getStack().findValue(getFullPropertyName(property), Object.class);
            }
            if (field != null)
            {
                return getRecordFieldValue(getRecordData(), field, null);            
            }
        }    
        // Get Value
        return getRecordValue(getRecordData(), value, null);
    }
    
    protected String getStringValue()
    {   // Convert value to String
        return StringUtils.toString(getValue());
    }
    
    protected String getItemValue(Object item)
    {
        if ((item instanceof String))
        {   // Item is a field or property name
            String str = item.toString();
            if (str.length()==0)
                return null; // Error: Item string is empty
            // Starts with
            char prefix = str.charAt(0);
            if (prefix=='%' || prefix=='#' || prefix=='$')
                return getString(str, null);
            if (prefix=='!')
                return ((String)item).substring(1);
            if (prefix<'A')
            {   log.error("Invalid property or field name supplied for item.");
                return null;
            }
            // Item is property or field name 
            if (property != null)
            {
                String fullName = getFullPropertyName(str);
                return (String)getStack().findValue(fullName, String.class);
            }
            else
            {
                return StringUtils.toString(getRecordFieldValue(record, str, null));            
            }
        }
        // Default
        return StringUtils.toString(getRecordValue(record, item, null));
    }
    
    protected String getPropertyFieldName()
    {
        if (property != null)
            return getFullPropertyName(property);
        if (field != null)
            return getFullPropertyName(field);        
        return null;
    }
    
    protected String getFullPropertyName(String name)
    {
        // Get name from column
        if ("*".equals(name) && column!=null)
            name = column.getBeanPropertyName();
        // Prepend parent Property name (if any)
        if (parentProperty==null)
            parentProperty = StringUtils.toString(getPageAttribute(PARENT_PROPERTY_ATTRIBUTE, null));
        if (parentProperty!=null)
            return parentProperty + "." + name;
        // return the name
        return name;
    }
    
    protected String getColumnPropertyName(ColumnExpr col)
    {
        return getFullPropertyName(col.getName());
    }
    
    protected boolean setPropertyNameFromValue()
    {
        if (property==null && (value instanceof String))
        {
            String strval = ((String)value);
            if (strval.startsWith("%{") && strval.endsWith("}"))
            { // It's a property Name
                property = strval.substring(2, strval.length() - 1).trim();
                return true;
            }
        }
        return false;
    }
    
    protected boolean hasDefaultValue()
    {
        return (value!=null && (property!=null || field!=null || column!=null));
    }
    
    protected Object getDefaultValue()
    {
        // Try property and field first
        if (property != null)
        {   // Value from Property
            return getStack().findValue(getFullPropertyName(property), Object.class);
        }
        if (field != null)
        {
            return getRecordFieldValue(getRecordData(), field, null);            
        }
        // Get Value
        return getRecordValue(getRecordData(), null, null);
    }

    // ------- Internal -------
    
    private Object getRecordFieldValue(RecordData rec, String field, Object defValue)
    {
        // Field Param must be supplied
        if (field==null)
            return defValue;
        // If record is not set, then try to read it from page context
        if (rec!=null)
        {   // Find field by name
            int index = rec.getFieldIndex(field);
            if (index>= 0)
                return rec.getValue(index);
            // Field not found 
            log.error("Supplied field '" + field + "' not found in record.");
        }
        else
        {   // Cannot find data source (record or bean)  
            log.error("No record supplied for field value");
        }
        return defValue;
    }
    
    private Object getRecordValue(RecordData rec, Object value, Object defValue)
    {
        // Find Record Value
        if (value==null && column!=null)
            value = column;
        // Find Record Value
        if (value instanceof ColumnExpr)
        {
            ColumnExpr column = ((ColumnExpr)value);
            if (rec!=null)
            {   // Find column by object first
                int index = rec.getFieldIndex(column);
                if (index<0)
                {   // Column not found. Trying name
                    log.debug("Column object '" + column.getName() + "' not found. Trying name.");
                    index = rec.getFieldIndex( column.getName());
                    if (index<0)
                    {   // Column not found  
                        log.error("Column '" + column.getName() + "' not found in record.");
                        return null;
                    }
                }
                // Get value from record
                return rec.getValue(index);
            }
            else
            {   // Check if Columns specifies a bean property
                Object bean = getBean();
                if (bean!=null)
                {   // Property Name
                    String prop = column.getBeanPropertyName();
                    return getBeanProperty(bean, prop);
                }
            }
            // Cannot find data source (record or bean)  
            // log.warn("No record supplied for column value");
            return defValue;
        }
        // getValue 
        return this.getObject(value, defValue);
    }

    // -------------------------------- Property accessors -----------------------------
    
    public final void setField(String field)
    {
        this.field = StringUtils.validate(field);
    }

    public final void setProperty(String property)
    {
        this.property = StringUtils.validate(property);
    }

    public final void setParentProperty(String property)
    {
        this.parentProperty = StringUtils.validate(property);
    }

    public final void setRecord(RecordData record)
    {
        this.record = record;
    }

    public final void setValue(Object value)
    {
        this.value = value;
    }

    public final void setColumn(ColumnExpr column)
    {
        this.column = column;
    }
    
}
