package org.openswing.swing.pivottable.cellspantable.client;

import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.plaf.basic.*;
import javax.swing.event.*;


/**
 * <p>Title: OpenSwing Framework</p>
 * <p>Description: JTable with support for cells span.</p>
 * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
 *
 * <p> This file is part of OpenSwing Framework.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the (LGPL) Lesser General Public
 * License as published by the Free Software Foundation;
 *
 *                GNU LESSER GENERAL PUBLIC LICENSE
 *                 Version 2.1, February 1999
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *       The author may be contacted at:
 *           maurocarniel@tin.it</p>
 *
 * @author Mauro Carniel
 * @version 1.0
 */
public class CellSpanTable extends JTable {


  public CellSpanTable() {
    setShowGrid(false);
    this.getColumnModel().setColumnMargin(0);
    this.setRowMargin(0);
//    setUI(new CellSpanTableUI());
//    getTableHeader().setReorderingAllowed(false);
//    setCellSelectionEnabled(true);
//    setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
  }


    public void setModel(TableModel model) {
      super.setModel(model);
      setUI(new CellSpanTableUI());
//      setShowGrid(false);
//      setGridSize(new Dimension(model.getColumnCount(),model.getRowCount()));
    }


    public Rectangle getCellRect(int row, int column, boolean includeSpacing) {
      Rectangle sRect = super.getCellRect(row,column,includeSpacing);
      if ((row <0) || (column<0) ||
          (getRowCount() <= row) || (getColumnCount() <= column)) {
          return sRect;
      }
      if (!isGridVisible(row,column)) {
        int temp_row    = row;
        int temp_column = column;
        row    += getSpan(temp_row,temp_column)[ROW];
        column += getSpan(temp_row,temp_column)[COLUMN];
      }
      int[] n = getSpan(row,column);

      int index = 0;
      int columnMargin = getColumnModel().getColumnMargin();

      Rectangle cellFrame = new Rectangle();
      int aCellHeight = rowHeight + rowMargin;
      cellFrame.y = row * aCellHeight;
      cellFrame.height = n[ROW] * aCellHeight;

      Enumeration enumeration = getColumnModel().getColumns();
      while (enumeration.hasMoreElements()) {
        TableColumn aColumn = (TableColumn)enumeration.nextElement();
        cellFrame.width = aColumn.getWidth() + columnMargin;
        if (index == column) break;
        cellFrame.x += cellFrame.width;
        index++;
      }
      try {
        for (int i = 0; i < n[COLUMN] - 1; i++) {
          TableColumn aColumn = (TableColumn) enumeration.nextElement();
          cellFrame.width += aColumn.getWidth() + columnMargin;
        }
      }
      catch (Exception ex) {
      }



      if (!includeSpacing) {
        Dimension spacing = getIntercellSpacing();
        cellFrame.setBounds(cellFrame.x +      spacing.width/2,
                            cellFrame.y +      spacing.height/2,
                            cellFrame.width -  spacing.width,
                            cellFrame.height - spacing.height);
      }
      return cellFrame;
    }


    private int[] rowColumnAtPoint(Point point) {
      int[] retValue = {-1,-1};
      int row = point.y / (rowHeight + rowMargin);
      if ((row <0)||(getRowCount() <= row)) return retValue;
      int column = getColumnModel().getColumnIndexAtX(point.x);

      if (isGridVisible(row,column)) {
        retValue[COLUMN] = column;
        retValue[ROW   ] = row;
        return retValue;
      }
      retValue[COLUMN] = column + getSpan(row,column)[COLUMN];
      retValue[ROW   ] = row + getSpan(row,column)[ROW];
      return retValue;
    }


    public int rowAtPoint(Point point) {
      return rowColumnAtPoint(point)[ROW];
    }
    public int columnAtPoint(Point point) {
      return rowColumnAtPoint(point)[COLUMN];
    }



    public void columnSelectionChanged(ListSelectionEvent e) {
      repaint();
    }

    public void valueChanged(ListSelectionEvent e) {
      int firstIndex = e.getFirstIndex();
      int  lastIndex = e.getLastIndex();
      if (firstIndex == -1 && lastIndex == -1) { // Selection cleared.
        repaint();
      }
      Rectangle dirtyRegion = getCellRect(firstIndex, 0, false);
      int numCoumns = getColumnCount();
      int index = firstIndex;
      for (int i=0;i<numCoumns;i++) {
        dirtyRegion.add(getCellRect(index, i, false));
      }
      index = lastIndex;
      for (int i=0;i<numCoumns;i++) {
        dirtyRegion.add(getCellRect(index, i, false));
      }
      repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
    }



















    public static final int ROW    = 0;
    public static final int COLUMN = 1;

    protected int rowSize;
    protected int columnSize;
    protected int[][][] span;




    protected void initValue() {
      for(int i=0; i<span.length;i++) {
        for(int j=0; j<span[i].length; j++) {
          span[i][j][COLUMN] = 1;
          span[i][j][ROW]    = 1;
        }
      }
    }


    //
    // CellSpan
    //
    public int[] getSpan(int row, int column) {
      if (isOutOfBounds(row, column)) {
        int[] ret_code = {1,1};
        return ret_code;
      }
      return span[row][column];
    }

    public void setSpan(int[] span, int row, int column) {
      if (isOutOfBounds(row, column)) return;
      this.span[row][column] = span;
    }

    public boolean isGridVisible(int row, int column) {
      if (isOutOfBounds(row, column)) return false;
      if ((span[row][column][COLUMN] < 1)
        ||(span[row][column][ROW]    < 1)) return false;
      return true;
    }

    public void combine(int[] rows, int[] columns) {
      if (isOutOfBounds(rows, columns)) return;
      int    rowSpan  = rows.length;
      int columnSpan  = columns.length;
      int startRow    = rows.length==0?0:rows[0];
      int startColumn = columns.length==0?0:columns[0];
      for (int i=0;i<rowSpan;i++) {
        for (int j=0;j<columnSpan;j++) {
          if ((span[startRow +i][startColumn +j][COLUMN] != 1)
            ||(span[startRow +i][startColumn +j][ROW]    != 1)) {
            return ;
          }
        }
      }
      for (int i=0,ii=0;i<rowSpan;i++,ii--) {
        for (int j=0,jj=0;j<columnSpan;j++,jj--) {
          span[startRow +i][startColumn +j][COLUMN] = jj;
          span[startRow +i][startColumn +j][ROW]    = ii;
          //System.out.println("r " +ii +"  c " +jj);
        }
      }
      span[startRow][startColumn][COLUMN] = columnSpan;
      span[startRow][startColumn][ROW]    =    rowSpan;

    }

    public void split(int row, int column) {
      if (isOutOfBounds(row, column)) return;
      int columnSpan = span[row][column][COLUMN];
      int    rowSpan = span[row][column][ROW];
      for (int i=0;i<rowSpan;i++) {
        for (int j=0;j<columnSpan;j++) {
          span[row +i][column +j][COLUMN] = 1;
          span[row +i][column +j][ROW]    = 1;
        }
      }
    }


    public void addColumn() {
      int[][][] oldSpan = span;
      int numRows    = oldSpan.length;
      int numColumns = oldSpan[0].length==0?0:oldSpan[0].length;
      span = new int[numRows][numColumns + 1][2];
      for(int i=0;i<numRows;i++) {
        span[i] = new int[numColumns + 1][2];
        System.arraycopy(oldSpan[i],0,span[i],0,oldSpan[i].length);
        span[i][numColumns][COLUMN] = 1;
        span[i][numColumns][ROW] = 1;
      }
      columnSize++;
    }

    public void addRow() {
      int[][][] oldSpan = span;
      int numRows    = oldSpan.length;
      int numColumns = oldSpan.length==0?getModel().getColumnCount():oldSpan[0].length;
      span = new int[numRows + 1][numColumns][2];
      System.arraycopy(oldSpan,0,span,0,numRows);
      for (int i=0;i<numColumns;i++) {
        span[numRows][i][COLUMN] = 1;
        span[numRows][i][ROW]    = 1;
      }
      rowSize++;
    }

    public void insertRow(int row) {
      int[][][] oldSpan = span;
      int numRows    = oldSpan.length;
      int numColumns = oldSpan.length==0?getModel().getColumnCount():oldSpan[0].length;
      span = new int[numRows + 1][numColumns][2];
      if (0 < row) {
        System.arraycopy(oldSpan,0,span,0,row-1);
      }
      System.arraycopy(oldSpan,0,span,row,numRows - row);
      for (int i=0;i<numColumns;i++) {
        span[row][i][COLUMN] = 1;
        span[row][i][ROW]    = 1;
      }
      rowSize++;
    }


    public void setGridSize(Dimension size) {
      columnSize = size.width;
      rowSize    = size.height;
      span = new int[rowSize][columnSize][2];   // 2: COLUMN,ROW
      initValue();
    }

    public final boolean isOutOfBounds(int row, int column) {
      if ((row    < 0)||(rowSize    <= row)
        ||(column < 0)||(columnSize <= column)) {
        return true;
      }
      return false;
    }

    public final boolean isOutOfBounds(int[] rows, int[] columns) {
      for (int i=0;i<rows.length;i++) {
        if ((rows[i] < 0)||(rowSize <= rows[i])) return true;
      }
      for (int i=0;i<columns.length;i++) {
        if ((columns[i] < 0)||(columnSize <= columns[i])) return true;
      }
      return false;
    }

    public final void setValues(Object[][] target, Object value,
                             int[] rows, int[] columns) {
      for (int i=0;i<rows.length;i++) {
        int row = rows[i];
        for (int j=0;j<columns.length;j++) {
          int column = columns[j];
          target[row][column] = value;
        }
      }
    }



}
