/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.ui.iteration.dataGrid;

import org.richfaces.cdk.annotations.JsfRenderer;
import org.richfaces.model.DataVisitResult;
import org.richfaces.renderkit.RenderKitUtils;
import org.richfaces.ui.common.HtmlConstants;
import org.richfaces.ui.common.meta.MetaComponentRenderer;
import org.richfaces.ui.iteration.AbstractRowsRenderer;
import org.richfaces.ui.iteration.EncodeStrategy;
import org.richfaces.ui.iteration.Row;
import org.richfaces.ui.iteration.RowHolder;
import org.richfaces.ui.iteration.RowHolderBase;
import org.richfaces.ui.iteration.UIDataTableBase;

import javax.faces.FacesException;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import java.io.IOException;
import java.util.Map;

/**
 * @author Anton Belevich
 *
 */
@JsfRenderer(type = "org.richfaces.ui.DataGridRenderer", family = AbstractDataGrid.COMPONENT_FAMILY)
@ResourceDependencies({ @ResourceDependency(library = "org.richfaces", name = "ajax.reslib"),
        @ResourceDependency(library = "org.richfaces", name = "base-component.reslib"),
        @ResourceDependency(library="org.richfaces", name = "richfaces-event.js"),
        @ResourceDependency(library = "org.richfaces", name = "iteration/dataGrid/datagrid.ecss") })
public class DataGridRenderer extends AbstractRowsRenderer implements MetaComponentRenderer {
    private static final EncodeStrategy THEAD = new EncodeStrategy() {
        public void begin(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params)
            throws IOException {
            String clientId = component.getClientId(context) + ":h";

            boolean partial = (Boolean) (Boolean) params[0];
            if (partial) {
                context.getPartialViewContext().getPartialResponseWriter().startUpdate(clientId);
            }

            writer.startElement(HtmlConstants.THEAD_ELEMENT, component);
            writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId, null);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-thead", null);
            writer.startElement(HtmlConstants.TR_ELEMENT, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-h", null);
            writer.startElement(HtmlConstants.TH_ELEM, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-h-c", null);

            int columns = (Integer) component.getAttributes().get("columns");
            if (columns > 0) {
                writer.writeAttribute(HtmlConstants.COLSPAN_ATTRIBUTE, columns, null);
            }
        }

        public void end(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params) throws IOException {
            writer.endElement(HtmlConstants.TH_ELEM);
            writer.endElement(HtmlConstants.TR_ELEMENT);
            writer.endElement(HtmlConstants.THEAD_ELEMENT);

            boolean partial = (Boolean) (Boolean) params[0];
            if (partial) {
                context.getPartialViewContext().getPartialResponseWriter().endUpdate();
            }
        }
    };
    private static final EncodeStrategy TFOOT = new EncodeStrategy() {
        public void begin(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params)
            throws IOException {

            String clientId = component.getClientId(context) + ":f";

            boolean partial = (Boolean) (Boolean) params[0];
            if (partial) {
                context.getPartialViewContext().getPartialResponseWriter().startUpdate(clientId);
            }

            writer.startElement(HtmlConstants.TFOOT_ELEMENT, component);
            writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId, null);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-tfoot", null);
            writer.startElement(HtmlConstants.TR_ELEMENT, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-f", null);
            writer.startElement(HtmlConstants.TD_ELEM, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-f-c", null);

            int columns = (Integer) component.getAttributes().get("columns");
            if (columns > 0) {
                writer.writeAttribute(HtmlConstants.COLSPAN_ATTRIBUTE, columns, null);
            }
        }

        public void end(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params) throws IOException {
            writer.endElement(HtmlConstants.TD_ELEM);
            writer.endElement(HtmlConstants.TR_ELEMENT);
            writer.endElement(HtmlConstants.TFOOT_ELEMENT);

            boolean partial = (Boolean) (Boolean) params[0];
            if (partial) {
                context.getPartialViewContext().getPartialResponseWriter().endUpdate();
            }
        }
    };
    private static final EncodeStrategy CAPTION = new EncodeStrategy() {
        public void begin(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params)
            throws IOException {
            writer.startElement(HtmlConstants.CAPTION_ELEMENT, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-cap", null);
        }

        public void end(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params) throws IOException {
            writer.endElement(HtmlConstants.CAPTION_ELEMENT);
        }
    };
    private static final EncodeStrategy NODATA = new EncodeStrategy() {
        public void begin(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params)
            throws IOException {
            writer.startElement(HtmlConstants.TR_ELEMENT, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-nd", null);
            writer.startElement(HtmlConstants.TD_ELEM, component);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-nd-c", null);

            int columns = (Integer) component.getAttributes().get("columns");
            if (columns > 0) {
                writer.writeAttribute(HtmlConstants.COLSPAN_ATTRIBUTE, columns, null);
            }
        }

        public void end(ResponseWriter writer, FacesContext context, UIComponent component, Object[] params) throws IOException {
            writer.endElement(HtmlConstants.TD_ELEM);
            writer.endElement(HtmlConstants.TR_ELEMENT);
        }
    };

    @Override
    public void encodeRow(ResponseWriter writer, FacesContext facesContext, RowHolderBase rowHolder) throws IOException {
        AbstractDataGrid dataGrid = (AbstractDataGrid) rowHolder.getRow();

        int columns = dataGrid.getColumns();
        int processCell = rowHolder.getProcessCell();

        if (columns > 0 && (processCell % columns == 0)) {
            if (processCell != 0) {
                writer.endElement(HtmlConstants.TR_ELEMENT);
                rowHolder.resetProcessCell();
            }
            writer.startElement(HtmlConstants.TR_ELEMENT, dataGrid);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-r", null);
            rowHolder.nextRow();
        }

        writer.startElement(HtmlConstants.TD_ELEM, dataGrid);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-c", null);
        renderChildren(facesContext, dataGrid);
        writer.endElement(HtmlConstants.TD_ELEM);
    }

    public void encodeHeader(ResponseWriter writer, FacesContext facesContext, AbstractDataGrid dataGrid, boolean partial)
        throws IOException {
        UIComponent headerFacet = dataGrid.getHeader();
        encodeFacet(writer, facesContext, headerFacet, THEAD, dataGrid, new Object[] { partial });
    }

    public void encodeFooter(ResponseWriter writer, FacesContext facesContext, AbstractDataGrid dataGrid, boolean partial)
        throws IOException {
        UIComponent footerFacet = dataGrid.getFooter();
        encodeFacet(writer, facesContext, footerFacet, TFOOT, dataGrid, new Object[] { partial });
    }

    public void encodeCaption(ResponseWriter writer, FacesContext facesContext, AbstractDataGrid dataGrid) throws IOException {
        UIComponent captionFacet = dataGrid.getCaption();
        encodeFacet(writer, facesContext, captionFacet, CAPTION, dataGrid, null);
    }

    public void encodeNoData(ResponseWriter writer, FacesContext facesContext, AbstractDataGrid dataGrid) throws IOException {
        UIComponent noDataFacet = dataGrid.getNoData();
        encodeFacet(writer, facesContext, noDataFacet, NODATA, dataGrid, null);
    }

    public void encodeFacet(ResponseWriter writer, FacesContext facesContext, UIComponent facet, EncodeStrategy strategy,
        AbstractDataGrid dataGrid, Object[] params) throws IOException {
        if (facet != null && facet.isRendered()) {
            strategy.begin(writer, facesContext, dataGrid, params);
            facet.encodeAll(facesContext);
            strategy.end(writer, facesContext, dataGrid, params);
        }
    }

    public void encodeTBody(ResponseWriter writer, FacesContext facesContext, AbstractDataGrid dataGrid, boolean partial)
        throws IOException {

        String clientId = dataGrid.getClientId(facesContext) + ":dgb";
        if (partial) {
            facesContext.getPartialViewContext().getPartialResponseWriter().startUpdate(clientId);
        }
        writer.startElement(HtmlConstants.TBODY_ELEMENT, dataGrid);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, clientId, null);
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-body", null);
        if (dataGrid.getRowCount() > 0) {
            processRows(writer, facesContext, dataGrid, null);
        } else {
            encodeNoData(writer, facesContext, dataGrid);
        }
        writer.endElement(HtmlConstants.TBODY_ELEMENT);

        if (partial) {
            facesContext.getPartialViewContext().getPartialResponseWriter().endUpdate();
        }
    }

    @Override
    protected void doEncodeChildren(ResponseWriter writer, FacesContext facesContext, UIComponent component) throws IOException {
        AbstractDataGrid dataGrid = (AbstractDataGrid) component;
        writer.startElement(HtmlConstants.TABLE_ELEMENT, dataGrid);
        writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, dataGrid.getClientId(facesContext), null);
        Map<String, Object> attributes = dataGrid.getAttributes();
        String classes = concatClasses("rf-dg", attributes.get(HtmlConstants.STYLE_CLASS_ATTR));
        writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, classes, null);
        RenderKitUtils.renderAttribute(facesContext, "style", attributes.get(HtmlConstants.STYLE_ATTRIBUTE));
        RenderKitUtils.renderAttribute(facesContext, HtmlConstants.TITLE_ATTRIBUTE,
            attributes.get(HtmlConstants.TITLE_ATTRIBUTE));
        encodeCaption(writer, facesContext, dataGrid);
        encodeHeader(writer, facesContext, dataGrid, false);
        encodeFooter(writer, facesContext, dataGrid, false);
        encodeTBody(writer, facesContext, dataGrid, false);

        writer.endElement(HtmlConstants.TABLE_ELEMENT);
    }

    @Override
    public RowHolderBase createRowHolder(FacesContext context, UIComponent component, Object[] options) {
        return new RowHolder(context, (AbstractDataGrid) component);
    }

    @Override
    protected Class<? extends UIComponent> getComponentClass() {
        return AbstractDataGrid.class;
    }

    public void encodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) throws IOException {
        AbstractDataGrid table = (AbstractDataGrid) component;

        if (UIDataTableBase.HEADER.equals(metaComponentId)) {
            encodeHeader(context.getResponseWriter(), context, table, true);
        } else if (UIDataTableBase.FOOTER.equals(metaComponentId)) {
            encodeFooter(context.getResponseWriter(), context, table, true);
        } else if (UIDataTableBase.BODY.equals(metaComponentId)) {
            encodeTBody(context.getResponseWriter(), context, table, true);
        } else {
            throw new IllegalArgumentException("Unsupported metaComponentIdentifier: " + metaComponentId);
        }
    }

    public void decodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) {
        throw new UnsupportedOperationException();
    }

    @Override
    protected void doCleanup(FacesContext context, RowHolderBase rowHolder) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        AbstractDataGrid dataGrid = (AbstractDataGrid) rowHolder.getRow();

        int cell = rowHolder.getProcessCell();
        int columns = dataGrid.getColumns();
        int rest = columns - cell;

        for (int i = 0; i < rest; i++) {
            writer.startElement(HtmlConstants.TD_ELEM, dataGrid);
            writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-dg-c", null);
            writer.endElement(HtmlConstants.TD_ELEM);
        }
        writer.endElement(HtmlConstants.TR_ELEMENT);
    }

    public DataVisitResult process(FacesContext facesContext, Object rowKey, Object argument) {
        RowHolderBase holder = (RowHolderBase) argument;
        Row row = holder.getRow();
        row.setRowKey(facesContext, rowKey);

        try {
            ResponseWriter writer = facesContext.getResponseWriter();
            encodeRow(writer, facesContext, holder);
        } catch (IOException e) {
            throw new FacesException(e);
        }

        holder.nextCell();
        return DataVisitResult.CONTINUE;
    }

    @Override
    public void encodeFakeRow(FacesContext facesContext, RowHolderBase rowHolder) throws IOException {
        UIComponent component = (UIComponent) rowHolder.getRow();
        ResponseWriter writer = facesContext.getResponseWriter();
        writer.startElement(HtmlConstants.TR_ELEMENT, component);
        writer.writeAttribute(HtmlConstants.STYLE_ATTRIBUTE, "display:none", null);
        writer.startElement(HtmlConstants.TD_ELEM, component);
        writer.endElement(HtmlConstants.TD_ELEM);
        writer.endElement(HtmlConstants.TR_ELEMENT);
    }
}
