/* 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.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.JPanel;
import javax.swing.JScrollBar;

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 TableTab extends JPanel implements TruthTablePanel, TabInterface {
	private static final Font HEAD_FONT = new Font("Serif", Font.BOLD, 14);
	private static final Font BODY_FONT = new Font("Serif", Font.PLAIN, 14);
	private static final int COLUMN_SEP = 8;
	private static final int HEADER_SEP = 4;
	
	private class MyListener implements TruthTableListener {
		public void cellsChanged(TruthTableEvent event) {
			repaint();
		}

		public void structureChanged(TruthTableEvent event) {
			computePreferredSize();
		}
	}
	
	private MyListener myListener = new MyListener();
	private TruthTable table;
	private int cellWidth = 25; // reasonable start values
	private int cellHeight = 15;
	private int tableWidth;
	private int tableHeight;
	private int provisionalX;
	private int provisionalY;
	private Entry provisionalValue = null;
	private TableTabCaret caret;
	private TableTabClip clip;
	
	public TableTab(TruthTable table) {
		this.table = table;
		table.addTruthTableListener(myListener);
		setToolTipText(" ");
		caret = new TableTabCaret(this);
		clip = new TableTabClip(this);
	}

	public TruthTable getTruthTable() {
		return table;
	}
	
	TableTabCaret getCaret() {
		return caret;
	}
	
	void localeChanged() {
		computePreferredSize();
		repaint();
	}
	
	public int getColumn(MouseEvent event) {
		int x = event.getX() - (getWidth() - tableWidth) / 2;
		if (x < 0) return -1;
		int inputs = table.getInputColumnCount();
		int cols = inputs + table.getOutputColumnCount();
		int ret = (x + COLUMN_SEP / 2) / (cellWidth + COLUMN_SEP);
		if (inputs == 0) ret--;
		return ret >= 0 ? ret < cols ? ret : cols : -1;
	}
	
	int getColumnCount() {
		int inputs = table.getInputColumnCount();
		int outputs = table.getOutputColumnCount();
		return inputs + outputs;
	}
	
	public int getOutputColumn(MouseEvent event) {
		int inputs = table.getInputColumnCount();
		if (inputs == 0) inputs = 1;
		int ret = getColumn(event);
		return ret >= inputs ? ret - inputs : -1;
	}
	
	public int getRow(MouseEvent event) {
		int y = event.getY() - (getHeight() - tableHeight) / 2;
		if (y < cellHeight + HEADER_SEP) return -1;
		int ret = (y - cellHeight - HEADER_SEP) / cellHeight;
		int rows = table.getRowCount();
		return ret >= 0 ? ret < rows ? ret : rows : -1;
	}
	
	public void setEntryProvisional(int y, int x, Entry value) {
		provisionalY = y;
		provisionalX = x;
		provisionalValue = value;
		
		int top = (getHeight() - tableHeight) / 2 + cellHeight + HEADER_SEP
			+ y * cellHeight;
		repaint(0, top, getWidth(), cellHeight);
	}
	
	@Override
	public String getToolTipText(MouseEvent event) {
		int row = getRow(event);
		int col = getOutputColumn(event);
		Entry entry = table.getOutputEntry(row, col);
		return entry.getErrorMessage();
	}
	
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		
		caret.paintBackground(g);
		
		Dimension sz = getSize();
		int top = Math.max(0, (sz.height - tableHeight) / 2);
		int left = Math.max(0, (sz.width - tableWidth) / 2);
		int inputs = table.getInputColumnCount();
		int outputs = table.getOutputColumnCount();
		if (inputs == 0 && outputs == 0) {
			g.setFont(BODY_FONT);
			GraphicsUtil.drawCenteredText(g, Strings.get("tableEmptyMessage"), sz.width / 2, sz.height / 2);
			return;
		}
		
		g.setColor(Color.GRAY);
		int lineX = left + (cellWidth + COLUMN_SEP) * inputs
			- COLUMN_SEP / 2;
		if (inputs == 0) lineX = left + cellWidth + COLUMN_SEP / 2;
		int lineY = top + cellHeight + HEADER_SEP / 2;
		g.drawLine(left, lineY, left + tableWidth, lineY);
		g.drawLine(lineX, top, lineX, top + tableHeight);
		
		g.setColor(Color.BLACK);
		g.setFont(HEAD_FONT);
		FontMetrics headerMetric = g.getFontMetrics();
		int x = left;
		int y = top + headerMetric.getAscent() + 1;
		if (inputs == 0) {
			x = paintHeader(Strings.get("tableNullHeader"), x, y, g, headerMetric);
		} else {
			for (int i = 0; i < inputs; i++) {
				x = paintHeader(table.getInputHeader(i), x, y, g, headerMetric);
			}
		}
		if (outputs == 0) {
			x = paintHeader(Strings.get("tableNullHeader"), x, y, g, headerMetric);
		} else {
			for (int i = 0; i < outputs; i++) {
				x = paintHeader(table.getOutputHeader(i), x, y, g, headerMetric);
			}
		}
		
		g.setFont(BODY_FONT);
		FontMetrics bodyMetric = g.getFontMetrics();
		y = top + cellHeight + HEADER_SEP;
		Rectangle clip = g.getClipBounds();
		int firstRow = Math.max(0, (clip.y - y) / cellHeight);
		int lastRow = Math.min(table.getRowCount(), 2 + (clip.y + clip.height - y) / cellHeight);
		y += firstRow * cellHeight;
		if (inputs == 0) left += cellWidth + COLUMN_SEP;
		boolean provisional = false;
		for (int i = firstRow; i < lastRow; i++) {
			x = left;
			for (int j = 0; j < inputs + outputs; j++) {
				Entry entry = j < inputs ? table.getInputEntry(i, j)
						: table.getOutputEntry(i, j - inputs);
				if (provisionalValue != null && i == provisionalY
						&& j - inputs == provisionalX) {
					provisional = true;
					entry = provisionalValue;
				}
				if (entry.isError()) {
					g.setColor(ERROR_COLOR);
					g.fillRect(x, y, cellWidth, cellHeight);
					g.setColor(Color.BLACK);
				}
				String label = entry.getDescription();
				int width = bodyMetric.stringWidth(label);
				if (provisional) {
					provisional = false;
					g.setColor(Color.GREEN);
					g.drawString(label, x + (cellWidth - width) / 2,
							y + bodyMetric.getAscent());
					g.setColor(Color.BLACK);
				} else {
					g.drawString(label, x + (cellWidth - width) / 2,
							y + bodyMetric.getAscent());
				}
				x += cellWidth + COLUMN_SEP;
			}
			y += cellHeight;
		}
		
		caret.paintForeground(g);
	}
	
	int getCellWidth() { return cellWidth; }
	int getCellHeight() { return cellHeight; }
	
	int getX(int col) {
		Dimension sz = getSize();
		int left = Math.max(0, (sz.width - tableWidth) / 2);
		int inputs = table.getInputColumnCount();
		if (inputs == 0) left += cellWidth + COLUMN_SEP;
		return left + col * (cellWidth + COLUMN_SEP);
	}
	
	int getY(int row) {
		Dimension sz = getSize();
		int top = Math.max(0, (sz.height - tableHeight) / 2);
		return top + cellHeight + HEADER_SEP + row * cellHeight;
	}

	private int paintHeader(String header, int x, int y,
			Graphics g, FontMetrics fm) {
		int width = fm.stringWidth(header);
		g.drawString(header, x + (cellWidth - width) / 2, y);
		return x + cellWidth + COLUMN_SEP;
	}
	
	private void computePreferredSize() {
		int inputs = table.getInputColumnCount();
		int outputs = table.getOutputColumnCount();
		if (inputs == 0 && outputs == 0) {
			setPreferredSize(new Dimension(0, 0));
			return;
		}
		
		Graphics g = getGraphics();
		if (g == null) {
			cellHeight = 16;
			cellWidth = 24;
		} else {
			FontMetrics fm = g.getFontMetrics(HEAD_FONT);
			cellHeight = fm.getHeight();
			cellWidth = 24;
			if (inputs == 0 || outputs == 0) {
				cellWidth = Math.max(cellWidth, fm.stringWidth(Strings.get("tableNullHeader")));
			}
			for (int i = 0; i < inputs + outputs; i++) {
				String header = i < inputs ? table.getInputHeader(i)
						: table.getOutputHeader(i - inputs);
				cellWidth = Math.max(cellWidth, fm.stringWidth(header));
			}
		}
		
		if (inputs == 0) inputs = 1;
		if (outputs == 0) outputs = 1;
		tableWidth = (cellWidth + COLUMN_SEP) * (inputs + outputs) - COLUMN_SEP;
		tableHeight = cellHeight * (1 + table.getRowCount()) + HEADER_SEP;
		setPreferredSize(new Dimension(tableWidth, tableHeight));
		revalidate();
		repaint();
	}

	JScrollBar getVerticalScrollBar() {
		return new JScrollBar() {
			@Override
			public int getUnitIncrement(int direction) {
				int curY = getValue();
				if (direction > 0) {
					return curY > 0 ? cellHeight : cellHeight + HEADER_SEP;
				} else {
					return curY > cellHeight + HEADER_SEP ? cellHeight
							: cellHeight + HEADER_SEP;
				}
			}
		
			@Override
			public int getBlockIncrement(int direction) {
				int curY = getValue();
				int curHeight = getVisibleAmount();
				int numCells = curHeight / cellHeight - 1;
				if (numCells <= 0) numCells = 1;
				if (direction > 0) {
					return curY > 0
						? numCells * cellHeight
						: numCells * cellHeight + HEADER_SEP;
				} else {
					return curY > cellHeight + HEADER_SEP
						? numCells * cellHeight
						: numCells * cellHeight + HEADER_SEP;
				}
			}
		};
	}

	public void copy() {
		requestFocus();
		clip.copy();
	}

	public void paste() {
		requestFocus();
		clip.paste();
	}

	public void delete() {
		requestFocus();
		int r0 = caret.getCursorRow();
		int r1 = caret.getMarkRow();
		int c0 = caret.getCursorCol();
		int c1 = caret.getMarkCol();
		if (r0 < 0 || r1 < 0) return;
		if (r1 < r0) { int t = r0; r0 = r1; r1 = t; }
		if (c1 < c0) { int t = c0; c0 = c1; c1 = t; }
		int inputs = table.getInputColumnCount();
		for (int c = c0; c <= c1; c++) {
			if (c >= inputs) {
				for (int r = r0; r <= r1; r++) {
					table.setOutputEntry(r, c - inputs, Entry.DONT_CARE);
				}
			}
		}
	}

	public void selectAll() {
		caret.selectAll();
	}
}
