/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.analyze.gui;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;

import com.cburch.logisim.analyze.model.Entry;
import com.cburch.logisim.analyze.model.TruthTable;
import com.cburch.logisim.analyze.model.TruthTableEvent;
import com.cburch.logisim.analyze.model.TruthTableListener;
import com.cburch.logisim.util.GraphicsUtil;

class TableTabCaret {
	private class Listener
			implements MouseListener, MouseMotionListener, KeyListener, FocusListener, TruthTableListener {

		@Override
		public void cellsChanged(TruthTableEvent event) {
		}

		@Override
		public void focusGained(FocusEvent e) {
			if (cursorRow >= 0)
				expose(cursorRow, cursorCol);
		}

		@Override
		public void focusLost(FocusEvent e) {
			if (cursorRow >= 0)
				expose(cursorRow, cursorCol);
		}

		@Override
		public void keyPressed(KeyEvent e) {
			if (cursorRow < 0)
				return;
			TruthTable model = table.getTruthTable();
			int rows = model.getRowCount();
			int inputs = model.getInputColumnCount();
			int outputs = model.getOutputColumnCount();
			int cols = inputs + outputs;
			boolean shift = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0;
			switch (e.getKeyCode()) {
			case KeyEvent.VK_UP:
				setCursor(cursorRow - 1, cursorCol, shift);
				break;
			case KeyEvent.VK_LEFT:
				setCursor(cursorRow, cursorCol - 1, shift);
				break;
			case KeyEvent.VK_DOWN:
				setCursor(cursorRow + 1, cursorCol, shift);
				break;
			case KeyEvent.VK_RIGHT:
				setCursor(cursorRow, cursorCol + 1, shift);
				break;
			case KeyEvent.VK_HOME:
				if (cursorCol == 0)
					setCursor(0, 0, shift);
				else
					setCursor(cursorRow, 0, shift);
				break;
			case KeyEvent.VK_END:
				if (cursorCol == cols - 1)
					setCursor(rows - 1, cols - 1, shift);
				else
					setCursor(cursorRow, cols - 1, shift);
				break;
			case KeyEvent.VK_PAGE_DOWN:
				rows = table.getVisibleRect().height / table.getCellHeight();
				if (rows > 2)
					rows--;
				setCursor(cursorRow + rows, cursorCol, shift);
				break;
			case KeyEvent.VK_PAGE_UP:
				rows = table.getVisibleRect().height / table.getCellHeight();
				if (rows > 2)
					rows--;
				setCursor(cursorRow - rows, cursorCol, shift);
				break;
			}
		}

		@Override
		public void keyReleased(KeyEvent e) {
		}

		@Override
		public void keyTyped(KeyEvent e) {
			int mask = e.getModifiersEx();
			if ((mask & ~InputEvent.SHIFT_DOWN_MASK) != 0)
				return;

			char c = e.getKeyChar();
			Entry newEntry = null;
			switch (c) {
			case ' ':
				if (cursorRow >= 0) {
					TruthTable model = table.getTruthTable();
					int inputs = model.getInputColumnCount();
					if (cursorCol >= inputs) {
						Entry cur = model.getOutputEntry(cursorRow, cursorCol - inputs);
						if (cur == Entry.ZERO)
							cur = Entry.ONE;
						else if (cur == Entry.ONE)
							cur = Entry.DONT_CARE;
						else
							cur = Entry.ZERO;
						model.setOutputEntry(cursorRow, cursorCol - inputs, cur);
					}
				}
				break;
			case '0':
				newEntry = Entry.ZERO;
				break;
			case '1':
				newEntry = Entry.ONE;
				break;
			case 'x':
				newEntry = Entry.DONT_CARE;
				break;
			case '\n':
				setCursor(cursorRow + 1, table.getTruthTable().getInputColumnCount(),
						(mask & InputEvent.SHIFT_DOWN_MASK) != 0);
				break;
			case '\u0008':
			case '\u007f':
				setCursor(cursorRow, cursorCol - 1, (mask & InputEvent.SHIFT_DOWN_MASK) != 0);
				break;
			default:
			}
			if (newEntry != null) {
				TruthTable model = table.getTruthTable();
				int inputs = model.getInputColumnCount();
				int outputs = model.getOutputColumnCount();
				if (cursorCol >= inputs) {
					model.setOutputEntry(cursorRow, cursorCol - inputs, newEntry);
					if (cursorCol >= inputs + outputs - 1) {
						setCursor(cursorRow + 1, inputs, false);
					} else {
						setCursor(cursorRow, cursorCol + 1, false);
					}
				}
			}
		}

		@Override
		public void mouseClicked(MouseEvent e) {
		}

		@Override
		public void mouseDragged(MouseEvent e) {
			int row = table.getRow(e);
			int col = table.getColumn(e);
			setCursor(row, col, true);
		}

		@Override
		public void mouseEntered(MouseEvent e) {
		}

		@Override
		public void mouseExited(MouseEvent e) {
		}

		@Override
		public void mouseMoved(MouseEvent e) {
		}

		@Override
		public void mousePressed(MouseEvent e) {
			table.requestFocus();
			int row = table.getRow(e);
			int col = table.getColumn(e);
			setCursor(row, col, (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			mouseDragged(e);
		}

		@Override
		public void structureChanged(TruthTableEvent event) {
			TruthTable model = event.getSource();
			int inputs = model.getInputColumnCount();
			int outputs = model.getOutputColumnCount();
			int rows = model.getRowCount();
			int cols = inputs + outputs;
			boolean changed = false;
			if (cursorRow >= rows) {
				cursorRow = rows - 1;
				changed = true;
			}
			if (cursorCol >= cols) {
				cursorCol = cols - 1;
				changed = true;
			}
			if (markRow >= rows) {
				markRow = rows - 1;
				changed = true;
			}
			if (markCol >= cols) {
				markCol = cols - 1;
				changed = true;
			}
			if (changed)
				table.repaint();
		}
	}

	private static Color SELECT_COLOR = new Color(192, 192, 255);
	private Listener listener = new Listener();
	private TableTab table;
	private int cursorRow;
	private int cursorCol;
	private int markRow;

	private int markCol;

	TableTabCaret(TableTab table) {
		this.table = table;
		cursorRow = 0;
		cursorCol = 0;
		markRow = 0;
		markCol = 0;
		table.getTruthTable().addTruthTableListener(listener);
		table.addMouseListener(listener);
		table.addMouseMotionListener(listener);
		table.addKeyListener(listener);
		table.addFocusListener(listener);

		InputMap imap = table.getInputMap();
		ActionMap amap = table.getActionMap();
		AbstractAction nullAction = new AbstractAction() {
			/**
			 * 
			 */
			private static final long serialVersionUID = 7932515593155479627L;

			@Override
			public void actionPerformed(ActionEvent e) {
			}
		};
		String nullKey = "null";
		amap.put(nullKey, nullAction);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), nullKey);
		imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), nullKey);
	}

	private void expose(int row, int col) {
		if (row >= 0) {
			int x0 = table.getX(0);
			int x1 = table.getX(table.getColumnCount() - 1) + table.getCellWidth();
			table.repaint(x0 - 2, table.getY(row) - 2, (x1 - x0) + 4, table.getCellHeight() + 4);
		}
	}

	int getCursorCol() {
		return cursorCol;
	}

	int getCursorRow() {
		return cursorRow;
	}

	int getMarkCol() {
		return markCol;
	}

	int getMarkRow() {
		return markRow;
	}

	void paintBackground(Graphics g) {
		if (cursorRow >= 0 && cursorCol >= 0 && (cursorRow != markRow || cursorCol != markCol)) {
			g.setColor(SELECT_COLOR);

			int r0 = cursorRow;
			int c0 = cursorCol;
			int r1 = markRow;
			int c1 = markCol;
			if (r1 < r0) {
				int t = r1;
				r1 = r0;
				r0 = t;
			}
			if (c1 < c0) {
				int t = c1;
				c1 = c0;
				c0 = t;
			}
			int x0 = table.getX(c0);
			int y0 = table.getY(r0);
			int x1 = table.getX(c1) + table.getCellWidth();
			int y1 = table.getY(r1) + table.getCellHeight();
			g.fillRect(x0, y0, x1 - x0, y1 - y0);
		}
	}

	void paintForeground(Graphics g) {
		if (!table.isFocusOwner())
			return;
		if (cursorRow >= 0 && cursorCol >= 0) {
			int x = table.getX(cursorCol);
			int y = table.getY(cursorRow);
			GraphicsUtil.switchToWidth(g, 2);
			g.drawRect(x, y, table.getCellWidth(), table.getCellHeight());
			GraphicsUtil.switchToWidth(g, 2);
		}
	}

	void selectAll() {
		table.requestFocus();
		TruthTable model = table.getTruthTable();
		setCursor(model.getRowCount(), model.getInputColumnCount() + model.getOutputColumnCount(), false);
		setCursor(0, 0, true);
	}

	private void setCursor(int row, int col, boolean keepMark) {
		TruthTable model = table.getTruthTable();
		int rows = model.getRowCount();
		int cols = model.getInputColumnCount() + model.getOutputColumnCount();
		if (row < 0)
			row = 0;
		if (col < 0)
			col = 0;
		if (row >= rows)
			row = rows - 1;
		if (col >= cols)
			col = cols - 1;

		if (row == cursorRow && col == cursorCol && (keepMark || (row == markRow && col == markCol))) {
			; // nothing is changing, so do nothing
		} else if (!keepMark && markRow == cursorRow && markCol == cursorCol) {
			int oldRow = cursorRow;
			int oldCol = cursorCol;
			cursorRow = row;
			cursorCol = col;
			markRow = row;
			markCol = col;
			expose(oldRow, oldCol);
			expose(cursorRow, cursorCol);
		} else {
			int r0 = Math.min(row, Math.min(cursorRow, markRow));
			int r1 = Math.max(row, Math.max(cursorRow, markRow));
			int c0 = Math.min(col, Math.min(cursorCol, markCol));
			int c1 = Math.max(col, Math.max(cursorCol, markCol));
			cursorRow = row;
			cursorCol = col;
			if (!keepMark) {
				markRow = row;
				markCol = col;
			}

			int x0 = table.getX(c0);
			int x1 = table.getX(c1) + table.getCellWidth();
			int y0 = table.getY(r0);
			int y1 = table.getY(r1) + table.getCellHeight();
			table.repaint(x0 - 2, y0 - 2, (x1 - x0) + 4, (y1 - y0) + 4);
		}
		int cx = table.getX(cursorCol);
		int cy = table.getY(cursorRow);
		int cw = table.getCellWidth();
		int ch = table.getCellHeight();
		if (cursorRow == 0) {
			ch += cy;
			cy = 0;
		}
		table.scrollRectToVisible(new Rectangle(cx, cy, cw, ch));
	}
}