/**
 * 
 */
package view.tool.jEasyTable;

import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;

import view.tool.dateField.DateField;
import view.tool.inputField.JValidTextField;
import view.tool.jEasyTable.factory.JEasyTableCellFactoryClick;
import view.tool.jEasyTable.factory.JEasyTableCellFactoryDoubleClick;
import view.tool.jEasyTable.factory.JEasyTableCellFactorySelectable;
import view.tool.jEasyTable.factory.JEasyTableFactory;

/**
 * @author alexis
 * Table that acts like JTable but alot more dynamique
 */
public class JEasyTable extends JPanel implements Observer{
	/**
	 * 
	 */
	private static final long serialVersionUID = 5251722666988021877L;
	private final Border grayBorder = BorderFactory.createLineBorder(Color.gray);
	private final Border lightgrayBorder = BorderFactory.createLineBorder(Color.lightGray);
	private GridBagConstraints c = new GridBagConstraints();
	private String[] column_names;
	private Map<Integer,JEasyTableFactory> listenerByColumn = new HashMap<Integer,JEasyTableFactory>();
	private int column;
	private int row;
	private int selected_index;
	
	/**************************************************************************
	 * constructeur
	 *************************************************************************/
	
	/**
	 * Constructor
	 * @param column_names titles of the column of the table
	 * @throws Exception 
	 */
	public JEasyTable(String[] column_names) throws Exception {
		if(column_names.length == 0)
			throw new Exception("column_names size should be atleast 1");
		
		this.setLayout(new GridBagLayout());
		this.setBorder(lightgrayBorder);
		this.column_names = column_names;
		column = column_names.length-1;
		row = 0;
		selected_index = -1;
	}
	
	/**
	 * add the header (titles) of the table
	 */
	private void addHeader(){
		for(int i = 0; i < column_names.length; i++)
		{
			c.fill = GridBagConstraints.BOTH;
			c.weightx = c.weighty = 1.0;
			c.gridx = i;
			c.gridy = 0;
			JLabel cell = new JLabel(column_names[i]);
			cell.setFont(new Font(cell.getFont().getName(),cell.getFont().getStyle(),cell.getFont().getSize()+4));
			cell.setBackground(Color.lightGray);
			cell.setOpaque(true);
			cell.setBorder(grayBorder);
			add(cell,c);
		}
		row++;
	}
	
	/**
	 * remove last row of the table
	 * @throws Exception 
	 */
	public void removeLastRow() throws Exception{
		if(row < 1)
			throw new Exception("Row amount < 1");
		
		for(int i = 0; i <= column; i++)
		{
			int temp = ((column+1)*(row+1))-i+column;
			this.remove(temp);
		}
		row--;
		updateVisual();
	}
	
	/**
	 * remove row from table
	 * @param index of the row to remove
	 * @throws Exception 
	 */
	public void removeRow(int index) throws Exception{
		if(row < 1)
			throw new Exception("Row amount < 1");
		if(index > row || index < 0)
			throw new Exception("Index out of bound");
	
		JComponent[][] temp = new JComponent[row][column+1];
		int currentColumn = 0;
		int currentRow = 0;
		boolean justSkipped = false;
		boolean hasSkipped = false;
		
		for(int i = column+1; i < (column+1)*(row+1)+column+1; i++)
		{
			if(currentColumn == column+1)
			{
				currentRow++;
				currentColumn = 0;
				
				if(justSkipped == true)
				{
					currentRow--;
					hasSkipped = true;
					justSkipped = false;
				}
			}
			if(hasSkipped || currentRow != index)
			{
				temp[currentRow][currentColumn] = ((JEasyTableCell)this.getComponent(i)).getContent();
			}
			else
			{
				justSkipped = true;
			}
			currentColumn++;
		}
		setContent(temp);
	}
	
	
	/**
	 * add row at the end of the table
	 * @param content row to add to the table
	 * @throws Exception 
	 */
	public void addRow(JComponent[] content) throws Exception{
		if(content.length-1 != column)
			throw new Exception("JComponent[].length should be equal to the number of columns");
		
		row++;
		
		for(int i = 0; i < content.length; i++)
		{
			c.fill = GridBagConstraints.BOTH;
			c.weightx = c.weighty = 1.0;
			c.gridx = i;
			c.gridy = row+1;
			
			JEasyTableCell tempPane;
			
			if(listenerByColumn.containsKey(i))
			{
				if(listenerByColumn.get(i) instanceof JEasyTableCellFactorySelectable)
					tempPane = listenerByColumn.get(i).build(content[i],row);
				else
					tempPane = listenerByColumn.get(i).build(content[i]);
			}
			else
			{
				tempPane = new JEasyTableCell(content[i]);
			}
			this.add(tempPane,c);
		}
		updateVisual();
	}
	
	/**
	 * Adds a row at given position
	 * @param content row of the table
	 * @param index index to place it
	 * @throws Exception 
	 */
	public void addRowAt(JComponent[] content, int index) throws Exception{
		if(row < 0 || index > row || index < 0)
			throw new Exception("Index out of bound exception");
		if(content.length-1 != column)
			throw new Exception("JComponent[].length should be equal to the number of columns");
			
		JComponent[][] temp = new JComponent[row+2][column+1];
		int currentColumn = 0;
		int currentRow = 0;
		boolean isAdded = false;

		for(int i = column+1; i < (column+1)*(row+1)+column+1; i++)
		{
			if(currentColumn == column+1)
			{
				currentRow++;
				currentColumn = 0;
			}
			if(currentRow == index && !isAdded)
			{
				isAdded = true;
				for(int k = 0; k < column+1; k++)
					temp[currentRow][k] = content[k];
				currentRow++;
			}
			temp[currentRow][currentColumn] = ((JEasyTableCell)this.getComponent(i)).getContent();

			currentColumn++;
		}
		setContent(temp);
	}
	
	/**
	 * Clear the content
	 */
	public void clear(){
		this.setContent(new JComponent[][]{{}});
	}
	
	/**
	 * update the graphical display
	 */
	private void updateVisual(){
		this.revalidate();
		this.repaint();
	}
	
	/**************************************************************************
	 * getter
	 *************************************************************************/
	
	/* (non-Javadoc)
	 * @see java.awt.Container#getComponentAt(int, int)
	 */
	public JComponent getComponentAt(int column_index, int row_index){
		if(column_index < 0 || column_index > column || row_index < 0 || row_index > row)
			return null;//throw new Exception("Index out of bound");
		
		int index = ((row_index+1) * (column+1))+column_index;
		return ((JEasyTableCell)this.getComponent(index)).getContent();
	}
	
	/**
	 * @param column_index
	 * @param row_index
	 * @return the cell at a specific position
	 * @throws Exception 
	 */
	private JEasyTableCell getCellAt(int column_index, int row_index) throws Exception{
		if(column_index < 0 || column_index > column || row_index < 0 || row_index > row)
			throw new Exception("Index out of bound");
		
		int index = ((row_index+1) * (column+1))+column_index;
		return (JEasyTableCell)this.getComponent(index);
	}
	
	/**
	 * @param row_index index of the row you want to get
	 * @return row of JComponent
	 * @throws Exception 
	 */
	public JComponent[] getRowComponentAt(int row_index) throws Exception{
		if(row_index < 0 || row_index > row)
			throw new Exception("Index out of bound");
		
		int index = ((row_index+1) * (column+1));
		JComponent[] componentRow = new JComponent[column+1];
		for(int i = 0; i < column+1; i++)
			componentRow[i] = ((JEasyTableCell)this.getComponent(index+i)).getContent();
		return componentRow;
	}
	
	/**
	 * @return index of the selected row
	 * return -1 when nothing is selected
	 */
	public int getSelectedRowIndex(){
		return selected_index;
	}
	
	/**
	 * @return the content of the selected row
	 */
	public JComponent[] getSelectedRow(){
		JComponent[] rowComponent = new JComponent[column];
		for(int i = 0; i < column; i++)
		{
			rowComponent[i] = this.getComponentAt(i, selected_index);
		}
		
		return rowComponent;
	}
	
	/**************************************************************************
	 * setter
	 *************************************************************************/
	
	/**
	 * place content at row and column
	 * @param content JComponent you want to place
	 * @param column_index index of the column
	 * @param row_index index of the row
	 * @throws Exception 
	 */
	public void setComponentAt(JComponent content, int column_index, int row_index) throws Exception{
		if(column_index > column || row_index > row || column_index < 0 || row_index < 0)
			throw new Exception("Index out of bound");
		
		int index = ((row_index+1) * (column+1))+column_index;
		c.fill = GridBagConstraints.BOTH;
		c.weightx = c.weighty = 1.0;
		c.gridx = column_index;
		c.gridy = row_index+1;
		this.remove(index);
		
		JEasyTableCell tempPane;
		
		if(listenerByColumn.containsKey(column_index))
		{
			if(listenerByColumn.get(column_index) instanceof JEasyTableCellFactorySelectable)
				tempPane = listenerByColumn.get(column_index).build(content,row_index);
			else
				tempPane = listenerByColumn.get(column_index).build(content);
		}
		else
		{
			tempPane = new JEasyTableCell(content);
		}
		
		this.add(tempPane,c,index);
		updateVisual();
	}
	
	/**
	 * remplace any previous content with content in parameter
	 * @param content to set in the table
	 */
	public void setContent(JComponent[][] content){
		this.removeAll();
		addHeader();
		
		for(int j = 0; j < content.length; j++)
		{
			for(int i = 0; i < content[0].length; i++)
			{
				c.fill = GridBagConstraints.BOTH;
				c.weightx = c.weighty = 1.0;
				c.gridx = i;
				c.gridy = j+1;
				
				JEasyTableCell tempPane;
				
				if(listenerByColumn.containsKey(i))
				{
					if(listenerByColumn.get(i) instanceof JEasyTableCellFactorySelectable)//------------------------------
						tempPane = listenerByColumn.get(i).build(content[j][i],j);
					else
						tempPane = listenerByColumn.get(i).build(content[j][i]);
				}
				else
				{
					tempPane = new JEasyTableCell(content[j][i]);
				}
					
				this.add(tempPane,c);
			}
		}
		
		row = content.length-1;
		updateVisual();
	}
	
	/**
	 * remplace row at index
	 * @param content to set at row
	 * @param row_index index of the row to remplace
	 * @throws Exception 
	 */
	public void setComponentRowAt(JComponent[] content, int row_index) throws Exception{
		if(content.length-1 != column)
			throw new Exception("JComponent[].length should be equal to the number of columns");
		if(row_index < 0 || row_index > row)
			throw new Exception("Index out of bound");
			
			
		int index = ((row_index+1) * (column+1));
		for(int i = 0; i < column+1; i++)
		{
			c.fill = GridBagConstraints.BOTH;
			c.weightx = c.weighty = 1.0;
			c.gridx = i;
			c.gridy = row_index+1;
			this.remove(index+(column-i));
			
			JEasyTableCell tempPane;
			
			if(listenerByColumn.containsKey(i))
			{
				if(listenerByColumn.get(i) instanceof JEasyTableCellFactorySelectable)//------------------------------
					tempPane = listenerByColumn.get(i).build(content[i],row_index);
				else
					tempPane = listenerByColumn.get(i).build(content[i]);
			}
			else
			{
				tempPane = new JEasyTableCell(content[i]);
			}
			this.add(tempPane,c,index+(column-i));
		}
		updateVisual();
	}
	
	/**
	 * add click listener at a specific column
	 * @param column_index
	 * @param listener
	 * @throws Exception
	 */
	public void setClickListenerAt(int column_index, Observer listener) throws Exception{
		if(column_index <= column && column != 0 && row == 0)
		{
			listenerByColumn.put(column_index, new JEasyTableCellFactoryClick(listener));
		}
		else
		{
			throw new Exception("Index out of bound");
		}
	}
	
	/**
	 * add doubleclick listener at a specific column
	 * @param column_index
	 * @param listener
	 * @throws Exception
	 */
	public void setDoubleClickListenerAt(int column_index, Observer listener) throws Exception{
		if(column_index > column || column == 0 || column_index < 0)
			throw new Exception("Index out of bound");
		
		listenerByColumn.put(column_index, new JEasyTableCellFactoryDoubleClick(listener));
	}
	
	/**
	 * add click listener at all the table, the click listener is used to select a row
	 * @param column_index
	 * @param listener
	 * @throws Exception
	 */
	public void setSelectableAll() throws Exception{
		for(int i = 0; i < column+1; i++)
		{
			listenerByColumn.put(i, new JEasyTableCellFactorySelectable(this));
		}
	}
	
	/**
	 * add click listener at a specific column, the click listener is used to select a row
	 * @param column_index
	 * @param listener
	 * @throws Exception
	 */
	public void setSelectableAt(int column_index) throws Exception{
		if(column_index < 0 || column_index > column)
			throw new Exception("Index out of bound");
		
		listenerByColumn.put(column_index, new JEasyTableCellFactorySelectable(this));
	}
	
	/**
	 * selects a line
	 * @param line index
	 * @throws Exception
	 */
	public void setSelectedLine(int line) throws Exception{
		if(line > row || line < 0)
			throw new Exception("Index out of bound");
		unselect();
		
		for(int j = 0; j < column+1; j++)
		{
			JEasyTableCell cell = this.getCellAt(j,line);
			cell.setSelected(true);
		}
	}
	
	/**
	 * remove the current selection
	 * @throws Exception
	 */
	public void unselect() throws Exception{
		this.selected_index = -1;
		for(int i = 0; i < row+1; i++)
		{
			for(int j = 0; j < column+1; j++)
			{
				JEasyTableCell cell = this.getCellAt(j,i);
				cell.setSelected(false);
			}
		}
	}
	
	@Override
	public void update(Observable o, Object e) {
		try {
			this.setSelectedLine((Integer)e);
			selected_index = (Integer)e;
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	}
	

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		JFrame a = new JFrame();
		a.setSize(300,300);
		a.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		JComponent[][] comp = new JLabel[6][3];
		for(int i = 0; i < 6; i++)
		{
			for(int j = 0; j < 3; j++)
			{
				if(j == 2)
					comp[i][j] = new JLabel("i"+i+" j"+j+"-------------------------");
				else
					comp[i][j] = new JLabel("i"+i+" j"+j);
			}
		}
		JEasyTable tab = new JEasyTable(new String[]{"aaaaaa","b","c"});
		
		
		
		tab.setClickListenerAt(0, new Observer(){
			@Override
			public void update(Observable o, Object arg) {
				System.out.println(((JLabel)arg).getText());
			}});
		tab.setDoubleClickListenerAt(2, new Observer(){
			@Override
			public void update(Observable o, Object arg) {
				System.out.println(((JLabel)arg).getText()+"B");
			}});

		//tab.setSelectableAll();
		tab.setSelectableAt(1);
		
		tab.setContent(comp);
		
		tab.removeRow(1);
		tab.removeLastRow();
		
		JComponent[] compAdd = new JLabel[3];
		for(int i = 0; i < compAdd.length; i++)
		{
			compAdd[i] = new JLabel("new "+i);
		}
		JComponent[] compAdd2 = new JLabel[3];
		for(int i = 0; i < compAdd.length; i++)
		{
			compAdd2[i] = new JLabel("new TIME "+i);
		}
		tab.addRowAt(compAdd2,3);

		tab.addRow(compAdd);
		
		JComponent[] row3 = tab.getRowComponentAt(3);
		for(int i = 0; i < row3.length; i++)
			((JLabel)row3[i]).setText("TEST"+i);
		
		JLabel tex = (JLabel) tab.getComponentAt(0,0);
		tex.setText("test");
		JComboBox<String> j = new JComboBox<String>(new String[]{"choix con","choix encore plus con","choix le plus con"});
		tab.setComponentAt(DateField.fromCalendar(Calendar.getInstance()), 1, 3);
		tab.setComponentAt(j, 1, 4);
		
		tab.setComponentAt(JValidTextField.JValidTextFieldWithTextDisallowCertainCharacter("PENIS]", "BANANA",32), 1, 4);
		
		JComponent[] row2 = new JComponent[3];
		for(int i = 0; i < row2.length; i++)
			row2[i] = new JLabel("Tessst"+i);
		tab.setComponentRowAt(row2,2);
		JComponent[] row1 = new JComponent[3];
		for(int i = 0; i < row1.length; i++)
			row1[i] = new JLabel("TessstT"+i);
		tab.setComponentRowAt(row1,1);
		JPanel pan = new JPanel();
		pan.add(tab);
		/*line selection*/
		tab.setSelectedLine(2);
		/***************/
		a.add(pan);
		a.setVisible(true);
	}
}