/*
 * RHQ Management Platform
 * Copyright (C) 2009-2010 Red Hat, Inc.
 * All rights reserved.
 *
 * This program 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 version 2 of the License.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.core.gui.table.component;

import org.ajax4jsf.component.UIDataAdaptor;
import org.rhq.core.gui.util.FacesComponentUtility;
import org.rhq.core.gui.util.FacesExpressionUtility;

import javax.el.ValueExpression;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIData;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;

/**
 * An HTML component that allows rows in data tables (i.e. h:dataTables or rich:*dataTables) to be selected via either
 * checkboxes (if in multi-select mode) or via radio buttons (if in single-select mode).
 *
 * NOTE: It is preferred to use the disabled attribute of this component instead of the rendered attribute. This will
 * provide consistency to the rendered table such as regardless of what items appear in the table, the check-able
 * column will always be rendered with a consistent width.
 *
 * @author Ian Springer
 */
public class RowSelectorComponent extends UIInput {
    public static final String COMPONENT_TYPE = "org.rhq.RowSelector";
    public static final String COMPONENT_FAMILY = "org.rhq.RowSelector";

    private static final Mode DEFAULT_MODE = Mode.multi;

    private Mode mode;

    @Override
    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    public Mode getMode() {
        if (this.mode != null) {
            return this.mode;
        }
        Mode mode = (Mode) getBinding("mode");
        this.mode = (mode != null) ? mode : DEFAULT_MODE;
        return this.mode;
    }

    public void setMode(Mode mode) {
        this.mode = mode;
    }
    
    @Override
    public Object getValue() {
        if (getValueExpression("value") == null) {
            throw new IllegalStateException("rowSelector 'value' attribute must be set to an EL expression.");
        }
        return super.getValue();
    }

    /**
     * By default, getClientId() would return something like
     * "myForm:myDataTable:10001:myRowSelector", where "myForm:myDataTable:10001" is the client id of our enclosing
     * data table, and "10001" is the index of the current row. For each row, we want ourselves to be rendered as a
     * HTML checkbox with the *same* "name" attribute as the checkboxes for the other rows, so it is easier to
     * obtain the set of checkboxes via JavaScript. Since the client id is used as the checkbox "name" attribute, we
     * need to make sure to return a client id that does not include the rowIndex
     * (e.g. "myForm:myDataTable:myRowSelector". Isn't JSF fun?
     * 
     * @param context the JSF context
     * @return our client id
     */
    @Override
    public String getClientId(FacesContext context) {
        // Get our enclosing data table.
        UIData data = FacesComponentUtility.getAncestorOfType(this, UIData.class);

        String baseClientId;        
        if (data instanceof UIDataAdaptor) {
            // It's a RichFaces data table, which conveniently provides a public method that returns its base client id.
            UIDataAdaptor dataAdaptor = (UIDataAdaptor) data;
            baseClientId = dataAdaptor.getBaseClientId(context);            
        } else {
            // It's not a RichFaces data table.
            int originalRowIndex = data.getRowIndex();
            // Temporarily set its row index to -1 to force it to return a client id for itself that doesn't include the
            // row index.
            data.setRowIndex(-1);
            baseClientId = data.getClientId(context);
            data.setRowIndex(originalRowIndex);
        }

        // Now append our id.
        String id = getId();
        if (id == null) {
            id = context.getViewRoot().createUniqueId();
            setId(id);
        }
        return baseClientId + NamingContainer.SEPARATOR_CHAR + id; 
    }

    @Override
    public Object saveState(FacesContext context) {
        Object[] values = new Object[2];
        values[0] = super.saveState(context);
        values[1] = this.mode;
        return values;
    }

    @Override
    public void restoreState(FacesContext context, Object state) {
        Object[] values = (Object[]) state;
        super.restoreState(context, values[0]);
        this.mode = (Mode) values[1];
    }

    public Object getBinding(String attr) {
        if (attr == null) {
            throw new NullPointerException("passed attribute is null");
        }

        ValueExpression valueExpression = this.getValueExpression(attr);
        @SuppressWarnings({"UnnecessaryLocalVariable"})
        Object attribValue = (valueExpression != null) ? FacesExpressionUtility.getValue(valueExpression, Object.class)
            : null;
        return attribValue;
    }

    public enum Mode {
        /**
         * Only one row can be selected at a time.
         */
        single,
        /**
         * Multiple rows can be selected simultaneously.
         */
        multi
    }
}
