/*
@ITMillApache2LicenseForJavaFiles@
 */

package com.vaadin.terminal.gwt.client.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;

/**
 * TODO make this work (just an early prototype). We may want to have paging
 * style table which will be much lighter than VScrollTable is.
 */
public class VTablePaging extends Composite implements Table, Paintable,
        ClickHandler {

    private final Grid tBody = new Grid();
    private final Button nextPage = new Button("&gt;");
    private final Button prevPage = new Button("&lt;");
    private final Button firstPage = new Button("&lt;&lt;");
    private final Button lastPage = new Button("&gt;&gt;");

    private int pageLength = 15;

    private boolean rowHeaders = false;

    private ApplicationConnection client;
    private String id;

    private boolean immediate = false;

    private int selectMode = Table.SELECT_MODE_NONE;

    private final ArrayList<String> selectedRowKeys = new ArrayList<String>();

    private int totalRows;

    private final HashMap<?, ?> visibleColumns = new HashMap<Object, Object>();

    private int rows;

    private int firstRow;
    private boolean sortAscending = true;
    private final HorizontalPanel pager;

    public HashMap<String, TableRow> rowKeysToTableRows = new HashMap<String, TableRow>();

    public VTablePaging() {

        tBody.setStyleName("itable-tbody");

        final VerticalPanel panel = new VerticalPanel();

        pager = new HorizontalPanel();
        pager.add(firstPage);
        firstPage.addClickHandler(this);
        pager.add(prevPage);
        prevPage.addClickHandler(this);
        pager.add(nextPage);
        nextPage.addClickHandler(this);
        pager.add(lastPage);
        lastPage.addClickHandler(this);

        panel.add(pager);
        panel.add(tBody);

        initWidget(panel);
    }

    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
        if (client.updateComponent(this, uidl, true)) {
            return;
        }

        this.client = client;
        id = uidl.getStringAttribute("id");
        immediate = uidl.getBooleanAttribute("immediate");
        totalRows = uidl.getIntAttribute("totalrows");
        pageLength = uidl.getIntAttribute("pagelength");
        firstRow = uidl.getIntAttribute("firstrow");
        rows = uidl.getIntAttribute("rows");

        if (uidl.hasAttribute("selectmode")) {
            if (uidl.getStringAttribute("selectmode").equals("multi")) {
                selectMode = Table.SELECT_MODE_MULTI;
            } else {
                selectMode = Table.SELECT_MODE_SINGLE;
            }

            if (uidl.hasAttribute("selected")) {
                final Set<String> selectedKeys = uidl
                        .getStringArrayVariableAsSet("selected");
                selectedRowKeys.clear();
                for (final Iterator<String> it = selectedKeys.iterator(); it
                        .hasNext();) {
                    selectedRowKeys.add(it.next());
                }
            }
        }

        if (uidl.hasVariable("sortascending")) {
            sortAscending = uidl.getBooleanVariable("sortascending");
        }

        if (uidl.hasAttribute("rowheaders")) {
            rowHeaders = true;
        }

        UIDL rowData = null;
        UIDL visibleColumns = null;
        for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
            final UIDL c = (UIDL) it.next();
            if (c.getTag().equals("rows")) {
                rowData = c;
            } else if (c.getTag().equals("actions")) {
                updateActionMap(c);
            } else if (c.getTag().equals("visiblecolumns")) {
                visibleColumns = c;
            }
        }
        tBody.resize(rows + 1, uidl.getIntAttribute("cols")
                + (rowHeaders ? 1 : 0));
        updateHeader(visibleColumns);
        updateBody(rowData);

        updatePager();
    }

    private void updateHeader(UIDL c) {
        final Iterator<?> it = c.getChildIterator();
        visibleColumns.clear();
        int colIndex = (rowHeaders ? 1 : 0);
        while (it.hasNext()) {
            final UIDL col = (UIDL) it.next();
            final String cid = col.getStringAttribute("cid");
            if (!col.hasAttribute("collapsed")) {
                tBody.setWidget(0, colIndex,
                        new HeaderCell(cid, col.getStringAttribute("caption")));

            }
            colIndex++;
        }
    }

    private void updateActionMap(UIDL c) {
        // TODO Auto-generated method stub

    }

    /**
     * Updates row data from uidl. UpdateFromUIDL delegates updating tBody to
     * this method.
     * 
     * Updates may be to different part of tBody, depending on update type. It
     * can be initial row data, scroll up, scroll down...
     * 
     * @param uidl
     *            which contains row data
     */
    private void updateBody(UIDL uidl) {
        final Iterator<?> it = uidl.getChildIterator();

        int curRowIndex = 1;
        while (it.hasNext()) {
            final UIDL rowUidl = (UIDL) it.next();
            final TableRow row = new TableRow(curRowIndex,
                    String.valueOf(rowUidl.getIntAttribute("key")),
                    rowUidl.hasAttribute("selected"));
            int colIndex = 0;
            if (rowHeaders) {
                tBody.setWidget(curRowIndex, colIndex, new BodyCell(row,
                        rowUidl.getStringAttribute("caption")));
                colIndex++;
            }
            final Iterator<?> cells = rowUidl.getChildIterator();
            while (cells.hasNext()) {
                final Object cell = cells.next();
                if (cell instanceof String) {
                    tBody.setWidget(curRowIndex, colIndex, new BodyCell(row,
                            (String) cell));
                } else {
                    final Paintable cellContent = client
                            .getPaintable((UIDL) cell);
                    final BodyCell bodyCell = new BodyCell(row);
                    bodyCell.setWidget((Widget) cellContent);
                    tBody.setWidget(curRowIndex, colIndex, bodyCell);
                }
                colIndex++;
            }
            curRowIndex++;
        }
    }

    private void updatePager() {
        if (pageLength == 0) {
            pager.setVisible(false);
            return;
        }
        if (isFirstPage()) {
            firstPage.setEnabled(false);
            prevPage.setEnabled(false);
        } else {
            firstPage.setEnabled(true);
            prevPage.setEnabled(true);
        }
        if (hasNextPage()) {
            nextPage.setEnabled(true);
            lastPage.setEnabled(true);
        } else {
            nextPage.setEnabled(false);
            lastPage.setEnabled(false);

        }
    }

    private boolean hasNextPage() {
        if (firstRow + rows + 1 > totalRows) {
            return false;
        }
        return true;
    }

    private boolean isFirstPage() {
        if (firstRow == 0) {
            return true;
        }
        return false;
    }

    public void onClick(ClickEvent event) {
        Object sender = event.getSource();
        if (sender instanceof Button) {
            if (sender == firstPage) {
                client.updateVariable(id, "firstvisible", 0, true);
            } else if (sender == nextPage) {
                client.updateVariable(id, "firstvisible",
                        firstRow + pageLength, true);
            } else if (sender == prevPage) {
                int newFirst = firstRow - pageLength;
                if (newFirst < 0) {
                    newFirst = 0;
                }
                client.updateVariable(id, "firstvisible", newFirst, true);
            } else if (sender == lastPage) {
                client.updateVariable(id, "firstvisible", totalRows
                        - pageLength, true);
            }
        }
        if (sender instanceof HeaderCell) {
            final HeaderCell hCell = (HeaderCell) sender;
            client.updateVariable(id, "sortcolumn", hCell.getCid(), false);
            client.updateVariable(id, "sortascending", (sortAscending ? false
                    : true), true);
        }
    }

    private class HeaderCell extends HTML {

        private String cid;

        public String getCid() {
            return cid;
        }

        public void setCid(String pid) {
            cid = pid;
        }

        HeaderCell(String pid, String caption) {
            super();
            cid = pid;
            addClickHandler(VTablePaging.this);
            setText(caption);
            // TODO remove debug color
            DOM.setStyleAttribute(getElement(), "color", "brown");
            DOM.setStyleAttribute(getElement(), "font-weight", "bold");
        }
    }

    /**
     * Abstraction of table cell content. In needs to know on which row it is in
     * case of context click.
     * 
     * @author mattitahvonen
     */
    public class BodyCell extends SimplePanel {
        private final TableRow row;

        public BodyCell(TableRow row) {
            super();
            sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT);
            this.row = row;
        }

        public BodyCell(TableRow row2, String textContent) {
            super();
            sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT);
            row = row2;
            setWidget(new Label(textContent));
        }

        @Override
        public void onBrowserEvent(Event event) {
            System.out.println("CEll event: " + event.toString());
            switch (DOM.eventGetType(event)) {
            case Event.BUTTON_RIGHT:
                row.showContextMenu(event);
                Window.alert("context menu un-implemented");
                DOM.eventCancelBubble(event, true);
                break;
            case Event.BUTTON_LEFT:
                if (selectMode > Table.SELECT_MODE_NONE) {
                    row.toggleSelected();
                }
                break;
            default:
                break;
            }
            super.onBrowserEvent(event);
        }
    }

    private class TableRow {

        private final String key;
        private final int rowIndex;
        private boolean selected = false;

        public TableRow(int rowIndex, String rowKey, boolean selected) {
            rowKeysToTableRows.put(rowKey, this);
            this.rowIndex = rowIndex;
            key = rowKey;
            setSelected(selected);
        }

        /**
         * This method is used to set row status. Does not change value on
         * server.
         * 
         * @param selected
         */
        public void setSelected(boolean sel) {
            selected = sel;
            if (selected) {
                selectedRowKeys.add(key);
                DOM.setStyleAttribute(
                        tBody.getRowFormatter().getElement(rowIndex),
                        "background", "yellow");

            } else {
                selectedRowKeys.remove(key);
                DOM.setStyleAttribute(
                        tBody.getRowFormatter().getElement(rowIndex),
                        "background", "transparent");
            }
        }

        public void setContextMenuOptions(HashMap<?, ?> options) {

        }

        /**
         * Toggles rows select state. Also updates state to server according to
         * tables immediate flag.
         * 
         */
        public void toggleSelected() {
            if (selected) {
                setSelected(false);
            } else {
                if (selectMode == Table.SELECT_MODE_SINGLE) {
                    deselectAll();
                }
                setSelected(true);
            }
            client.updateVariable(
                    id,
                    "selected",
                    selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
                    immediate);
        }

        /**
         * Shows context menu for this row.
         * 
         * @param event
         *            Event which triggered context menu. Correct place for
         *            context menu can be determined with it.
         */
        public void showContextMenu(Event event) {
            System.out.println("TODO: Show context menu");
        }
    }

    public void deselectAll() {
        final Object[] keys = selectedRowKeys.toArray();
        for (int i = 0; i < keys.length; i++) {
            final TableRow tableRow = rowKeysToTableRows.get(keys[i]);
            if (tableRow != null) {
                tableRow.setSelected(false);
            }
        }
        // still ensure all selects are removed from
        selectedRowKeys.clear();
    }

    public void add(Widget w) {
        // TODO Auto-generated method stub

    }

    public void clear() {
        // TODO Auto-generated method stub

    }

    public Iterator<Widget> iterator() {
        // TODO Auto-generated method stub
        return null;
    }

    public boolean remove(Widget w) {
        // TODO Auto-generated method stub
        return false;
    }
}
