package charactermanaj.ui;

import static java.lang.Math.*;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.util.Collections;
import java.util.Comparator;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;

import charactermanaj.graphics.filters.ColorConvertParameter;
import charactermanaj.graphics.io.ImageResource;
import charactermanaj.graphics.io.PNGFileImageHeader;
import charactermanaj.graphics.io.PNGFileImageHeaderReader;
import charactermanaj.model.AppConfig;
import charactermanaj.model.Layer;
import charactermanaj.model.LayerOrderMapper;
import charactermanaj.model.PartsIdentifier;
import charactermanaj.model.PartsSet;
import charactermanaj.model.PartsSpecResolver;
import charactermanaj.model.io.PartsImageCollectionParser;
import charactermanaj.model.io.PartsImageCollectionParser.PartsImageCollectionHandler;
import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
import charactermanaj.ui.util.ScaleSupport;
import charactermanaj.util.DesktopUtilities;
import charactermanaj.util.ErrorMessageHelper;
import charactermanaj.util.LocalizedResourcePropertyLoader;

/**
 * 情報ダイアログを開く
 * @author seraphy
 */
public class InformationDialog extends JDialog {

	private static final long serialVersionUID = 1L;

	private static final Logger logger = Logger.getLogger(InformationDialog.class.getName());

	protected static final String STRINGS_RESOURCE = "languages/informationdialog";

	private PartsSpecResolver partsSpecResolver;

	private JTable informationTable;

	private InformationTableModel informationTableModel;

	private TableRowSorter<InformationTableModel> sorter;

	private boolean modeOpen;

	private Runnable closeHandler;

	public InformationDialog(JFrame parent, PartsSpecResolver partsSpecResolver, Runnable closeHandler) {
		super(parent, false);

		this.partsSpecResolver = partsSpecResolver;
		this.closeHandler = closeHandler;

		AppConfig appConfig = AppConfig.getInstance();
		modeOpen = appConfig.isInformationDialogOpenMethod();

		if (partsSpecResolver == null) {
			throw new IllegalArgumentException("partsSpecResolver is null");
		}

		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				onClose();
			}
		});

		final Properties strings = LocalizedResourcePropertyLoader
				.getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);

		setTitle(strings.getProperty("title"));

		informationTableModel = new InformationTableModel();

		informationTable = new JTable(informationTableModel) {
			private static final long serialVersionUID = 1L;
			// セルの幅を大きいものにあわせる
			public Component prepareRenderer(final TableCellRenderer renderer,
					final int row, final int column) {
				final Component prepareRenderer = super.prepareRenderer(renderer, row, column);
				final TableColumn tableColumn = getColumnModel().getColumn(column);
				int preferredWidth = max(prepareRenderer
						.getPreferredSize().width, tableColumn
						.getPreferredWidth()); // セルかヘッダのどちらか幅の大きいほう
				if (tableColumn.getPreferredWidth() != preferredWidth) {
					tableColumn.setPreferredWidth(preferredWidth);
				}
				return prepareRenderer;
			}
		};
		ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
		informationTableModel.adjustColumnModel(informationTable.getColumnModel(), scaleSupport.getManualScaleX());
		informationTable.setShowGrid(true);
		informationTable.setGridColor(appConfig.getGridColor());
		informationTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
		informationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		informationTable.setRowHeight(informationTable.getRowHeight() + 4);

		// 行の高さをフォントの高さにする
		informationTable.setRowHeight((int)(informationTable.getFont().getSize() * 1.2));

		informationTable.setDefaultRenderer(JButton.class, new ButtonCellRender());
		informationTable.setDefaultEditor(JButton.class, new ButtonCellEditor());

		sorter = new TableRowSorter<InformationTableModel>(informationTableModel);
		sorter.setSortable(InformationTableModel.ColumnDef.EDIT_BUTTON.ordinal(), false);
		informationTable.setRowSorter(sorter);
		informationTable.getTableHeader().addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					// ヘッダをダブルクリックでソート順を解除して、モデルの標準の順番に戻す。
					sorter.setSortKeys(null);
					e.consume();
				}
			}
		});

		// セルデータの幅にあわせる(事前に)
		for (int row = 0; row < informationTable.getRowCount(); row++) {
			for (int col = 0; col < informationTable.getColumnCount(); col++) {
				TableCellRenderer renderer = informationTable.getCellRenderer(row, col);
				informationTable.prepareRenderer(renderer, row, col);
			}
		}

		final JPopupMenu popupMenu = new JPopupMenu();
		popupMenu.add(new AbstractAction(strings.getProperty("popupmenu.copyPath")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onCopyFilePath();
			}
		});

		informationTable.setComponentPopupMenu(popupMenu);

		Container contentPane = getContentPane();
		contentPane.setLayout(new BorderLayout());
		JScrollPane informationTableSP = new JScrollPane(informationTable);
		JPanel informationTableSPPabel = new JPanel(new BorderLayout());
		informationTableSPPabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
		informationTableSPPabel.add(informationTableSP, BorderLayout.CENTER);
		contentPane.add(informationTableSPPabel, BorderLayout.CENTER);

		AbstractAction actClose = new AbstractAction(strings.getProperty("btnClose")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onClose();
			}
		};

		JPanel btnPanel = new JPanel();
		btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 45));
		GridBagLayout btnPanelLayout = new GridBagLayout();
		btnPanel.setLayout(btnPanelLayout);

		GridBagConstraints gbc = new GridBagConstraints();
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.anchor = GridBagConstraints.EAST;
		gbc.fill = GridBagConstraints.BOTH;
		gbc.insets = new Insets(3, 3, 3, 3);
		gbc.ipadx = 0;
		gbc.ipady = 0;
		gbc.weightx = 1.;
		gbc.weighty = 0.;
		btnPanel.add(Box.createHorizontalGlue(), gbc);

		gbc.gridx = 1;
		gbc.gridy = 0;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		JButton btnClose = new JButton(actClose);
		btnPanel.add(btnClose, gbc);

		contentPane.add(btnPanel, BorderLayout.SOUTH);

		Toolkit tk = Toolkit.getDefaultToolkit();
		JRootPane rootPane = getRootPane();
		rootPane.setDefaultButton(btnClose);

		InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
		ActionMap am = rootPane.getActionMap();
		im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "closeInformationDialog");
		im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeInformationDialog");
		am.put("closeInformationDialog", actClose);

		// 推奨サイズをスクリーンのスケールで調整する
		Dimension dim = getPreferredSize();
		dim = scaleSupport.manualScaled(dim);
		setSize(dim);
		setLocationRelativeTo(parent);
	}

	public void showPartsInformation(PartsSet partsSet, LayerOrderMapper layerOrderMapper) {

		final Properties strings = LocalizedResourcePropertyLoader
				.getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);
		final PNGFileImageHeaderReader pngHeaderReader = PNGFileImageHeaderReader.getInstance();

		informationTableModel.clear();
		if (partsSet != null) {
			PartsImageCollectionParser parser = new PartsImageCollectionParser(partsSpecResolver);
			parser.parse(partsSet, layerOrderMapper, new PartsImageCollectionHandler() {
				private int serialOrder = 1;
				public void detectImageSource(PartsIdentifier partsIdentifier, Layer layer, float layerOrder,
						final ImageResource imageResource, ColorConvertParameter param) {

					AbstractAction act = new AbstractAction(
							strings.getProperty(modeOpen ? "btn.edit.open" : "btn.edit.edit")) {
						private static final long serialVersionUID = 1L;
						public void actionPerformed(ActionEvent e) {
							onOpen(imageResource);
						}
					};

					URI uri = imageResource.getURI();
					if (uri != null && "file".equals(uri.getScheme()) && DesktopUtilities.isSupported()) {
						act.setEnabled(true);
					} else {
						act.setEnabled(false);
					}

					PNGFileImageHeader pngHeader;
					try {
						pngHeader = pngHeaderReader.readHeader(uri);
					} catch (IOException ex) {
						logger.log(Level.WARNING, "PNG Header loading error.: " + uri, ex);
						pngHeader = null;
					}

					InformationRowModel information = new InformationRowModel(serialOrder++, partsIdentifier, layer, layerOrder,
							imageResource, param, pngHeader, act);
					informationTableModel.addRow(information);
				}
			});
			informationTableModel.sort();
		}
	}

	protected void onClose() {
		if (closeHandler != null) {
			closeHandler.run();
		} else {
			dispose();
		}
	}

	protected void onCopyFilePath() {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		for (int selRow : informationTable.getSelectedRows()) {
			InformationRowModel information = informationTableModel.getRow(selRow);
			pw.println(information.getImageResourceName());
		}

		Toolkit tk = Toolkit.getDefaultToolkit();

		String text = sw.toString();
		if (text.length() == 0) {
			tk.beep();
			return;
		}

		StringSelection textSelection = new StringSelection(sw.toString());

		Clipboard cb = tk.getSystemClipboard();
		cb.setContents(textSelection, null);
	}

	protected void onOpen(ImageResource imageResource) {
		try {
			URI uri = imageResource.getURI();
			if (uri != null && "file".equals(uri.getScheme())) {
				File file = new File(uri);
				DesktopUtilities.open(file);
			}

		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
		}
	}
}

class InformationTableModel extends AbstractTableModelWithComboBoxModel<InformationRowModel> {

	protected enum ColumnDef {
		PARTS_NAME("column.partsName", String.class) {
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getPartsName();
			}
		},
		CATEGORY("column.categoryName", String.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getCategoryName();
			}
		},
		LAYER("column.layerName", String.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getLayerName();
			}
		},
		DEFAULT_LAYER_ORDER("column.defaultLayerOrder", Integer.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getDefaultLayerOrder();
			}
		},
		ACTUAL_LAYER_ORDER("column.layerOrder", Float.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getLayerOrder();
			}
		},
		ORDER("column.order", Float.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getOrder();
			}
		},
		IMAGE_SIZE("column.imagesize", String.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getImageSizeStr();
			}
		},
		COLOR_TYPE("column.colortype", String.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getColorTypeStr();
			}
		},
		EDIT_BUTTON("column.editbtn", JButton.class){
			@Override
			public Object getValue(InformationRowModel information) {
				return information.getButton();
			}
		};

		private final String resource;

		private final Class<?> cls;

		ColumnDef(String resource, Class<?> cls) {
			this.resource = resource;
			this.cls = cls;
		}

		public String getResource() {
			return resource;
		}

		public Class<?> getType() {
			return cls;
		}

		public abstract Object getValue(InformationRowModel information);
	}

	private static final ColumnDef[] COLUMNS = ColumnDef.values();

	private static final long serialVersionUID = 1L;

	private static final String[] columnNames = new String[COLUMNS.length];

	private static final int[] columnWidths = new int[COLUMNS.length];

	static {
		final Properties strings = LocalizedResourcePropertyLoader
				.getCachedInstance().getLocalizedProperties(InformationDialog.STRINGS_RESOURCE);
		for (int idx = 0; idx < COLUMNS.length; idx++) {
			columnNames[idx] = strings.getProperty(COLUMNS[idx].getResource());
			columnWidths[idx] = Integer.parseInt(strings.getProperty(COLUMNS[idx].getResource() + ".width"));
		}
	}

	public void adjustColumnModel(TableColumnModel columnModel, double scale) {
		for (int idx = 0; idx < COLUMNS.length; idx++) {
			columnModel.getColumn(idx).setPreferredWidth((int)(columnWidths[idx] * scale));
		}
	}

	public int getColumnCount() {
		return COLUMNS.length;
	}

	@Override
	public String getColumnName(int column) {
		return columnNames[column];
	}

	@Override
	public Class<?> getColumnClass(int columnIndex) {
		return COLUMNS[columnIndex].getType();
	}

	public Object getValueAt(int rowIndex, int columnIndex) {
		InformationRowModel information = getRow(rowIndex);
		return COLUMNS[columnIndex].getValue(information);
	}

	public void sort() {
		Collections.sort(elements);
		fireTableDataChanged();
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		if (JButton.class.equals(COLUMNS[columnIndex].getType())) {
			InformationRowModel information = getRow(rowIndex);
			return information.getButton().isEnabled();
		}
		return false;
	}
}

class InformationRowModel implements Comparable<InformationRowModel> {

	private int order;

	private PartsIdentifier partsIdentifier;

	private Layer layer;

	private float layerOrder;

	private ImageResource imageResource;

	private JButton btnOpen;

	private PNGFileImageHeader pngHeader;

	public InformationRowModel(int order, PartsIdentifier partsIdentifier, Layer layer,
			float layerOrder,
			ImageResource imageResource,
			ColorConvertParameter colorConvertParameter,
			PNGFileImageHeader pngHeader,
			AbstractAction actOpen) {
		this.order = order;
		this.partsIdentifier = partsIdentifier;
		this.layer = layer;
		this.layerOrder = layerOrder;
		this.imageResource = imageResource;
		this.pngHeader = pngHeader;
		this.btnOpen = new JButton(actOpen) {
			private static final long serialVersionUID = 1L;
			@Override
			public String toString() {
				// JTableをクリップボードにコピーしたときに設定されるカラムの文字列表現
				return "open";
			}
		};
	}

	@Override
	public int hashCode() {
		return partsIdentifier.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}
		if (obj != null && obj instanceof InformationRowModel) {
			InformationRowModel o = (InformationRowModel) obj;
			return partsIdentifier.equals(o.partsIdentifier)
					&& layer.equals(o.layer);
		}
		return false;
	}

	/**
	 * パーツのレイヤー毎のソート順の定義
	 */
	public static final Comparator<InformationRowModel> COMPARATOR = new Comparator<InformationRowModel>() {
		@Override
		public int compare(InformationRowModel o1, InformationRowModel o2) {
			// 補正済みレイヤー重ね順
			int ret = Float.compare(o1.layerOrder, o2.layerOrder);
			if (ret == 0) {
				// レイヤー重ね順
				ret = o1.layer.compareTo(o2.layer);
			}
			if (ret == 0) {
				// 定義順
				ret = o1.order - o2.order;
			}
			if (ret == 0) {
				// カテゴリ、レイヤー、パーツ名順(念のため)
				ret = o1.partsIdentifier.compareTo(o2.partsIdentifier);
			}
			if (ret == 0) {
				// リソース順(念のため)
				ret = o1.imageResource.compareTo(o2.imageResource);
			}
			return ret;
		}
	};

	public int compareTo(InformationRowModel o) {
		return COMPARATOR.compare(this, o);
	}

	public int getOrder() {
		return order;
	}

	public String getPartsName() {
		return this.partsIdentifier.getLocalizedPartsName();
	}

	public String getCategoryName() {
		return this.partsIdentifier.getPartsCategory().getLocalizedCategoryName();
	}

	public String getLayerName() {
		return this.layer.getLocalizedName();
	}

	public int getDefaultLayerOrder() {
		return this.layer.getOrder();
	}

	public float getLayerOrder() {
		return this.layerOrder;
	}

	public String getImageResourceName() {
		return this.imageResource.getFullName();
	}

	public JButton getButton() {
		return btnOpen;
	}

	public String getImageSizeStr() {
		if (pngHeader == null) {
			return "INVALID";
		}
		return pngHeader.getWidth() + "x" + pngHeader.getHeight();
	}

	public String getColorTypeStr() {
		if (pngHeader == null) {
			return "INVALID";
		}

		StringBuilder buf = new StringBuilder();

		int colorType = pngHeader.getColorType();
		if ((colorType & 0x01) != 0) {
			buf.append("Indexed ");
		}
		if ((colorType & 0x02) != 0) {
			buf.append("Color ");

		} else {
			buf.append("Greyscale ");
		}

		if (colorType == 6 || pngHeader.hasTransparencyInformation()) {
			// 6:TrueColor または アルファ情報がある場合のみアルファ有りとする.
			buf.append("Alpha ");
		}

		buf.append(pngHeader.getBitDepth() + "bit");

		return buf.toString().trim();
	}
}

/**
 * ボタンレンダー.<br>
 * @author seraphy
 */
class ButtonCellRender extends DefaultTableCellRenderer {

	private static final long serialVersionUID = 1L;

	@Override
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
		return (JButton) value;
	}
}

/**
 * ボタンエディタ.<br>
 * @author seraphy
 */
class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor {

	private static final long serialVersionUID = 1L;

	public Component getTableCellEditorComponent(final JTable table, final Object value,
			final boolean isSelected, final int row, final int column) {
		final JButton orgBtn = (JButton) value;
		final JButton btn = new JButton(new AbstractAction(orgBtn.getText()) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				fireEditingCanceled();
				for (ActionListener listener : orgBtn.getActionListeners()) {
					listener.actionPerformed(e);
				}
			}
		});
		return btn;
	}

	public Object getCellEditorValue() {
		return null;
	}
}
