package autotest.common.table;

import autotest.common.DomUtils;
import autotest.common.ui.RightClickTable;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HTMLTable;

import java.util.ArrayList;
import java.util.List;

/**
 * Customized table class supporting multiple tbody elements.  It is modified to support input
 * handling, getRowCount(), getCellCount(), and getCellFormatter().getElement().  getElement() 
 * also works.  Calls to other methods aren't guaranteed to work.
 */
public class FragmentedTable extends RightClickTable {
    public class FragmentedCellFormatter extends HTMLTable.CellFormatter {
        @Override
        public Element getElement(int row, int column) {
            checkCellBounds(row, column);
            Element bodyElem = bodyElems.get(getFragmentIndex(row));
            return getCellElement(bodyElem, getRowWithinFragment(row), column);
        }
        
        /**
         * Native method to efficiently get a td element from a tbody. Copied from GWT's 
         * HTMLTable.java. 
         */
        private native Element getCellElement(Element tbody, int row, int col) /*-{
            return tbody.rows[row].cells[col];
        }-*/;
    }
    
    private List<Element> bodyElems = new ArrayList<Element>();
    private int totalRowCount;
    private int rowsPerFragment;
    
    public FragmentedTable() {
        super();
        setCellFormatter(new FragmentedCellFormatter());
        
        // Reset the FragmentedTable to clear out elements that were added by the HTMLTable and
        // FlexTable constructors
        reset();
    }
    
    /**
     * This method must be called after added or removing tbody elements and before using other
     * functionality (accessing cell elements, input handling, etc.).
     */
    public void updateBodyElems() {
        totalRowCount = 0;
        Element tbody = DOM.getFirstChild(getElement());
        for(; tbody != null; tbody = DOM.getNextSibling(tbody)) {
            assert tbody.getTagName().equalsIgnoreCase("tbody");
            bodyElems.add(tbody);
            totalRowCount += getRowCount(tbody);
        }
    }

    public void reset() {
        bodyElems.clear();
        DomUtils.clearDomChildren(getElement());
    }

    private int getRowWithinFragment(int row) {
        return row % rowsPerFragment;
    }

    private int getFragmentIndex(int row) {
        return row / rowsPerFragment;
    }
    
    @Override
    public HTMLTable.Cell getCellForEvent(ClickEvent event) {
        return getCellForDomEvent(event);
    }
    
    @Override
    protected RowColumn getCellPosition(Element td) {
        Element tr = DOM.getParent(td);
        Element body = DOM.getParent(tr);
        int fragmentIndex = DOM.getChildIndex(getElement(), body);
        int rowWithinFragment = DOM.getChildIndex(body, tr);
        int row = fragmentIndex * rowsPerFragment + rowWithinFragment;
        int column = DOM.getChildIndex(tr, td);
        return new RowColumn(row, column);
    }

    /**
     * This is a modified version of getEventTargetCell() from HTMLTable.java.
     */
    @Override
    protected Element getEventTargetCell(Event event) {
        Element td = DOM.eventGetTarget(event);
        for (; td != null; td = DOM.getParent(td)) {
            // If it's a TD, it might be the one we're looking for.
            if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
                // Make sure it's directly a part of this table before returning
                // it.
                Element tr = DOM.getParent(td);
                Element body = DOM.getParent(tr);
                Element tableElem = DOM.getParent(body);
                if (tableElem == getElement()) {
                    return td;
                }
            }
            // If we run into this table's element, we're out of options.
            if (td == getElement()) {
                return null;
            }
        }
        return null;
    }

    @Override
    public int getCellCount(int row) {
        Element bodyElem = bodyElems.get(getFragmentIndex(row));
        return getCellCount(bodyElem, getRowWithinFragment(row));
    }

    @Override
    public int getRowCount() {
        return totalRowCount;
    }
    
    private native int getRowCount(Element tbody) /*-{
        return tbody.rows.length;
    }-*/;

    private native int getCellCount(Element tbody, int row) /*-{
        return tbody.rows[row].cells.length;
    }-*/;

    /**
     * This must be called before using other functionality (accessing cell elements, input 
     * handling, etc.).
     * @param rowsPerFragment  The number of rows in each tbody.  The last tbody may have fewer 
     * rows.  All others must have exactly this number of rows.
     */
    public void setRowsPerFragment(int rowsPerFragment) {
        this.rowsPerFragment = rowsPerFragment;
    }
}
