/* 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.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
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.prefs.AppPreferences;
import com.cburch.logisim.util.GraphicsUtil;

class TableTab extends JPanel implements TruthTablePanel, TabInterface {
	private class MyListener implements TruthTableListener {
		@Override
		public void cellsChanged(TruthTableEvent event) {
			repaint();
		}

		@Override
		public void structureChanged(TruthTableEvent event) {
			computePreferredSize();
		}
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -8687487329361908314L;
	private static final Font HEAD_FONT = new Font("sans serif", Font.BOLD, 14);
	private static final Font BODY_FONT = new Font("sans serif", Font.BOLD, 14);
	private static final int COLUMN_SEP = 8;

	private static final int HEADER_SEP = 4;

	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;
	private final Color[] colors = { new Color(128, 128, 128), new Color(144, 64, 192), new Color(192, 0, 192),
			new Color(0, 0, 255), new Color(0, 192, 192), new Color(0, 128, 0), new Color(0, 192, 0),
			new Color(255, 96, 128), new Color(222, 0, 0), new Color(48, 48, 48) };

	public TableTab(TruthTable table) {
		this.table = table;
		table.addTruthTableListener(myListener);
		setToolTipText(" ");
		caret = new TableTabCaret(this);
		clip = new TableTabClip(this);
	}

	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();
	}

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

	@Override
	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);
				}
			}
		}
	}

	TableTabCaret getCaret() {
		return caret;
	}

	int getCellHeight() {
		return cellHeight;
	}

	int getCellWidth() {
		return cellWidth;
	}

	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;
	}

	@Override
	public int getOutputColumn(MouseEvent event) {
		int inputs = table.getInputColumnCount();
		if (inputs == 0)
			inputs = 1;
		int ret = getColumn(event);
		return ret >= inputs ? ret - inputs : -1;
	}

	@Override
	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;
	}

	@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 TruthTable getTruthTable() {
		return table;
	}

	JScrollBar getVerticalScrollBar() {
		return new JScrollBar() {
			/**
			 * 
			 */
			private static final long serialVersionUID = 7084235376262867690L;

			@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;
				}
			}

			@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;
				}
			}
		};
	}

	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;
	}

	void localeChanged() {
		computePreferredSize();
		repaint();
	}

	@Override
	public void paintComponent(Graphics g) {
		if (AppPreferences.ANTI_ALIASING.getBoolean()) {
			Graphics2D g2 = (Graphics2D) g;
			g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		}
		super.paintComponent(g);
		GraphicsUtil.switchToWidth(g, 1.5f);
		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;
		}

		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.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++) {
			g.setColor(colors[i % 10]);
			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);
	}

	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;
	}

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

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

	@Override
	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);
	}
}