package charactermanaj.ui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
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.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Timestamp;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Semaphore;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;

import charactermanaj.Main;
import charactermanaj.graphics.io.PNGFileImageHeader;
import charactermanaj.model.AppConfig;
import charactermanaj.model.CharacterData;
import charactermanaj.model.PartsAuthorInfo;
import charactermanaj.model.PartsCategory;
import charactermanaj.model.PartsIdentifier;
import charactermanaj.model.PartsManageData;
import charactermanaj.model.PartsSet;
import charactermanaj.model.PartsSpec;
import charactermanaj.model.io.CharacterDataArchiveFile;
import charactermanaj.model.io.CharacterDataFileReaderWriterFactory;
import charactermanaj.model.io.CharacterDataPersistent;
import charactermanaj.model.io.ExportInfoKeys;
import charactermanaj.model.io.AbstractCharacterDataArchiveFile.CategoryLayerPair;
import charactermanaj.model.io.AbstractCharacterDataArchiveFile.PartsImageContent;
import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
import charactermanaj.util.ErrorMessageHelper;
import charactermanaj.util.LocalizedResourcePropertyLoader;


public class ImportWizardDialog extends JDialog {

	private static final long serialVersionUID = 1L;
	
	
	protected static final String STRINGS_RESOURCE = "strings/importwizdialog";
	

	public static final int EXIT_PROFILE_UPDATED = 1;
	
	public static final int EXIT_PROFILE_CREATED = 2;
	
	public static final int EXIT_CANCELED = 0;
	
	/**
	 * インポートウィザードの実行結果.
	 */
	private int exitCode = EXIT_CANCELED;
	
	/**
	 * インポートされたキャラクターデータ.<br>
	 */
	private CharacterData importedCharacterData;


	/**
	 * 現在表示中もしくは選択中のプロファイル.<br>
	 */
	protected CharacterData current;
	
	/**
	 * 他のプロファイル情報.<br>
	 * プロファイルの選択画面から呼び出された場合のみ非nullとなる.<br.
	 */
	protected Collection<CharacterData> profiles;
	
	
	private CardLayout mainPanelLayout;

	private ImportWizardCardPanel activePanel;

	private AbstractAction actNext;
	
	private AbstractAction actPrev;

	private AbstractAction actFinish;
	

	protected ImportFileSelectPanel importFileSelectPanel;
	
	protected ImportTypeSelectPanel importTypeSelectPanel;
	
	protected ImportPartsSelectPanel importPartsSelectPanel;
	
	protected ImportPresetSelectPanel importPresetSelectPanel;

	
	
	/**
	 * プロファイルにパーツデータ・プリセットデータをインポートします.<br>
	 * @param parent 親フレーム
	 * @param current 現在のプロファイル(必須)
	 */
	public ImportWizardDialog(JFrame parent, CharacterData current) {
		super(parent, true);
		if (current == null) {
			throw new IllegalArgumentException();
		}
		initComponent(parent, current, null);
	}
	
	/**
	 * 選択したプロファイルにパーツデータ、プリセットデータをインポートするか、
	 * インポートしたアーカイブが新規作成可能であれば新規作成します.<br>
	 * @param parent
	 * @param current 選択していてるプロファイル、もしくはnull
	 * @param profiles 他のプロファイルリスト、(必須、空でも良いがnullは不可)
	 */
	public ImportWizardDialog(JDialog parent, CharacterData current, Collection<CharacterData> profiles) {
		super(parent, true);
		if (profiles == null) {
			throw new IllegalArgumentException();
		}
		initComponent(parent, current, profiles);
	}
	
	/**
	 * ウィザードダイアログのコンポーネントを初期化します.<br>
	 * 新規インポートが可能になるためには、profilesはnullであってはなりません.<br>
	 * プロファイルの更新が可能になるためには、currentはnullであってはなりません.<br>
	 * @param parent 親コンテナ
	 * @param current 現在選択中のプロファイル、もしくはnull
	 * @param profiles 他のプロファイル一覧、もしくはnull
	 */
	private void initComponent(Component parent, CharacterData current, Collection<CharacterData> profiles) {
		this.current = current;
		this.profiles = profiles;

		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				onClose();
			}
		});
		
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(STRINGS_RESOURCE);

		// タイトル
		setTitle(strings.getProperty("title"));

		// メインパネル
		
		Container contentPane = getContentPane();
		contentPane.setLayout(new BorderLayout());

		final JPanel mainPanel = new JPanel();
		mainPanel.setBorder(BorderFactory.createEtchedBorder());
		this.mainPanelLayout = new CardLayout(5, 5);
		mainPanel.setLayout(mainPanelLayout);
		contentPane.add(mainPanel, BorderLayout.CENTER);
		
		ChangeListener changeListener = new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				updateBtnPanelState();
			}
		};
		
		ComponentListener componentListener = new ComponentAdapter() {
			public void componentShown(ComponentEvent e) {
				onComponentShown((ImportWizardCardPanel) e.getComponent());
			}
		};

		
		// アクション
		
		this.actNext = new AbstractAction(strings.getProperty("btn.next")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				setEnableButtons(false);
				String nextPanelName = doNext();
				if (nextPanelName != null) {
					mainPanelLayout.show(mainPanel, nextPanelName);
				} else {
					// 移動先ページ名なければ、現在のページでボタン状態を再設定する.
					// 移動先ページ名がある場合、実際に移動し表示されるまでディセーブルのままとする.
					updateBtnPanelState();
				}
			}
		};
		this.actPrev = new AbstractAction(strings.getProperty("btn.prev")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				setEnableButtons(false);
				String prevPanelName = doPrevious();
				if (prevPanelName != null) {
					mainPanelLayout.show(mainPanel, prevPanelName);
				} else {
					updateBtnPanelState();
				}
			}
		};
		this.actFinish = new AbstractAction(strings.getProperty("btn.finish")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onFinish();
			}
		};
		AbstractAction actCancel = new AbstractAction(strings.getProperty("btn.cancel")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onClose();
			}
		};

		// ImportFileSelectPanel
		this.importFileSelectPanel = new ImportFileSelectPanel();
		this.importFileSelectPanel.addComponentListener(componentListener);
		this.importFileSelectPanel.addChangeListener(changeListener);
		mainPanel.add(this.importFileSelectPanel, ImportFileSelectPanel.PANEL_NAME);
		
		// ImportTypeSelectPanel
		this.importTypeSelectPanel = new ImportTypeSelectPanel();
		this.importTypeSelectPanel.addComponentListener(componentListener);
		this.importTypeSelectPanel.addChangeListener(changeListener);
		mainPanel.add(this.importTypeSelectPanel, ImportTypeSelectPanel.PANEL_NAME);
		
		// ImportPartsSelectPanel
		this.importPartsSelectPanel = new ImportPartsSelectPanel();
		this.importPartsSelectPanel.addComponentListener(componentListener);
		this.importPartsSelectPanel.addChangeListener(changeListener);
		mainPanel.add(this.importPartsSelectPanel, ImportPartsSelectPanel.PANEL_NAME);
		
		// ImportPresetSelectPanel
		this.importPresetSelectPanel = new ImportPresetSelectPanel();
		this.importPresetSelectPanel.addComponentListener(componentListener);
		this.importPresetSelectPanel.addChangeListener(changeListener);
		mainPanel.add(this.importPresetSelectPanel, ImportPresetSelectPanel.PANEL_NAME);
		
		
		// button panel

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

		actPrev.setEnabled(false);
		actNext.setEnabled(false);
		actFinish.setEnabled(false);
		
		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.ipadx = 0;
		gbc.ipady = 0;
		gbc.insets = new Insets(3, 3, 3, 3);
		gbc.weightx = 1.;
		gbc.weighty = 0.;
		btnPanel.add(Box.createHorizontalGlue(), gbc);
		
		gbc.gridx = Main.isMacOSX() ? 2 : 1;
		gbc.gridy = 0;
		gbc.weightx = 0.;
		btnPanel.add(new JButton(this.actPrev), gbc);
		
		gbc.gridx = Main.isMacOSX() ? 3 : 2;
		gbc.gridy = 0;
		JButton btnNext = new JButton(this.actNext);
		btnPanel.add(btnNext, gbc);
		
		gbc.gridx = Main.isMacOSX() ? 4 : 3;
		gbc.gridy = 0;
		btnPanel.add(new JButton(this.actFinish), gbc);

		gbc.gridx = Main.isMacOSX() ? 1 : 4;
		gbc.gridy = 0;
		JButton btnCancel = new JButton(actCancel);
		btnPanel.add(btnCancel, gbc);

		contentPane.add(btnPanel, BorderLayout.SOUTH);
		
		// インプットマップ/アクションマップ
		
		JRootPane rootPane = getRootPane();
		
		rootPane.setDefaultButton(btnNext);
		
		InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
		ActionMap am = rootPane.getActionMap();
		im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeImportWizDialog");
		am.put("closeImportWizDialog", actCancel);

		// 表示

		setSize(500, 500);
		setLocationRelativeTo(parent);
		
		mainPanelLayout.first(mainPanel);
		updateBtnPanelState();
	}
	
	protected void onComponentShown(JPanel panel) {
		ImportWizardCardPanel activePanel = (ImportWizardCardPanel) panel;
		activePanel.onActive(this, this.activePanel);
		this.activePanel = activePanel;
		updateBtnPanelState();
	}
	
	protected void updateBtnPanelState() {
		if (activePanel != null) {
			actPrev.setEnabled(activePanel.isReadyPrevious());
			actNext.setEnabled(activePanel.isReadyNext());
			actFinish.setEnabled(activePanel.isReadyFinish());
			
		} else {
			setEnableButtons(false);
		}
	}
	
	public void setEnableButtons(boolean enabled) {
		actPrev.setEnabled(enabled);
		actNext.setEnabled(enabled);
		actFinish.setEnabled(enabled);
	}
	
	protected String doNext() {
		if (activePanel == null) {
			throw new IllegalStateException();
		}
		String nextPanelName;

		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		try {
			nextPanelName = activePanel.doNext();
		} finally {
			setCursor(Cursor.getDefaultCursor());
		}
		return nextPanelName;
	}
	
	protected String doPrevious() {
		if (activePanel == null) {
			throw new IllegalStateException();
		}
		return activePanel.doPrevious();
	}
	
	protected void onClose() {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(STRINGS_RESOURCE);
		
		if (JOptionPane.showConfirmDialog(this,
				strings.getProperty("confirm.close"),
				strings.getProperty("confirm"),
				JOptionPane.YES_NO_OPTION,
				JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) {
			return;
		}

		// アーカイブを閉じる.
		importFileSelectPanel.closeArchive();

		// キャンセル
		this.exitCode = EXIT_CANCELED;
		this.importedCharacterData = null;
		
		// ウィンドウを閉じる
		dispose();
	}

	/**
	 * インポートの実行.<br>
	 */
	protected void onFinish() {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		try {
			// 新規プロファイル作成モード時、新規作成するプロファイルと同一のIDが
			// 既存のプロファイル一覧の中にあれば、本当に新規作成するか問い合わせる.
			if (importTypeSelectPanel.isNewProfile()) {
				CharacterData newCd = importFileSelectPanel.getCharacterData();
				if (newCd == null || !newCd.isValid() || profiles == null) {
					return;
				}
				String profileId = newCd.getId();
				boolean existsSameId = false;
				for (CharacterData cd : profiles) {
					if (cd != null && cd.isValid()) {
						if (cd.getId().equals(profileId)) {
							existsSameId = true;
							break;
						}
					}
				}
				if (existsSameId) {
					int ret = JOptionPane.showConfirmDialog(this, strings
							.getProperty("existsSameProfileId"), strings
							.getProperty("confirm"), JOptionPane.YES_NO_OPTION,
							JOptionPane.QUESTION_MESSAGE);
					if (ret != JOptionPane.YES_OPTION) {
						// キャンセルした場合
						return;
					}
				}
			}

			// 新規プロファイル作成、または更新の実行
			setEnableButtons(false);
			setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			int exitCode;
			CharacterData importedCharacterData;
			try {
				if (importTypeSelectPanel.isNewProfile()) {
					// 新規作成
					importedCharacterData = createNewProfile();
					exitCode = EXIT_PROFILE_CREATED;
				} else {
					// 更新
					importedCharacterData = updateProfile();
					exitCode = EXIT_PROFILE_UPDATED;
				}
			} finally {
				setCursor(Cursor.getDefaultCursor());
			}
			
			// アーカイブを閉じる
			importFileSelectPanel.closeArchive();
			
			// 完了メッセージ
			JOptionPane.showMessageDialog(this, strings.getProperty("complete"));
			
			// 完了後、ウィンドウを閉じる.
			this.exitCode = exitCode;
			this.importedCharacterData = importedCharacterData;
			dispose();
			
		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
			// ディセーブルにしていたボタンをパネルの状態に戻す.
			updateBtnPanelState();
		}
	}
	
	/**
	 * ウィザードが閉じられた場合の終了コード
	 * @return 終了コード
	 */
	public int getExitCode() {
		return exitCode;
	}
	
	/**
	 * 新規または更新されたプロファイル、キャンセルされた場合はnull
	 * @return プロファイル
	 */
	public CharacterData getImportedCharacterData() {
		return importedCharacterData;
	}
	
	/**
	 * アーカイブからの新規プロファイルの作成
	 * @return 作成された新規プロファイル
	 * @throws IOException 失敗
	 */
	protected CharacterData createNewProfile() throws IOException {
		CharacterData cd = importFileSelectPanel.getCharacterData();
		if (cd == null || !cd.isValid()) {
			throw new IllegalStateException("imported caharcer data is invalid." + cd);
		}
		
		CharacterDataPersistent persist = CharacterDataPersistent.getInstance();

		CharacterData characterData = cd.duplicateBasicInfo(false);
		characterData.clearPartsSets(false);
		
		// プリセットをインポートする場合
		if (importTypeSelectPanel.isImportPreset()) {
			for (PartsSet partsSet : importPresetSelectPanel.getSelectedPartsSets()) {
				PartsSet ps = partsSet.clone();
				ps.setPresetParts(true);
				characterData.addPartsSet(ps);
			}
			characterData.setDefaultPartsSetId(importPresetSelectPanel.getPrefferedDefaultPartsSetId());
		}
		
		// プロファイルの新規作成
		// docBaseが設定されて返される.
		persist.createProfile(characterData);

		// インポートするパーツの更新
		if (importTypeSelectPanel.isImportPartsImages()) {
			// パーツのコピー
			Collection<PartsImageContent> partsImageContents
				= importPartsSelectPanel.getSelectedPartsImageContents();
			copyPartsImageContents(partsImageContents, characterData);

			// パーツ管理情報の登録
			PartsManageData partsManageData = importFileSelectPanel.getPartsManageData();
			updatePartsManageData(partsImageContents, partsManageData, null, characterData);
		}
		
		// インポートするピクチャの更新
		if (importTypeSelectPanel.isImportSampleImage()) {
			BufferedImage samplePicture = importFileSelectPanel.getSamplePicture();
			if (samplePicture != null) {
				persist.saveSamplePicture(characterData, samplePicture);
			}
		}
		
		return characterData;
	}
	
	/**
	 * プロファイルの更新
	 * @return 更新されたプロファイル
	 * @throws IOException 失敗
	 */
	protected CharacterData updateProfile() throws IOException {
		if (current == null || !current.isValid()) {
			throw new IllegalStateException("current profile is not valid. :" + current);
		}

		CharacterDataPersistent persist = CharacterDataPersistent.getInstance();

		CharacterData characterData = current.duplicateBasicInfo(false);
		
		boolean imported = false;
		boolean modCharacterDef = false;
		boolean modFavories = false;
		
		// インポートするパーツの更新
		if (importTypeSelectPanel.isImportPartsImages()) {
			// パーツのコピー
			Collection<PartsImageContent> partsImageContents
				= importPartsSelectPanel.getSelectedPartsImageContents();
			copyPartsImageContents(partsImageContents, characterData);
			
			// パーツ管理情報の追記・更新
			PartsManageData partsManageData = importFileSelectPanel.getPartsManageData();
			updatePartsManageData(partsImageContents, partsManageData, characterData, characterData);
			
			imported = true;
		}
		
		// インポートするピクチャの更新
		if (importTypeSelectPanel.isImportSampleImage()) {
			BufferedImage samplePicture = importFileSelectPanel.getSamplePicture();
			if (samplePicture != null) {
				persist.saveSamplePicture(characterData, samplePicture);
				imported = true;
			}
		}
		
		// インポートするパーツセットの更新
		if (importTypeSelectPanel.isImportPreset()) {
			for (PartsSet partsSet : importPresetSelectPanel.getSelectedPartsSets()) {
				PartsSet ps = partsSet.clone();
				ps.setPresetParts(false);
				characterData.addPartsSet(ps);
			}
			imported = true;
			modCharacterDef = true;
			modFavories = true;
		}
		
		// 説明の更新
		if (importTypeSelectPanel.isAddDescription() && imported) {
			URL archivedFile = importFileSelectPanel.getFile();
			String note = importTypeSelectPanel.getAdditionalDescription();
			if (note != null && note.length() > 0) {
				String description = characterData.getDescription();
				if (description == null) {
					description = "";
				}
				String lf = System.getProperty("line.separator");
				Timestamp tm = new Timestamp(System.currentTimeMillis());
				description += lf + "--- import: " + tm + " : " + archivedFile + " ---" + lf;
				description += note + lf;
				characterData.setDescription(description);
				modCharacterDef = true;
			}
		}
		
		// キャラクター定義の更新
		if (modCharacterDef) {
			persist.updateProfile(characterData, false); // キャラクター定義の構造に変化なし
			
			// XXX: プロファイルの再読み込みは実装されていないので現在のインスタンスに直接更新かける。これをどうするかは今後の課題。
			current.setDescription(characterData.getDescription());
		}
		// お気に入りの更新
		if (modFavories) {
			persist.saveFavorites(characterData);
		}
		
		return characterData;
	}

	/**
	 * パーツ管理情報を更新または作成する.<br>
	 * パーツ管理情報がnullまたは空であれば何もしない.<br>
	 * そうでなければインポートするパーツに該当するパーツ管理情報を、現在のプロファイルのパーツ管理情報に追記・更新し、
	 * それを書き出す.<br>
	 * インポートもとにパーツ管理情報がなく、既存にある場合、インポートしても管理情報は削除されません.(上書きセマンティクスのため)
	 * @param partsImageContents インポートするパーツ
	 * @param partsManageData パーツ管理データ
	 * @param current 現在のパーツ管理データを保持しているプロファイル、新規の場合はnull
	 * @param target 書き込み先のプロファイル
	 * @throws IOException 書き込みに失敗した場合
	 */
	protected void updatePartsManageData(
			Collection<PartsImageContent> partsImageContents,
			PartsManageData partsManageData, CharacterData current,
			CharacterData target) throws IOException {
		if (target == null || !target.isValid()) {
			throw new IllegalArgumentException();
		}
		
		if (partsImageContents == null || partsImageContents.isEmpty()
				|| partsManageData == null || partsManageData.isEmpty()) {
			// インポートするパーツが存在しないか、管理情報がない場合は更新しようがないので何もしないで戻る.
			return;
		}

		CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
		
		PartsManageData mergedPartsManagedData;
		if (current != null && current.isValid()) {
			// 現在のプロファイルからパーツ管理情報を取得する.
			mergedPartsManagedData = persist.loadPartsManageData(current.getDocBase());
		} else {
			// 新規の場合は空
			mergedPartsManagedData = new PartsManageData();
		}
		
		// インポート対象のパーツに該当するパーツ管理情報のみを取り出して追記する.
		for (PartsImageContent partsImageContent : partsImageContents) {
			String partsName = partsImageContent.getPartsName();
			for (CategoryLayerPair catLayerPair : partsImageContent.getCategoryLayerPairs()) {
				PartsCategory partsCategory = catLayerPair.getPartsCategory();
				String categoryId = partsCategory.getCategoryId();
				
				PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsName, categoryId);
				
				PartsAuthorInfo partsAuthorInfo = partsManageData.getPartsAuthorInfo(partsKey);
				PartsManageData.PartsVersionInfo versionInfo = partsManageData.getVersion(partsKey);
				String localizedName = partsManageData.getLocalizedName(partsKey);
				
				if (partsAuthorInfo != null || versionInfo != null || localizedName != null) {
					// いずれかの情報の登録がある場合、パーツ管理情報として追記する.
					mergedPartsManagedData.putPartsInfo(partsKey, localizedName, partsAuthorInfo, versionInfo);
				}
			}
		}

		// パーツ管理情報を更新する.
		persist.savePartsManageData(target.getDocBase(), mergedPartsManagedData);
	}
	
	/**
	 * パーツデータをプロファイルの画像ディレクトリに一括コピーする.<br>
	 * @param partsImageContents コピー対象のパーツデータ
	 * @param cd コピー先のプロファイル
	 * @throws IOException 失敗
	 */
	protected void copyPartsImageContents(
			Collection<PartsImageContent> partsImageContents, CharacterData cd)
			throws IOException {
		if (cd == null || cd.getDocBase() == null) {
			throw new IllegalArgumentException("invalid character data");
		}

		// コピー先ディレクトリの確定
		URL docbase = cd.getDocBase();
		if (!docbase.getProtocol().equals("file")) {
			throw new IOException("not file protocol: " + docbase);
		}
		File configFile = new File(docbase.getPath());
		File baseDir = configFile.getParentFile();
		if (baseDir == null || !baseDir.isDirectory()) {
			throw new IOException("not a directory. " + baseDir);
		}

		AppConfig appConfig = AppConfig.getInstance();
		byte[] stmbuf = new byte[appConfig.getFileTransferBufferSize()];
		
		// ファイルコピー
		for (PartsImageContent content : partsImageContents) {
			InputStream is = new BufferedInputStream(content.openStream());
			try {
				File outDir = new File(baseDir, content.getDirName());
				outDir.mkdirs();
				File outFile = new File(outDir, content.getFileName());
				OutputStream os = new BufferedOutputStream(new FileOutputStream(outFile));
				try {
					for (;;) {
						int rd = is.read(stmbuf);
						if (rd < 0) {
							break;
						}
						os.write(stmbuf, 0, rd);
					}
				} finally {
					os.close();
				}
				outFile.setLastModified(content.lastModified());
				
			} finally {
				is.close();
			}
		}
	}
}

/**
 * タブの抽象基底クラス.<br>
 * @author seraphy
 */
abstract class ImportWizardCardPanel extends JPanel {

	private static final long serialVersionUID = 1L;
	
	private LinkedList<ChangeListener> listeners = new LinkedList<ChangeListener>();
	
	public void addChangeListener(ChangeListener l) {
		if (l != null) {
			listeners.add(l);
		}
	}
	
	public void removeChangeListener(ChangeListener l) {
		if (l != null) {
			listeners.remove(l);
		}
	}
	
	public void fireChangeEvent() {
		ChangeEvent e = new ChangeEvent(this);
		for (ChangeListener l : listeners) {
			l.stateChanged(e);
		}
	}
	
	public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
		// なにもしない
	}
	
	public boolean isReadyPrevious() {
		return false;
	}
	
	public boolean isReadyNext() {
		return false;
	}
	
	public boolean isReadyFinish() {
		return false;
	}

	public String doNext() {
		throw new UnsupportedOperationException();
	}
	
	public String doPrevious() {
		throw new UnsupportedOperationException();
	}
}

/**
 * ファイル選択パネル
 * @author seraphy
 */
class ImportFileSelectPanel extends ImportWizardCardPanel {

	private static final long serialVersionUID = 1L;
	
	public static final String PANEL_NAME = "fileSelectPanel";

	/**
	 * アーカイブ用ファイルダイアログ
	 */
	private static ArchiveFileDialog archiveFileDialog = new ArchiveFileDialog();

	private ImportWizardDialog parent;
	
	/**
	 * ファイル名を指定してインポート
	 */
	private JRadioButton radioFileArchive;
	
	/**
	 * ファイル名入力ボックス 
	 */
	private JTextField txtArchiveFile;
	
	/**
	 * ファイル選択ボタン
	 */
	private Action actChooseFile;


	/**
	 * ディレクトリを指定してインポート
	 */
	private JRadioButton radioDirectory;
	
	/**
	 * ディレクトリ入力ボックス
	 */
	private JTextField txtDirectory;
	
	/**
	 * ディレクトリ選択ボタン
	 */
	private Action actChooseDirectory;

	
	/**
	 * URLを指定してインポート
	 */
	private JRadioButton radioDownload;
	
	/**
	 * ダウンロード入力ボックス
	 */
	private JTextField txtDownload;

	/**
	 * URL一覧テーブル
	 */
	private JTable urlsTable;
	
	/**
	 * URL一覧テーブルモデル
	 */
	private URLTableModel urlsTableModel;

	
	
	/* 以下、対象ファイルの読み取り結果 */
	
	private URL importFile;
	
	private CharacterDataArchiveFile archiveFile;
	
	private CharacterData characterData;
	
	private BufferedImage samplePicture;
	
	private String readme;
	
	private Properties exportInfoProp;
	
	private Collection<PartsImageContent> partsImageContentsMapForCurrentProfile;
	
	private Collection<PartsImageContent> partsImageContentsMapForNewProfile;
	
	private PartsManageData partsManageData;
	
	public ImportFileSelectPanel() {
		setLayout(new BorderLayout());
		
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		DocumentListener documentListener = new DocumentListener() {
			public void removeUpdate(DocumentEvent e) {
				fireEvent();
			}
			public void insertUpdate(DocumentEvent e) {
				fireEvent();
			}
			public void changedUpdate(DocumentEvent e) {
				fireEvent();
			}
			protected void fireEvent() {
				fireChangeEvent();
			}
		};
		
		urlsTableModel = new URLTableModel();
		
		txtArchiveFile = new JTextField();
		txtDirectory = new JTextField();
		txtDownload = new JTextField();

		txtArchiveFile.getDocument().addDocumentListener(documentListener);
		txtDirectory.getDocument().addDocumentListener(documentListener);
		txtDownload.getDocument().addDocumentListener(documentListener);
		txtDownload.getDocument().addDocumentListener(new DocumentListener() {
			public void changedUpdate(DocumentEvent e) {
				onDownloadURLEdited(false);
			}
			public void insertUpdate(DocumentEvent e) {
				onDownloadURLEdited(false);
			}
			public void removeUpdate(DocumentEvent e) {
				onDownloadURLEdited(false);
			}
		});
		
		actChooseFile = new AbstractAction(strings.getProperty("browse")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onChooseFile();
			}
		};
		
		actChooseDirectory = new AbstractAction(strings.getProperty("browse")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onChooseDirectory();
			}
		};
		

		JPanel fileChoosePanel = new JPanel();
		GridBagLayout fileChoosePanelLayout = new GridBagLayout();
		fileChoosePanel.setLayout(fileChoosePanelLayout);
		
		radioFileArchive = new JRadioButton(strings.getProperty("importingArchiveFile"));
		radioDirectory = new JRadioButton(strings.getProperty("importingDirectory"));
		radioDownload = new JRadioButton(strings.getProperty("importingURL"));

		ChangeListener radioChangeListener = new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				updateUIState();
				fireChangeEvent();
			}
		};
		radioFileArchive.addChangeListener(radioChangeListener);
		radioDirectory.addChangeListener(radioChangeListener);
		radioDownload.addChangeListener(radioChangeListener);

		ButtonGroup btnGroup = new ButtonGroup();
		btnGroup.add(radioFileArchive);
		btnGroup.add(radioDirectory);
		btnGroup.add(radioDownload);
		
		GridBagConstraints gbc = new GridBagConstraints();

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

		gbc.gridx = 2;
		gbc.gridy = 1;
		gbc.ipadx = 0;
		gbc.weightx = 0.;
		fileChoosePanel.add(new JButton(actChooseFile), gbc);

		gbc.gridx = 0;
		gbc.gridy = 2;
		gbc.ipadx = 0;
		gbc.gridwidth = 3;
		gbc.weightx = 1.;
		fileChoosePanel.add(radioDirectory, gbc);

		gbc.gridx = 0;
		gbc.gridy = 3;
		gbc.ipadx = 45;
		gbc.gridwidth = 1;
		gbc.weightx = 0;
		fileChoosePanel.add(Box.createHorizontalGlue(), gbc);
		
		gbc.gridx = 1;
		gbc.gridy = 3;
		gbc.ipadx = 0;
		gbc.weightx = 1.;
		fileChoosePanel.add(txtDirectory, gbc);

		gbc.gridx = 2;
		gbc.gridy = 3;
		gbc.ipadx = 0;
		gbc.weightx = 0.;
		fileChoosePanel.add(new JButton(actChooseDirectory), gbc);

		gbc.gridx = 0;
		gbc.gridy = 4;
		gbc.ipadx = 0;
		gbc.gridwidth = 3;
		gbc.weightx = 1.;
		fileChoosePanel.add(radioDownload, gbc);
		
		gbc.gridx = 0;
		gbc.gridy = 5;
		gbc.ipadx = 45;
		gbc.gridwidth = 1;
		gbc.weightx = 0;
		fileChoosePanel.add(Box.createHorizontalGlue(), gbc);
		
		gbc.gridx = 1;
		gbc.gridy = 5;
		gbc.ipadx = 0;
		gbc.gridwidth = 2;
		gbc.weightx = 1.;
		fileChoosePanel.add(txtDownload, gbc);
		
		gbc.gridx = 0;
		gbc.gridy = 6;
		gbc.ipadx = 45;
		gbc.gridwidth = 1;
		gbc.weightx = 0;
		fileChoosePanel.add(Box.createHorizontalGlue(), gbc);

		AppConfig appConfig = AppConfig.getInstance();
		final Color disabledForeground = appConfig.getDisabledCellForgroundColor();

		urlsTable = new JTable(urlsTableModel) {
			private static final long serialVersionUID = 1L;
			
			public Component prepareRenderer(TableCellRenderer renderer,
					int row, int column) {
				Component comp = super.prepareRenderer(renderer, row, column);
				if (comp instanceof JCheckBox) {
					// BooleanのデフォルトのレンダラーはKCheckBoxを継承したJTable$BooleanRenderer
					comp.setEnabled(isCellEditable(row, column) && isEnabled());
				}
				comp.setForeground(!isEnabled() ?
						disabledForeground : (isCellSelected(row,column) ? getSelectionForeground() : getForeground()));
				comp.setBackground(!isEnabled() ?
						getBackground() : (isCellSelected(row, column) ? getSelectionBackground() : getBackground()));
				return comp;
			}
		};
		JScrollPane urlsTableSP = new JScrollPane(urlsTable);
		urlsTable.setShowGrid(true);
		urlsTable.setGridColor(appConfig.getGridColor());
		urlsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		urlsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
		urlsTableModel.adjustColumnModel(urlsTable.getColumnModel());

		ListSelectionModel listSelectionModel = urlsTable.getSelectionModel();
		listSelectionModel.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
				onDownloadURLEdited(true);
			}
		});
		listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		urlsTable.setRowSelectionAllowed(true);
		
		gbc.gridx = 1;
		gbc.gridy = 6;
		gbc.ipadx = 0;
		gbc.gridwidth = 2;
		gbc.weightx = 1.;
		gbc.weighty = 1.;
		fileChoosePanel.add(urlsTableSP, gbc);
		
		add(fileChoosePanel, BorderLayout.CENTER);
		
		updateUIState();
	}
	
	protected void updateUIState() {
		boolean enableArchiveFile = radioFileArchive.isSelected();
		boolean enableDirectory = radioDirectory.isSelected();
		boolean enableDownload = radioDownload.isSelected();
		
		txtArchiveFile.setEnabled(enableArchiveFile);
		actChooseFile.setEnabled(enableArchiveFile);
		
		txtDirectory.setEnabled(enableDirectory);
		actChooseDirectory.setEnabled(enableDirectory);
		
		txtDownload.setEnabled(enableDownload);
		urlsTable.setEnabled(enableDownload);
	}
	
	private Semaphore downloadURLEditSempaphore = new Semaphore(1);
	
	protected void onDownloadURLEdited(boolean selected) {
		if (downloadURLEditSempaphore.tryAcquire()) {
			try {
				if (selected) {
					// テーブルのURLを選択したら、ダウンロードURL入力ボックスに転記する.
					int selRow = urlsTable.getSelectedRow();
					if (selRow >= 0) {
						URLTableRow row = urlsTableModel.getRow(selRow);
						txtDownload.setText(row.getDownloadURL());
					}
				} else {
					// ダウンロードURL入力ボックスが変更されたら、テーブルのURL選択のフォーカスを解除する.
					urlsTable.getSelectionModel().clearSelection();
				}
			} finally {
				downloadURLEditSempaphore.release();
			}
		}
	}
	
	protected void onChooseFile() {
		File initFile = null;
		if (txtArchiveFile.getText().trim().length() > 0) {
			initFile = new File(txtArchiveFile.getText());
		}
		File file = archiveFileDialog.showOpenDialog(this, initFile);
		if (file != null) {
			txtArchiveFile.setText(file.getPath());
			fireChangeEvent();
		}
	}
	
	protected void onChooseDirectory() {
		String directoryTxt = txtDirectory.getText();
		JFileChooser dirChooser = new JFileChooser(directoryTxt);
		dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

		if (dirChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
			return;
		}
		File dir = dirChooser.getSelectedFile();
		if (dir != null) {
			txtDirectory.setText(dir.getPath());
			fireChangeEvent();
		}
	}

	@Override
	public boolean isReadyNext() {
		if (radioFileArchive.isSelected()) {
			String fileTxt = txtArchiveFile.getText();
			if (fileTxt != null && fileTxt.trim().length() > 0) {
				return true;
			}
		} else if (radioDirectory.isSelected()) {
			String directoryTxt = txtDirectory.getText();
			if (directoryTxt != null && directoryTxt.trim().length() > 0) {
				return true;
			}
		} else if (radioDownload.isSelected()) {
			String downloadTxt = txtDownload.getText();
			if (downloadTxt != null && downloadTxt.trim().length() > 0) {
				return downloadTxt.startsWith("http://") || downloadTxt.startsWith("ftp://");
			}
		}
		return false;
	}
	
	@Override
	public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
		this.parent = parent;
		
		// 開いているアーカイブがあれば閉じる
		closeArchive();
		
		// リセットする.
		importFile = null;
		archiveFile = null;
		characterData = null;
		exportInfoProp = null;
		samplePicture = null;
		readme = null;
		partsImageContentsMapForCurrentProfile = null;
		partsImageContentsMapForNewProfile = null;
		partsManageData = null;
		
		// 現在のプロファイルのキャラクターデータからパーツのダウンロードURL情報を取り出す
		urlsTableModel.initModel(parent.current);
	}
	
	/**
	 * 開いているアーカイブがあればクローズする.
	 */
	public void closeArchive() {
		if (archiveFile != null) {
			try {
				archiveFile.close();

			} catch (IOException ex) {
				ErrorMessageHelper.showErrorDialog(this, ex);
				// エラーが発生しても、とりあえず無視する.
			}
			archiveFile = null;
		}
	}
	
	@Override
	public String doNext() {
		if (!isReadyNext()) {
			return null;
		}

		Properties strings = LocalizedResourcePropertyLoader.getInstance()
			.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		URL importArchive;
		try {
			if (radioFileArchive.isSelected()) {
				// ファイルによるインポート
				File file = new File(txtArchiveFile.getText());
				if (!file.exists() || !file.isFile()) {
					JOptionPane.showMessageDialog(this, strings
							.getProperty("fileNotFound"), "ERROR",
							JOptionPane.ERROR_MESSAGE);
					return null;
				}
				importArchive = file.toURL();
			} else if (radioDirectory.isSelected()) {
				// ディレクトリによるインポート
				File file = new File(txtDirectory.getText());
				if (!file.exists() || !file.isDirectory()) {
					JOptionPane.showMessageDialog(this, strings
							.getProperty("directoryNotFound"), "ERROR",
							JOptionPane.ERROR_MESSAGE);
					return null;
				}
				importArchive = file.toURL();
			} else if (radioDownload.isSelected()) {
				// URLによるインポート
				importArchive = new URL(txtDownload.getText());
			} else {
				// それ以外はサポートしていない.
				return null;
			}
		} catch (MalformedURLException ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
			return null;
		}
		
		try {
			CharacterDataFileReaderWriterFactory factory = CharacterDataFileReaderWriterFactory.getInstance();
			
			this.importFile = importArchive;
			archiveFile = factory.openArchive(importFile);

			readme = archiveFile.readReadMe();
			characterData = archiveFile.readCharacterData();
			if (characterData == null) {
				// character.xmlがない場合は、character.iniで試行する.
				characterData = archiveFile.readCharacterINI();
				if (characterData != null) {
					// character.iniがあった場合は、エクスポートプロパティを推定して生成しておく
					exportInfoProp = new Properties();
					exportInfoProp.setProperty(ExportInfoKeys.EXPORT_SUBSET, "false");
					exportInfoProp.setProperty(ExportInfoKeys.EXPORT_PRESETS, "true");
					exportInfoProp.setProperty(ExportInfoKeys.EXPORT_PARTS_IMAGES, "true");
					// readmeがあれば、それを説明として登録しておく
					if (readme != null && readme.trim().length() > 0) {
						characterData.setDescription(readme);
					}
				} else {
					exportInfoProp = null;
				}
			} else {
				exportInfoProp = archiveFile.readExportProp();
			}
			samplePicture = archiveFile.readSamplePicture();
			partsImageContentsMapForCurrentProfile = archiveFile.getPartsImageContents(parent.current);
			partsImageContentsMapForNewProfile = archiveFile.getPartsImageContents(characterData);
			partsManageData = archiveFile.getPartsManageData();
			
			// 読み込めたら次ページへ
			return ImportTypeSelectPanel.PANEL_NAME;

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

	public URL getFile() {
		return importFile;
	}
	
	public CharacterDataArchiveFile getArchiveFile() {
		return archiveFile;
	}

	public CharacterData getCharacterData() {
		return characterData;
	}

	public BufferedImage getSamplePicture() {
		return samplePicture;
	}

	public String getReadme() {
		return readme;
	}

	public Properties getExportInfoProp() {
		return exportInfoProp;
	}

	public Collection<PartsImageContent> getPartsImageContentsForCurrentProfile() {
		return partsImageContentsMapForCurrentProfile;
	}

	public Collection<PartsImageContent> getPartsImageContentsForNewProfile() {
		return partsImageContentsMapForNewProfile;
	}
	
	public PartsManageData getPartsManageData() {
		return partsManageData;
	}
}

class URLTableRow implements Serializable {
	
	private static final long serialVersionUID = 3452190266438145049L;

	private String downloadURL;
	
	private String author;
	
	public String getAuthor() {
		return author;
	}
	
	public String getDownloadURL() {
		return downloadURL;
	}
	
	public void setAuthor(String author) {
		this.author = author;
	}
	
	public void setDownloadURL(String downloadURL) {
		this.downloadURL = downloadURL;
	}
	
}

class URLTableModel extends AbstractTableModelWithComboBoxModel<URLTableRow> {

	private static final long serialVersionUID = 7075478118793390224L;

	private static final String[] COLUMN_NAMES;
	
	private static final int[] COLUMN_WIDTHS;
	
	static {
		COLUMN_NAMES = new String[] {
				"作者",
				"URL",
		};
		COLUMN_WIDTHS = new int[] {
				100,
				300,
		};
	}
	
	@Override
	public String getColumnName(int column) {
		return COLUMN_NAMES[column];
	}
	
	public int getColumnCount() {
		return COLUMN_NAMES.length;
	}
	
	public Object getValueAt(int rowIndex, int columnIndex) {
		URLTableRow row = getRow(rowIndex);
		switch (columnIndex) {
		case 0:
			return row.getAuthor();
		case 1:
			return row.getDownloadURL();
		}
		return "";
	}
	
	@Override
	public Class<?> getColumnClass(int columnIndex) {
		return String.class;
	}
	

	public void adjustColumnModel(TableColumnModel columnModel) {
		for (int idx = 0; idx < COLUMN_WIDTHS.length; idx++) {
			columnModel.getColumn(idx).setPreferredWidth(COLUMN_WIDTHS[idx]);
		}
	}
	
	public void initModel(CharacterData characterData) {
		clear();
		HashMap<String, String> downloadUrlsMap = new HashMap<String, String>();
		if (characterData != null) {
			for (PartsCategory category : characterData.getPartsCategories()) {
				for (Map.Entry<PartsIdentifier, PartsSpec> entry : characterData
						.getPartsSpecMap(category).entrySet()) {
					PartsSpec partsSpec = entry.getValue();
					String author = partsSpec.getAuthor();
					String downloadURL = partsSpec.getDownloadURL();
					if (downloadURL != null && downloadURL.trim().length() > 0) {
						if (author == null || author.trim().length() == 0) {
							author = "";
						}
						downloadUrlsMap.put(downloadURL, author);
					}
				}
			}
		}
		
		for (Map.Entry<String, String> entry : downloadUrlsMap.entrySet()) {
			String downloadURL = entry.getKey();
			String author = entry.getValue();
			URLTableRow row = new URLTableRow();
			row.setDownloadURL(downloadURL);
			row.setAuthor(author);
			addRow(row);
		}
		
		Collections.sort(elements, new Comparator<URLTableRow>() {
			public int compare(URLTableRow o1, URLTableRow o2) {
				int ret = o1.getAuthor().compareTo(o2.getAuthor());
				if (ret == 0) {
					ret = o1.getDownloadURL().compareTo(o2.getDownloadURL());
				}
				return ret;
			}
		});
		
		fireTableDataChanged();
	}
}


/**
 * ファイル選択パネル
 * @author seraphy
 */
class ImportTypeSelectPanel extends ImportWizardCardPanel {
	
	private static final long serialVersionUID = 1L;

	public static final String PANEL_NAME = "importTypeSelectPanel";
	
	private SamplePicturePanel samplePicturePanel;
	
	private JTextField txtCharacterId;
	
	private JTextField txtCharacterRev;

	private JTextField txtCharacterName;
	
	private JTextField txtAuthor;
	
	private JTextArea txtDescription;
	
	private JCheckBox chkCharacterDef;
	
	private JCheckBox chkPartsImages;
	
	private JCheckBox chkPresets;
	
	private JCheckBox chkSampleImage;
	
	private JCheckBox chkExportSubset;
	
	private JCheckBox chkAddDescription;
	
	private String additionalDescription;
	

	/* 以下、選択結果 */
	
	private boolean noContents; 
	
	
	public ImportTypeSelectPanel() {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		GridBagLayout basicPanelLayout = new GridBagLayout();
		GridBagConstraints gbc = new GridBagConstraints();
		setLayout(basicPanelLayout);

		JPanel contentsSpecPanel = new JPanel();
		contentsSpecPanel.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory
						.createTitledBorder(strings
								.getProperty("basic.contentsSpec"))));
		BoxLayout contentsSpecPanelLayout = new BoxLayout(contentsSpecPanel,
				BoxLayout.PAGE_AXIS);
		contentsSpecPanel.setLayout(contentsSpecPanelLayout);

		chkCharacterDef = new JCheckBox(strings
				.getProperty("characterdef"));
		chkPartsImages = new JCheckBox(strings
				.getProperty("basic.chk.partsImages"));
		chkPresets = new JCheckBox(strings.getProperty("basic.chk.presets"));
		chkSampleImage = new JCheckBox(strings
				.getProperty("basic.chk.samplePicture"));

		contentsSpecPanel.add(chkCharacterDef);
		contentsSpecPanel.add(chkPartsImages);
		contentsSpecPanel.add(chkPresets);
		contentsSpecPanel.add(chkSampleImage);

		// /

		JPanel archiveInfoPanel = new JPanel();
		archiveInfoPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
				.createEmptyBorder(5, 5, 5, 5), BorderFactory
				.createTitledBorder(strings.getProperty("basic.archiveInfo"))));
		Dimension archiveInfoPanelMinSize = new Dimension(300, 200);
		archiveInfoPanel.setMinimumSize(archiveInfoPanelMinSize);
		archiveInfoPanel.setPreferredSize(archiveInfoPanelMinSize);
		GridBagLayout commentPanelLayout = new GridBagLayout();
		archiveInfoPanel.setLayout(commentPanelLayout);

		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridheight = 1;
		gbc.gridwidth = 2;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		gbc.insets = new Insets(3, 3, 3, 3);
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.BOTH;
		chkExportSubset = new JCheckBox(strings.getProperty("exportSubset"));
		chkExportSubset.setEnabled(false); // 読み取り専用
		archiveInfoPanel.add(chkExportSubset, gbc);

		gbc.gridx = 0;
		gbc.gridy = 1;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileId"), JLabel.RIGHT), gbc);

		gbc.gridx = 1;
		gbc.gridy = 1;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		txtCharacterId = new JTextField();
		txtCharacterId.setEditable(false); // 読み取り専用
		archiveInfoPanel.add(txtCharacterId, gbc);
		
		gbc.gridx = 0;
		gbc.gridy = 2;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileRev"), JLabel.RIGHT), gbc);

		gbc.gridx = 1;
		gbc.gridy = 2;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		txtCharacterRev = new JTextField();
		txtCharacterRev.setEditable(false); // 読み取り専用
		archiveInfoPanel.add(txtCharacterRev, gbc);

		gbc.gridx = 0;
		gbc.gridy = 3;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileName"), JLabel.RIGHT), gbc);

		gbc.gridx = 1;
		gbc.gridy = 3;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		txtCharacterName = new JTextField();
		txtCharacterName.setEditable(false); // 読み取り専用
		archiveInfoPanel.add(txtCharacterName, gbc);

		gbc.gridx = 0;
		gbc.gridy = 4;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		archiveInfoPanel.add(
				new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc);

		gbc.gridx = 1;
		gbc.gridy = 4;
		gbc.gridwidth = 1;
		gbc.weightx = 1.;
		txtAuthor = new JTextField();
		txtAuthor.setEditable(false); // 読み込み専用
		archiveInfoPanel.add(txtAuthor, gbc);

		gbc.gridx = 0;
		gbc.gridy = 5;
		gbc.gridwidth = 1;
		gbc.gridheight = 1;
		gbc.weightx = 0.;
		archiveInfoPanel.add(new JLabel(strings.getProperty("description"),
				JLabel.RIGHT), gbc);

		gbc.gridx = 1;
		gbc.gridy = 5;
		gbc.gridwidth = 1;
		gbc.gridheight = 5;
		gbc.weighty = 1.;
		gbc.weightx = 1.;
		txtDescription = new JTextArea(); // 説明は更新可能にしておく。
		txtDescription.setFont(getFont());
		archiveInfoPanel.add(new JScrollPane(txtDescription), gbc);

		gbc.gridx = 0;
		gbc.gridy = 10;
		gbc.gridheight = 1;
		gbc.gridwidth = 2;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		gbc.weighty = 0.;
		gbc.weightx = 0.;
		chkAddDescription = new JCheckBox(strings.getProperty("appendDescription"));
		archiveInfoPanel.add(chkAddDescription, gbc);

		// /

		samplePicturePanel = new SamplePicturePanel();
		JScrollPane samplePicturePanelSP = new JScrollPane(samplePicturePanel);
		samplePicturePanelSP.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory
						.createTitledBorder(strings
								.getProperty("basic.sampleImage"))));

		// /
		
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridheight = 1;
		gbc.gridwidth = 2;
		gbc.weightx = 1.;
		gbc.weighty = 0.;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.BOTH;
		add(contentsSpecPanel, gbc);

		gbc.gridx = 0;
		gbc.gridy = 1;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 0.;
		gbc.weighty = 1.;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.BOTH;
		add(archiveInfoPanel, gbc);

		gbc.gridx = 1;
		gbc.gridy = 1;
		gbc.gridheight = 1;
		gbc.gridwidth = 1;
		gbc.weightx = 1;
		gbc.weighty = 1.;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.BOTH;
		add(samplePicturePanelSP, gbc);

		// アクションリスナ

		ActionListener modListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				fireChangeEvent();
			}
		};
		chkCharacterDef.addActionListener(modListener);
		chkPartsImages.addActionListener(modListener);
		chkPresets.addActionListener(modListener);
		chkSampleImage.addActionListener(modListener);
		chkAddDescription.addActionListener(modListener);
	}

	@Override
	public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
		if (previousPanel == parent.importPartsSelectPanel) {
			return;
		}

		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		// 呼び出しもと情報
		Collection<CharacterData> profiles = parent.profiles;
		CharacterData current = parent.current;
		
		// キャラクター定義情報
		CharacterData cd = parent.importFileSelectPanel.getCharacterData();
		Properties exportInfoProp = parent.importFileSelectPanel.getExportInfoProp();
		boolean partsFlag = true;
		String readme = null;

		// 開いているか選択しているプロファイルが有効である場合、そのプロファイルへの更新が可能。
		final boolean updatable = (current != null && current.isValid());
		// アーカイブ上のプロファイルが有効であり、profilesがnullでない場合、新規作成が可能。
		final boolean creatable = (profiles != null) && (cd != null && cd.isValid());
		
		// ID、REVが一致するか?
		boolean matchID = false;
		boolean matchREV = false;

		if (cd != null && cd.isValid()) {
			
			boolean subsetFlag = true;
			boolean presetFlag = false;
			partsFlag = false;
			if (exportInfoProp != null) {
				subsetFlag = Boolean.parseBoolean(exportInfoProp.getProperty(ExportInfoKeys.EXPORT_SUBSET));
				presetFlag = Boolean.parseBoolean(exportInfoProp.getProperty(ExportInfoKeys.EXPORT_PRESETS));
				partsFlag = Boolean.parseBoolean(exportInfoProp.getProperty(ExportInfoKeys.EXPORT_PARTS_IMAGES));
			}
			
			if (!updatable) {
				// 現在有効なプロファイルがなく、インポートもとにキャラクター定義がある場合
				// 新規作成のみ可に固定する。
				// ただし、部分エクスポートもしくは作成可能でなければ常に不可に固定する。
				chkCharacterDef.setEnabled(false);
				chkCharacterDef.setSelected(!subsetFlag && creatable);
			} else {
				// 現在有効なプロファイルが更新可能であれば
				// 部分エクスポートでなく新規作成可能である場合のみ、新規作成可とする。
				// そうでなければ常に新規作成不可で固定する。
				chkCharacterDef.setEnabled(!subsetFlag && creatable);
				chkCharacterDef.setSelected(false);
			}
			
			chkPresets.setEnabled(presetFlag && (updatable || creatable)); // エクスポート時にプリセットが有効であれば選択可
			chkPresets.setSelected(presetFlag); // プリセットが有効であればデフォルトで選択

			txtCharacterId.setText(cd.getId());
			txtCharacterRev.setText(cd.getRev());
			txtCharacterName.setText(cd.getName());
			
			if (current != null) {
				// 既存のプロファイルを選択していてインポート結果のキャラクター定義がある場合はID, REVを比較する.
				matchID = current.getId() == null ? cd.getId() == null : current.getId().equals(cd.getId());
				matchREV = current.getRev() == null ? cd.getRev() == null : current.getRev().equals(cd.getRev());
			} else {
				// 既存のプロファイルが存在しない場合は、ID,REVの比較は成功とみなす
				matchID = true;
				matchREV = true;
			}
			
			AppConfig appConfig = AppConfig.getInstance();
			Color invalidBgColor = appConfig.getInvalidBgColor();
			
			txtCharacterId.setBackground(matchID ? getBackground() : invalidBgColor);
			txtCharacterRev.setBackground(matchREV ? getBackground() : invalidBgColor);
			
			txtAuthor.setText(cd.getAuthor());
			readme = cd.getDescription(); 
			
			chkExportSubset.setSelected(subsetFlag);

		} else {
			// キャラクター定義がない場合、
			// 新規作成は常に不可で固定
			chkCharacterDef.setEnabled(false);
			chkCharacterDef.setSelected(false);

			// プリセットも存在しないので常に不可で固定
			chkPresets.setEnabled(false);
			chkPresets.setSelected(false);

			// ID, REV等は存在しないので空にする
			txtCharacterId.setText("");
			txtCharacterRev.setText("");
			txtCharacterName.setText("");
			txtAuthor.setText("");
			
			// readmeで代用
			readme = parent.importFileSelectPanel.getReadme();

			// 常に部分インポートとみなす
			chkExportSubset.setSelected(true);
		}
		
		// 説明を追記する.
		boolean existsReadme = (readme != null && readme.trim().length() > 0);
		additionalDescription = existsReadme ? readme : "";
		txtDescription.setText(additionalDescription);
		chkAddDescription.setEnabled((updatable || creatable) && existsReadme);
		chkAddDescription.setSelected((updatable || creatable) && existsReadme);
		
		// パーツイメージ
		Collection<PartsImageContent> partsImageContentsMapForCur = parent.importFileSelectPanel
				.getPartsImageContentsForCurrentProfile();
		Collection<PartsImageContent> partsImageContentsMapForNew = parent.importFileSelectPanel
		.getPartsImageContentsForNewProfile();
		
		if (!partsFlag
			|| !((updatable && !partsImageContentsMapForCur.isEmpty()) || (creatable && !partsImageContentsMapForNew.isEmpty()))
			|| !(updatable || creatable)) {
			// パーツイメージがエクスポートしていないか、更新可能時に更新可能なイメージがなく、且つ、作成可能時に作成可能ファイルがないか、
			// 現在のプロファイルが無効で且つキャラクターデータも読み込まれていないため作成できない場合は無効
			chkPartsImages.setEnabled(false);
			chkPartsImages.setSelected(false);
		} else {
			chkPartsImages.setEnabled(true);
			chkPartsImages.setSelected(true); // パーツがあればデフォルトで選択
		}
		
		// サンプルピクチャ
		BufferedImage samplePicture = parent.importFileSelectPanel.getSamplePicture();
		if (samplePicture != null && (updatable || creatable)) {
			// サンプルピクチャが存在し、インポートか新規作成が可能であれば有効にする.
			samplePicturePanel.setSamplePicture(samplePicture);
			chkSampleImage.setEnabled(true);
			chkSampleImage.setSelected(false);
		} else {
			samplePicturePanel.setSamplePicture(samplePicture);
			chkSampleImage.setEnabled(false);
			chkSampleImage.setSelected(false);
		}
		
		noContents = !chkCharacterDef.isEnabled()
				&& !chkPartsImages.isEnabled() && !chkPresets.isEnabled()
				&& !chkSampleImage.isEnabled();
		
		if (noContents) {
			JOptionPane.showMessageDialog(this, strings.getProperty("noContents"));
		} else if (cd == null) {
			JOptionPane.showMessageDialog(this, strings.getProperty("notFormalArchive"));
		} else if (!matchID) {
			JOptionPane.showMessageDialog(this, strings.getProperty("unmatchedProfileId"));
		} else if (!matchREV) {
			JOptionPane.showMessageDialog(this, strings.getProperty("unmatchedProfileRev"));
		}
	}
	
	public boolean isNewProfile() {
		return chkCharacterDef.isSelected();
	}
	
	public boolean isImportPreset() {
		return chkPresets.isSelected();
	}
	
	public boolean isImportPartsImages() {
		return chkPartsImages.isSelected();
	}
	
	public boolean isImportSampleImage() {
		return chkSampleImage.isSelected();
	}
	
	public boolean isAddDescription() {
		return chkAddDescription.isSelected();
	}
	
	/**
	 * 説明として追加するドキュメント.<Br>
	 * これはユーザーが編集可能であり、ユーザー編集後の値が取得される.<br>
	 * @return 説明として追加するドキュメント
	 */
	public String getAdditionalDescription() {
		return txtDescription.getText();
	}
	
	
	
	
	@Override
	public boolean isReadyPrevious() {
		return true;
	}
	
	@Override
	public String doPrevious() {
		return ImportFileSelectPanel.PANEL_NAME;
	}
	
	@Override
	public boolean isReadyNext() {
		if (isImportPartsImages() || isImportPreset()) {
			// パーツイメージの選択もしくはパーツセットの選択を指定している場合は次へ進む
			return true;
		}
		return false;
	}
	
	@Override
	public boolean isReadyFinish() {
		if (!isImportPartsImages() && !isImportPreset()) {
			if (isNewProfile() || isImportSampleImage()) {
				// 新規プロファイル作成かサンプルイメージの更新のみでイメージもパーツセットもいらなければ、ただちに作成可能.
				return true;
			}
		}
		return false;
	}
	
	@Override
	public String doNext() {
		return ImportPartsSelectPanel.PANEL_NAME;
	}
}


/**
 * パーツ選択パネル
 * @author seraphy
 */
class ImportPartsSelectPanel extends ImportWizardCardPanel {

	private static final long serialVersionUID = 1L;

	public static final String PANEL_NAME = "importPartsSelectPanel"; 
	
	private ImportWizardDialog parent;
	

	private ImportPartsTableModel partsTableModel;
	
	private JPanel profileSizePanel;
	
	private JTextField txtProfileHeight;
	
	private int profileWidth;
	
	private int profileHeight;

	private JTextField txtProfileWidth;

	private JTable partsTable;
	
	private Action actSelectAll;
	
	private Action actDeselectAll;

	private Action actSort;

	private Action actSortByTimestamp;

	
	public ImportPartsSelectPanel() {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		setLayout(new BorderLayout());

		profileSizePanel = new JPanel();
		GridBagLayout profileSizePanelLayout = new GridBagLayout();
		profileSizePanel.setLayout(profileSizePanelLayout);
		profileSizePanel.setBorder(BorderFactory.createTitledBorder("プロファイルのサイズ"));
		
		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.weightx = 0.;
		gbc.weighty = 0.;
		gbc.ipadx = 0;
		gbc.ipady = 0;
		profileSizePanel.add(new JLabel("幅:", JLabel.RIGHT), gbc);

		txtProfileWidth = new JTextField();
		txtProfileWidth.setEditable(false);
		gbc.gridx = 1;
		gbc.gridy = 0;
		profileSizePanel.add(txtProfileWidth, gbc);
		
		gbc.gridx = 2;
		gbc.gridy = 0;
		profileSizePanel.add(new JLabel("高さ:", JLabel.RIGHT), gbc);

		txtProfileHeight = new JTextField();
		txtProfileHeight.setEditable(false);
		gbc.gridx = 3;
		gbc.gridy = 0;
		profileSizePanel.add(txtProfileHeight, gbc);
		
		gbc.gridx = 4;
		gbc.gridy = 0;
		gbc.weightx = 1.;
		profileSizePanel.add(Box.createHorizontalGlue(), gbc);

		add(profileSizePanel, BorderLayout.NORTH);
		
		partsTableModel = new ImportPartsTableModel();

		partsTableModel.addTableModelListener(new TableModelListener() {
			public void tableChanged(TableModelEvent e) {
				fireChangeEvent();
			}
		});

		AppConfig appConfig = AppConfig.getInstance();
		final Color disabledForeground = appConfig.getDisabledCellForgroundColor();

		partsTable = new JTable(partsTableModel) {
			private static final long serialVersionUID = 1L;

			@Override
			public Component prepareRenderer(TableCellRenderer renderer,
					int row, int column) {
				Component comp = super.prepareRenderer(renderer, row, column);
				if (comp instanceof JCheckBox) {
					// BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer
					comp.setEnabled(isCellEditable(row, column) && isEnabled());
				}
				
				// 行モデル取得
				ImportPartsTableModel model = (ImportPartsTableModel) getModel();
				ImportPartsModel rowModel = model.getRow(row);
				
				Long lastModifiedAtCur = rowModel.getLastModifiedAtCurrentProfile();
				if (lastModifiedAtCur != null) {
					// 既存のパーツが存在すれば太字
					comp.setFont(getFont().deriveFont(Font.BOLD));
				} else {
					// 新規パーツであれば通常フォント
					comp.setFont(getFont());
				}

				// 列ごとの警告の判定
				boolean warnings = false;
				if (column == ImportPartsTableModel.COLUMN_LASTMODIFIED) {
					// 既存のほうが日付が新しければワーニング
					if (lastModifiedAtCur != null && 
							rowModel.getLastModified() < lastModifiedAtCur.longValue()) {
						warnings = true;
					}
				} else if (column == ImportPartsTableModel.COLUMN_ALPHA) {
					// アルファ情報がない画像は警告
					if (!rowModel.isAlphaColor()) {
						warnings = true;
					}
				} else if (column == ImportPartsTableModel.COLUMN_SIZE) {
					// プロファイルの画像サイズと一致しないか、不揃いな画像であれば警告
					if (rowModel.isUnmatchedSize()
							|| profileWidth != rowModel.getWidth()
							|| profileHeight != rowModel.getHeight()) {
						warnings = true;
					}
				}
				
				// 前景色、ディセーブル時は灰色
				Color foregroundColor = isCellSelected(row, column) ? getSelectionForeground() : getForeground();
				comp.setForeground(isEnabled() ? foregroundColor : disabledForeground);
				
				// 背景色、警告行は赤色に
				if (warnings) {
					AppConfig appConfig = AppConfig.getInstance();
					Color invalidBgColor = appConfig.getInvalidBgColor();
					comp.setBackground(invalidBgColor);
				} else {
					if (isCellSelected(row, column)) {
						comp.setBackground(getSelectionBackground());
					} else {
						comp.setBackground(getBackground());
					}
				}
				
				return comp;
			}
		};
		partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		partsTable.setShowGrid(true);
		partsTable.setGridColor(appConfig.getGridColor());
		partsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
		partsTableModel.adjustColumnModel(partsTable.getColumnModel());
		partsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		partsTable.setRowSelectionAllowed(true);

		Action actPartsSetCheck = new AbstractAction(strings.getProperty("parts.popup.check")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				int[] selRows = partsTable.getSelectedRows();
				partsTableModel.setCheck(selRows, true);
			}
		};
		Action actPartsUnsetCheck = new AbstractAction(strings.getProperty("parts.popup.uncheck")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				int[] selRows = partsTable.getSelectedRows();
				partsTableModel.setCheck(selRows, false);
			}
		};
		
		final JPopupMenu partsTablePopupMenu = new JPopupMenu();
		partsTablePopupMenu.add(actPartsSetCheck);
		partsTablePopupMenu.add(actPartsUnsetCheck);
		
		partsTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				evaluatePopup(e);
			}
			@Override
			public void mouseReleased(MouseEvent e) {
				evaluatePopup(e);
			}
			private void evaluatePopup(MouseEvent e) {
				if (e.isPopupTrigger()) {
					partsTablePopupMenu.show(partsTable, e.getX(), e.getY());
				}
			}
		});
		
		JScrollPane partsTableSP = new JScrollPane(partsTable);
		partsTableSP.setBorder(BorderFactory.createTitledBorder(strings
				.getProperty("parts.title")));

		add(partsTableSP, BorderLayout.CENTER);

		actSelectAll = new AbstractAction(strings
				.getProperty("parts.btn.selectAll")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onSelectAll();
			}
		};
		actDeselectAll = new AbstractAction(strings
				.getProperty("parts.btn.deselectAll")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onDeselectAll();
			}
		};
		actSort = new AbstractAction(strings.getProperty("parts.btn.sort")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onSort();
			}
		};
		actSortByTimestamp = new AbstractAction(strings
				.getProperty("parts.btn.sortByTimestamp")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onSortByTimestamp();
			}
		};

		JPanel btnPanel = new JPanel();
		GridBagLayout btnPanelLayout = new GridBagLayout();
		btnPanel.setLayout(btnPanelLayout);

		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;
		JButton btnSelectAll = new JButton(actSelectAll);
		btnPanel.add(btnSelectAll, gbc);

		gbc.gridx = 1;
		gbc.gridy = 0;
		JButton btnDeselectAll = new JButton(actDeselectAll);
		btnPanel.add(btnDeselectAll, gbc);

		gbc.gridx = 2;
		gbc.gridy = 0;
		JButton btnSort = new JButton(actSort);
		btnPanel.add(btnSort, gbc);

		gbc.gridx = 3;
		gbc.gridy = 0;
		JButton btnSortByTimestamp = new JButton(actSortByTimestamp);
		btnPanel.add(btnSortByTimestamp, gbc);

		gbc.gridx = 4;
		gbc.gridy = 0;
		gbc.weightx = 1.;
		btnPanel.add(Box.createHorizontalGlue(), gbc);

		add(btnPanel, BorderLayout.SOUTH);
	}

	
	@Override
	public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
		this.parent = parent;
		if (previousPanel == parent.importPresetSelectPanel) {
			return;
		}
		
		// インポート対象のプロファイルサイズ
		CharacterData characterData;
		if (parent.importTypeSelectPanel.isNewProfile()) {
			characterData = parent.importFileSelectPanel.getCharacterData();
		} else {
			characterData = parent.current;
		}
		int profileWidth = 0;
		int profileHeight = 0;
		if (characterData != null) {
			Dimension imageSize = characterData.getImageSize();
			if (imageSize != null) {
				profileWidth = imageSize.width;
				profileHeight = imageSize.height;
			}
		}
		txtProfileWidth.setText(Integer.toString(profileWidth));
		txtProfileHeight.setText(Integer.toString(profileHeight));
		profileSizePanel.revalidate();
		this.profileHeight = profileHeight;
		this.profileWidth = profileWidth;
		
		// パーツのインポート指定があれば編集可能に、そうでなければ表示のみ
		// (パーツセットのインポートの確認のため、パーツ一覧は表示できるようにしておく)
		boolean enabled = parent.importTypeSelectPanel.isImportPartsImages();
		
		partsTable.setEnabled(enabled);
		actDeselectAll.setEnabled(enabled);
		actSelectAll.setEnabled(enabled);
		actSort.setEnabled(enabled);
		actSortByTimestamp.setEnabled(enabled);
		
		CharacterData currentProfile;
		Collection<PartsImageContent> partsImageContents;
		
		// 新規インポートか既存へのインポートかによってインポートするパーツの情報を構成する.
		if (parent.importTypeSelectPanel.isNewProfile()) {
			currentProfile = null;
			partsImageContents = parent.importFileSelectPanel.getPartsImageContentsForNewProfile();
		} else {
			currentProfile = parent.current;
			partsImageContents = parent.importFileSelectPanel.getPartsImageContentsForCurrentProfile();
		}
		PartsManageData partsManageData = parent.importFileSelectPanel.getPartsManageData();
		partsTableModel.initModel(partsImageContents, partsManageData, currentProfile);
		
		// プリセットのモデルも更新する.
		Collection<PartsSet> partsSets = null;
		if (parent.importTypeSelectPanel.isImportPreset()) {
			CharacterData cd = parent.importFileSelectPanel.getCharacterData();
			if (cd != null && cd.isValid()) {
				partsSets = cd.getPartsSets().values();
			}
		}
		
		String defaultPartsSetId;
		CharacterData presetImportTarget;
		if (parent.importTypeSelectPanel.isNewProfile()) {
			presetImportTarget = null;
			CharacterData cd = parent.importFileSelectPanel.getCharacterData();
			if (cd != null) {
				defaultPartsSetId = cd.getDefaultPartsSetId();
			} else {
				defaultPartsSetId = null;
			}
		} else {
			presetImportTarget = parent.current;
			defaultPartsSetId = null; // 既存の場合はデフォルトのパーツセットであるかは表示する必要ないのでnullにする.
		}
		
		parent.importPresetSelectPanel.initModel(partsSets, defaultPartsSetId, presetImportTarget);
	}

	@Override
	public boolean isReadyPrevious() {
		return true;
	}
	
	@Override
	public String doPrevious() {
		this.partsTableModel.clear();
		return ImportTypeSelectPanel.PANEL_NAME;
	}

	@Override
	public boolean isReadyNext() {
		if (this.parent != null) {
			if (this.parent.importTypeSelectPanel.isImportPreset()) {
				// パーツセットのインポート指定があれば次へ
				return true;
			}
		}
		return false;
	}
	
	@Override
	public boolean isReadyFinish() {
		if (this.parent != null) {
			if (this.parent.importTypeSelectPanel.isImportPartsImages()
					&& !this.parent.importTypeSelectPanel.isImportPreset()) {
				// パーツセットのインポート指定がなければ可
				return true;
			}
		}
		return false;
	}
	
	public String doNext() {
		return ImportPresetSelectPanel.PANEL_NAME;
	};
	
	
	protected void onSelectAll() {
		partsTableModel.selectAll();
	}

	protected void onDeselectAll() {
		partsTableModel.deselectAll();
	}

	protected void onSort() {
		partsTableModel.sort();
		if (partsTableModel.getRowCount() > 0) {
			Rectangle rct = partsTable.getCellRect(0, 0, true);
			partsTable.scrollRectToVisible(rct);
		}
	}

	protected void onSortByTimestamp() {
		partsTableModel.sortByTimestamp();
		if (partsTableModel.getRowCount() > 0) {
			Rectangle rct = partsTable.getCellRect(0, 0, true);
			partsTable.scrollRectToVisible(rct);
		}
	}

	/**
	 * 選択されたイメージコンテンツのコレクション.<br>
	 * @return 選択されたイメージコンテンツのコレクション、なければ空
	 */
	public Collection<PartsImageContent> getSelectedPartsImageContents() {
		return partsTableModel.getSelectedPartsImageContents();
	}
	
	/**
	 * すでにプロファイルに登録済みのパーツ識別子、および、これからインポートする予定の選択されたパーツ識別子のコレクション.<br>
	 * @return インポートされた、またはインポートするパーツ識別子のコレクション.なければ空.
	 */
	public Collection<PartsIdentifier> getImportedPartsIdentifiers() {
		HashSet<PartsIdentifier> partsIdentifiers = new HashSet<PartsIdentifier>();
		partsIdentifiers.addAll(partsTableModel.getCurrentProfilePartsIdentifers());
		partsIdentifiers.addAll(partsTableModel.getSelectedPartsIdentifiers());
		return partsIdentifiers;
	}
	
	public void selectByPartsIdentifiers(Collection<PartsIdentifier> partsIdentifiers) {
		partsTableModel.selectByPartsIdentifiers(partsIdentifiers);
	}
	
}


/**
 * 同じパーツ名をもつイメージのコレクション.<br>
 * パーツの各レイヤーの集合を想定する.<br>
 * @author seraphy
 */
class ImportPartsImageSet extends AbstractCollection<PartsImageContent> {

	/**
	 * パーツ名
	 */
	private String partsName;
	
	/**
	 * 各レイヤー
	 */
	private ArrayList<PartsImageContent> contentSet = new ArrayList<PartsImageContent>();
	
	private Long lastModified;
	
	private int width;
	
	private int height;
	
	private boolean unmatchedSize;
	
	private boolean alphaColor;
	
	private Collection<PartsCategory> partsCategories;
	
	private boolean checked;

	
	public ImportPartsImageSet(String partsName) {
		if (partsName == null || partsName.length() == 0) {
			throw new IllegalArgumentException();
		}
		this.partsName = partsName;
	}
	
	public String getPartsName() {
		return partsName;
	}
	
	@Override
	public int size() {
		return contentSet.size();
	}
	
	public Iterator<PartsImageContent> iterator() {
		return contentSet.iterator();
	}
	
	@Override
	public boolean add(PartsImageContent o) {
		if (o == null) {
			throw new IllegalArgumentException();
		}
		if (!partsName.equals(o.getPartsName())) {
			throw new IllegalArgumentException();
		}
		
		lastModified = null; // リセットする.
		
		return contentSet.add(o);
	}
	
	public int getWidth() {
		recheck();
		return width;
	}
	
	public int getHeight() {
		recheck();
		return height;
	}
	
	public boolean isUnmatchedSize() {
		recheck();
		return unmatchedSize;
	}
	
	public boolean isAlphaColor() {
		recheck();
		return alphaColor;
	}
	
	public long lastModified() {
		recheck();
		return lastModified.longValue();
	}
	
	public Collection<PartsCategory> getPartsCategories() {
		recheck();
		return this.partsCategories;
	}
	
	protected void recheck() {
		if (lastModified != null) {
			return;
		}

		long lastModified = 0;
		int maxWidth = 0;
		int maxHeight = 0;
		int minWidth = 0;
		int minHeight = 0;
		boolean alphaColor = !this.contentSet.isEmpty();
		HashSet<PartsCategory> partsCategories = new HashSet<PartsCategory>();

		for (PartsImageContent partsImageContent : this.contentSet) {
			PNGFileImageHeader header = partsImageContent.getPngFileImageHeader();

			maxWidth = Math.max(maxWidth, header.getWidth());
			maxHeight = Math.max(maxHeight, header.getHeight());
			minWidth = Math.max(minWidth, header.getWidth());
			minHeight = Math.max(minHeight, header.getHeight());
			
			if (header.getColorType() != 6 && !header.hasTransparencyInformation()) {
				// TrueColor + Alpha (6)か、アルファ情報があるもの以外はアルファなしとする.
				alphaColor = false;
			}
			
			for (CategoryLayerPair clPair : partsImageContent.getCategoryLayerPairs()) {
				partsCategories.add(clPair.getPartsCategory());
			}
			
			long tm = partsImageContent.lastModified();
			lastModified = Math.max(lastModified, tm);
		}
		
		this.lastModified = Long.valueOf(lastModified);
		this.width = maxWidth;
		this.height = maxHeight;
		this.unmatchedSize = (minWidth != maxWidth) || (minHeight != maxHeight);
		this.alphaColor = alphaColor;
		this.partsCategories = Collections.unmodifiableCollection(partsCategories);
	}

	public void setChecked(boolean checked) {
		this.checked = checked;
	}
	
	public boolean isChecked() {
		return checked;
	}
}



class ImportPartsModel {

	private PartsIdentifier partsIdentifier;
	
	private PartsAuthorInfo authorInfo;
	
	private PartsManageData.PartsVersionInfo versionInfo;
	
	private PartsSpec partsSpecAtCurrent;
	
	private ImportPartsImageSet imageSet;
	
	private int numOfLink;
	
	private Long lastModifiedAtCurrentProfile;
	

	/**
	 * 行モデルを構築する
	 * @param partsIdentifier パーツ識別子
	 * @param authorInfo 作者情報(なければnull)
	 * @param versionInfo バージョン情報(なければnull)
	 * @param imageSet イメージファイルのセット
	 * @param numOfLink カテゴリの参照カウント数(複数カテゴリに参照される場合は2以上となる)
	 */
	public ImportPartsModel(PartsIdentifier partsIdentifier,
			PartsAuthorInfo authorInfo,
			PartsManageData.PartsVersionInfo versionInfo,
			PartsSpec partsSpecAtCurrent,
			ImportPartsImageSet imageSet, int numOfLink) {
		if (partsIdentifier == null || imageSet == null) {
			throw new IllegalArgumentException();
		}

		this.partsIdentifier = partsIdentifier;
		this.authorInfo = authorInfo;
		this.versionInfo = versionInfo;
		this.partsSpecAtCurrent = partsSpecAtCurrent;
		this.imageSet = imageSet;
		this.numOfLink = numOfLink;
		
		if (partsSpecAtCurrent != null) {
			lastModifiedAtCurrentProfile = Long.valueOf(partsSpecAtCurrent
					.getPartsFiles().lastModified());
		} else {
			lastModifiedAtCurrentProfile = null;
		}
	}
	
	public int getNumOfLink() {
		return numOfLink;
	}
	
	public PartsIdentifier getPartsIdentifier() {
		return partsIdentifier;
	}
	
	public ImportPartsImageSet getImageSet() {
		return imageSet;
	}

	public String getPartsName() {
		return partsIdentifier.getLocalizedPartsName();
	}
	
	public String getAuthor() {
		if (authorInfo != null) {
			return authorInfo.getAuthor();
		}
		return null;
	}
	
	public String getAuthorAtCurrent() {
		if (partsSpecAtCurrent != null) {
			PartsAuthorInfo partsAuthorInfo = partsSpecAtCurrent.getAuthorInfo();
			if (partsAuthorInfo != null) {
				return partsAuthorInfo.getAuthor();
			}
		}
		return null;
	}
	
	public double getVersion() {
		if (versionInfo != null) {
			return versionInfo.getVersion();
		}
		return 0;
	}
	
	public double getVersionAtCurrent() {
		if (partsSpecAtCurrent != null) {
			return partsSpecAtCurrent.getVersion();
		}
		return 0;
	}
	
	public PartsCategory getPartsCategory() {
		return partsIdentifier.getPartsCategory();
	}
	
	public void setChecked(boolean checked) {
		imageSet.setChecked(checked);
	}
	
	public boolean isChecked() {
		return imageSet.isChecked();
	}
	
	public int getWidth() {
		return imageSet.getWidth();
	}
	
	public int getHeight() {
		return imageSet.getHeight();
	}
	
	public boolean isUnmatchedSize() {
		return imageSet.isUnmatchedSize();
	}
	
	public boolean isAlphaColor() {
		return imageSet.isAlphaColor();
	}
	
	public long getLastModified() {
		return imageSet.lastModified();
	}
	
	public Long getLastModifiedAtCurrentProfile() {
		return lastModifiedAtCurrentProfile;
	}
}


class ImportPartsTableModel extends AbstractTableModelWithComboBoxModel<ImportPartsModel> {

	private static final long serialVersionUID = 1L;

	private static final String[] COLUMN_NAMES;
	
	private static final int[] COLUMN_WIDTHS;
	
	public static final int COLUMN_LASTMODIFIED = 5;
	
	public static final int COLUMN_ALPHA = 4;
	
	public static final int COLUMN_SIZE = 3;
	
	private Set<PartsIdentifier> currentProfilePartsIdentifiers;
	
	static {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
		
		COLUMN_NAMES = new String[] {
				strings.getProperty("parts.column.check"),
				strings.getProperty("parts.column.partsname"),
				strings.getProperty("parts.column.category"),
				strings.getProperty("parts.column.imagesize"),
				strings.getProperty("parts.column.alpha"),
				strings.getProperty("parts.column.lastmodified"),
				strings.getProperty("parts.column.org-lastmodified"),
				strings.getProperty("parts.column.author"),
				strings.getProperty("parts.column.org-author"),
				strings.getProperty("parts.column.version"),
				strings.getProperty("parts.column.org-version"),
		};
		COLUMN_WIDTHS = new int[] {
				Integer.parseInt(strings.getProperty("parts.column.check.size")),
				Integer.parseInt(strings.getProperty("parts.column.partsname.size")),
				Integer.parseInt(strings.getProperty("parts.column.category.size")),
				Integer.parseInt(strings.getProperty("parts.column.imagesize.size")),
				Integer.parseInt(strings.getProperty("parts.column.alpha.size")),
				Integer.parseInt(strings.getProperty("parts.column.lastmodified.size")),
				Integer.parseInt(strings.getProperty("parts.column.org-lastmodified.size")),
				Integer.parseInt(strings.getProperty("parts.column.author.size")),
				Integer.parseInt(strings.getProperty("parts.column.org-author.size")),
				Integer.parseInt(strings.getProperty("parts.column.version.size")),
				Integer.parseInt(strings.getProperty("parts.column.org-version.size")),
		};
	}
	

	/**
	 * モデルを初期化する.<br>
	 * @param partsImageContents インポートもとアーカイブに含まれる、全パーツイメージコンテンツ
	 * @param currentProfile インポート先のプロファイル、現在プロファイルが既に持っているパーツを取得するためのもの。
	 */
	public void initModel(Collection<PartsImageContent> partsImageContents, PartsManageData partsManageData, CharacterData currentProfile) {
		clear();
		if (partsImageContents == null || partsManageData == null) {
			return;
		}
		
		// 現在のプロファイルが所有する全パーツ一覧を構築する.
		// 現在のプロファイルがなければ空.
		HashSet<PartsIdentifier> currentProfilePartsIdentifiers = new HashSet<PartsIdentifier>();
		if (currentProfile != null) {
			for (PartsCategory partsCategory : currentProfile.getPartsCategories()) {
				currentProfilePartsIdentifiers.addAll(currentProfile.getPartsSpecMap(partsCategory).keySet());
			}
		}
		this.currentProfilePartsIdentifiers = Collections.unmodifiableSet(currentProfilePartsIdentifiers);

		// 同じパーツ名をもつ各レイヤーを集める
		HashMap<String, ImportPartsImageSet> partsImageSets = new HashMap<String, ImportPartsImageSet>();
		for (PartsImageContent content : partsImageContents) {
			String partsName = content.getPartsName();
			ImportPartsImageSet partsImageSet = partsImageSets.get(partsName);
			if (partsImageSet == null) {
				partsImageSet = new ImportPartsImageSet(partsName);
				partsImageSets.put(partsName, partsImageSet);
			}
			partsImageSet.add(content);
		}
		
		// 名前順に並び替える
		ArrayList<String> partsNames = new ArrayList<String>(partsImageSets.keySet());
		Collections.sort(partsNames);

		// 登録する
		for (String partsName : partsNames) {
			ImportPartsImageSet partsImageSet = partsImageSets.get(partsName);
			int numOfLink = partsImageSet.getPartsCategories().size();
			for (PartsCategory partsCategory : partsImageSet.getPartsCategories()) {
				
				// パーツ管理情報の索引キー
				PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsName, partsCategory.getCategoryId());
				// ローカライズされたパーツ名があれば取得する。なければオリジナルのまま
				String localizedPartsName = partsManageData.getLocalizedName(partsKey);
				if (localizedPartsName == null || localizedPartsName.length() == 0) {
					localizedPartsName = partsName;
				}
				// 作者情報・バージョン情報があれば取得する.
				PartsAuthorInfo partsAuthorInfo = partsManageData.getPartsAuthorInfo(partsKey);
				PartsManageData.PartsVersionInfo versionInfo = partsManageData.getVersion(partsKey);

				// パーツ識別子を構築する
				PartsIdentifier partsIdentifier = new PartsIdentifier(partsCategory, partsName, localizedPartsName);

				// 現在のプロファイル上のパーツ情報を取得する.(なければnull)
				PartsSpec partsSpec;
				if (currentProfile != null) {
					partsSpec = currentProfile.getPartsSpec(partsIdentifier);
				} else {
					partsSpec = null;
				}

				// 行モデルを構築する.
				ImportPartsModel rowModel = new ImportPartsModel(
						partsIdentifier, partsAuthorInfo, versionInfo,
						partsSpec, partsImageSet, numOfLink);
				
				addRow(rowModel);
			}
		}
		
		// 既存がないか、既存よりも新しい日付であれば自動的にチェックを設定する.
		// もしくはバージョンが上であれば自動的にチェックをつける.
		for (ImportPartsModel rowModel : elements) {
			
			// 現在のプロファイル上のファイル群の最終更新日
			Long lastModifiedAtCurrent = rowModel.getLastModifiedAtCurrentProfile();
			if (lastModifiedAtCurrent == null) {
				lastModifiedAtCurrent = Long.valueOf(0);
			}
			
			// インポートするファイル群の最終更新日
			ImportPartsImageSet partsImageSet = rowModel.getImageSet();

			// 新しければ自動的にチェックをつける.
			if (lastModifiedAtCurrent.longValue() < partsImageSet.lastModified()) {
				partsImageSet.setChecked(true);
			}
			
			// バージョンが新しければチェックをつける. (改変版や作者名改名もあるので、作者名が同一であるかは問わない.)
			double versionAtCurrent = rowModel.getVersionAtCurrent();
			double version = rowModel.getVersion();
			if (versionAtCurrent < version) {
				partsImageSet.setChecked(true);
			}
		}

		// 並び替え
		sort();
	}
	
	/**
	 * 選択されているパーツを構成するファイルのコレクションを返します.<br>
	 * @return パーツイメージコンテンツのコレクション、選択がなければ空
	 */
	public Collection<PartsImageContent> getSelectedPartsImageContents() {
		IdentityHashMap<ImportPartsImageSet, ImportPartsImageSet> partsImageSets
				= new IdentityHashMap<ImportPartsImageSet, ImportPartsImageSet>();
		
		for (ImportPartsModel rowModel : elements) {
			ImportPartsImageSet partsImageSet = rowModel.getImageSet();
			if (partsImageSet.isChecked()) {
				partsImageSets.put(partsImageSet, partsImageSet);
			}
		}
		
		ArrayList<PartsImageContent> partsImageContents = new ArrayList<PartsImageContent>();
		for (ImportPartsImageSet partsImageSet : partsImageSets.values()) {
			partsImageContents.addAll(partsImageSet);
		}
		return partsImageContents;
	}

	/**
	 * 選択されているパーツ識別子のコレクションを返します.<br>
	 * 返されるコレクションには同一のパーツ識別子が複数存在しないことが保証されます.<br>
	 * 一つも選択がない場合は空が返されます.<br>
	 * @return パーツ識別子のコレクション.<br>
	 */
	public Collection<PartsIdentifier> getSelectedPartsIdentifiers() {
		HashSet<PartsIdentifier> partsIdentifiers = new HashSet<PartsIdentifier>();
		for (ImportPartsModel rowModel : elements) {
			if (rowModel.isChecked()) {
				partsIdentifiers.add(rowModel.getPartsIdentifier());
			}
		}
		return partsIdentifiers;
	}
	
	/**
	 * 現在のプロファイルが所有している全パーツの識別子.<br>
	 * 現在のプロファイルがないか、まったく所有していなければ空.<br>
	 * @return 現在のプロファイルが所有するパーツの識別子のコレクション.(重複しない一意であることが保証される.)
	 */
	public Collection<PartsIdentifier> getCurrentProfilePartsIdentifers() {
		return currentProfilePartsIdentifiers;
	}

	public int getColumnCount() {
		return COLUMN_NAMES.length;
	}
	
	@Override
	public String getColumnName(int column) {
		return COLUMN_NAMES[column];
	}
	
	public Object getValueAt(int rowIndex, int columnIndex) {
		ImportPartsModel rowModel = getRow(rowIndex);
		switch (columnIndex) {
		case 0:
			return rowModel.isChecked();
		case 1:
			return rowModel.getPartsName();
		case 2:
			return rowModel.getPartsCategory().getLocalizedCategoryName();
		case 3:
			return rowModel.getWidth() + "x" + rowModel.getHeight()
					+ (rowModel.isUnmatchedSize() ? "*" : "");
		case 4:
			return rowModel.isAlphaColor();
		case 5:
			long lastModified = rowModel.getLastModified();
			if (lastModified > 0) {
				return new Timestamp(lastModified).toString();
			}
			return "";
		case 6:
			Long lastModifiedAtCur = rowModel.getLastModifiedAtCurrentProfile();
			if (lastModifiedAtCur != null && lastModifiedAtCur.longValue() > 0) {
				return new Timestamp(lastModifiedAtCur.longValue()).toString();
			}
			return "";
		case 7:
			return rowModel.getAuthor();
		case 8:
			return rowModel.getAuthorAtCurrent();
		case 9:
			double version = rowModel.getVersion();
			if (version > 0) {
				return Double.toString(version);
			}
			return "";
		case 10:
			double versionAtCurrent = rowModel.getVersionAtCurrent();
			if (versionAtCurrent > 0) {
				return Double.toString(versionAtCurrent);
			}
			return "";
		}
		return "";
	}
	
	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		ImportPartsModel rowModel = getRow(rowIndex);

		switch (columnIndex) {
		case 0:
			rowModel.setChecked(((Boolean) aValue).booleanValue());
			break;
		default:
			return;
		}

		if (rowModel.getNumOfLink() > 1) {
			fireTableDataChanged();
		} else {
			fireListUpdated(rowIndex, rowIndex);
		}
	}
	
	@Override
	public Class<?> getColumnClass(int columnIndex) {
		switch (columnIndex) {
		case 0:
			return Boolean.class;
		case 1:
			return String.class;
		case 2:
			return String.class;
		case 3:
			return String.class;
		case 4:
			return Boolean.class;
		case 5:
			return String.class;
		case 6:
			return String.class;
		case 7:
			return String.class;
		case 8:
			return String.class;
		case 9:
			return String.class;
		case 10:
			return String.class;
		}
		return String.class;
	}
	
	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		if (columnIndex == 0) {
			return true;
		}
		return false;
	}
	
	public void adjustColumnModel(TableColumnModel columnModel) {
		int mx = columnModel.getColumnCount();
		for (int idx = 0; idx < mx; idx++) {
			columnModel.getColumn(idx).setWidth(COLUMN_WIDTHS[idx]);
		}
	}
	
	public void selectAll() {
		boolean modified = false;
		for (ImportPartsModel rowModel : elements) {
			if (!rowModel.isChecked()) {
				rowModel.setChecked(true);
				modified = true;
			}
		}
		if (modified) {
			fireTableDataChanged();
		}
	}
	
	public void deselectAll() {
		boolean modified = false;
		for (ImportPartsModel rowModel : elements) {
			if (rowModel.isChecked()) {
				rowModel.setChecked(false);
				modified = true;
			}
		}
		if (modified) {
			fireTableDataChanged();
		}
	}
	
	public void sort() {
		Collections.sort(elements, new Comparator<ImportPartsModel> () {
			public int compare(ImportPartsModel o1, ImportPartsModel o2) {
				int ret = (o1.isChecked() ? 0 : 1) - (o2.isChecked() ? 0 : 1);
				if (ret == 0) {
					ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
				}
				return ret;
			}
		});
		
		fireTableDataChanged();
	}
	
	public void sortByTimestamp() {
		Collections.sort(elements, new Comparator<ImportPartsModel> () {
			public int compare(ImportPartsModel o1, ImportPartsModel o2) {
				long ret = (o1.isChecked() ? 0 : 1) - (o2.isChecked() ? 0 : 1);
				if (ret == 0) {
					Long tm1 = o1.getLastModifiedAtCurrentProfile();
					Long tm2 = o2.getLastModifiedAtCurrentProfile();
					
					long lastModified1 = Math.max(o1.getLastModified(), tm1 == null ? 0 : tm1.longValue());
					long lastModified2 = Math.max(o2.getLastModified(), tm2 == null ? 0 : tm2.longValue());

					ret = lastModified1 - lastModified2;
				}
				if (ret == 0) {
					ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
				}
				
				return ret == 0 ? 0 : ret > 0 ? 1 : -1;
			}
		});

		fireTableDataChanged();
	}

	/**
	 * 指定したパーツ識別子をチェック状態にする.
	 * @param partsIdentifiers パーツ識別子のコレクション、nullの場合は何もしない.
	 */
	public void selectByPartsIdentifiers(Collection<PartsIdentifier> partsIdentifiers) {
		boolean modified = false;
		if (partsIdentifiers != null) {
			for (PartsIdentifier partsIdentifier : partsIdentifiers) {
				for (ImportPartsModel rowModel : elements) {
					if (rowModel.getPartsIdentifier().equals(partsIdentifier)) {
						if (!rowModel.isChecked()) {
							rowModel.setChecked(true);
							modified = true;
						}
					}
				}
			}
		}
		if (modified) {
			fireTableDataChanged();
		}
	}
	
	public void setCheck(int[] selRows, boolean checked) {
		if (selRows == null || selRows.length == 0) {
			return;
		}
		Arrays.sort(selRows);
		for (int selRow : selRows) {
			ImportPartsModel rowModel = getRow(selRow);
			rowModel.setChecked(checked);
		}
		fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);
	}
}



/**
 * プリセット選択パネル
 * @author seraphy
 */
class ImportPresetSelectPanel extends ImportWizardCardPanel {

	private static final long serialVersionUID = 1L;

	public static final String PANEL_NAME = "importPresetSelectPanel"; 
	
	private ImportPresetTableModel presetTableModel;
	
	private ImportWizardDialog parent;
	
	private JTable presetTable;

	private Action actSelectAll;
	
	private Action actDeselectAll;
	
	private Action actSelectUsedParts;

	public ImportPresetSelectPanel() {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);

		setBorder(BorderFactory.createTitledBorder(strings.getProperty("preset.title")));

		setLayout(new BorderLayout());

		presetTableModel = new ImportPresetTableModel();

		presetTableModel.addTableModelListener(new TableModelListener() {
			public void tableChanged(TableModelEvent e) {
				if (e.getType() == TableModelEvent.UPDATE) {
					fireChangeEvent();
				}
			}
		});

		AppConfig appConfig = AppConfig.getInstance();
		final Color warningForegroundColor = appConfig.getExportPresetWarningsForegroundColor();
		final Color disabledForeground = appConfig.getDisabledCellForgroundColor();

		presetTable = new JTable(presetTableModel) {
			private static final long serialVersionUID = 1L;

			@Override
			public Component prepareRenderer(TableCellRenderer renderer,
					int row, int column) {
				Component comp = super.prepareRenderer(renderer, row, column);

				if (comp instanceof JCheckBox) {
					// BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer
					comp.setEnabled(isCellEditable(row, column) && isEnabled());
				}

				ImportPresetModel presetModel = presetTableModel.getRow(row);
				
				// インポート先のプリセットを上書きする場合、もしくはデフォルトのパーツセットの場合は太字にする.
				if (presetModel.isOverwrite() || presetTableModel.isDefaultPartsSet(row)) {
					comp.setFont(getFont().deriveFont(Font.BOLD));
				} else {
					comp.setFont(getFont());
				}

				// インポートするプリセットのパーツが不足している場合、警告色にする.
				if (!isEnabled()) {
					comp.setForeground(disabledForeground);

				} else {
					if (presetModel.isCheched()
							&& presetModel.getMissingPartsIdentifiers().size() > 0) {
						comp.setForeground(warningForegroundColor);
					} else {
						comp.setForeground(getForeground());
					}
				}
				return comp;
			}
		};
		presetTable.setShowGrid(true);
		presetTable.setGridColor(appConfig.getGridColor());
		presetTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

		actSelectUsedParts = new AbstractAction(strings.getProperty("preset.popup.selectUsedParts")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				exportUsedParts();
			}
		};

		final JPopupMenu popupMenu = new JPopupMenu();
		popupMenu.add(actSelectUsedParts);

		presetTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				evaluatePopup(e);
			}

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

			private void evaluatePopup(MouseEvent e) {
				if (e.isPopupTrigger()) {
					popupMenu.show(presetTable, e.getX(), e.getY());
				}
			}
		});

		presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		presetTableModel.adjustColumnModel(presetTable.getColumnModel());

		add(new JScrollPane(presetTable), BorderLayout.CENTER);

		actSelectAll = new AbstractAction(strings.getProperty("parts.btn.selectAll")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onSelectAll();
			}
		};
		actDeselectAll = new AbstractAction(strings.getProperty("parts.btn.deselectAll")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onDeselectAll();
			}
		};
		Action actSort = new AbstractAction(strings
				.getProperty("parts.btn.sort")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				onSort();
			}
		};

		JPanel btnPanel = new JPanel();
		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;
		JButton btnSelectAll = new JButton(actSelectAll);
		btnPanel.add(btnSelectAll, gbc);

		gbc.gridx = 1;
		gbc.gridy = 0;
		JButton btnDeselectAll = new JButton(actDeselectAll);
		btnPanel.add(btnDeselectAll, gbc);

		gbc.gridx = 2;
		gbc.gridy = 0;
		JButton btnSort = new JButton(actSort);
		btnPanel.add(btnSort, gbc);

		gbc.gridx = 3;
		gbc.gridy = 0;
		gbc.weightx = 1.;
		btnPanel.add(Box.createHorizontalGlue(), gbc);

		add(btnPanel, BorderLayout.SOUTH);
	}

	@Override
	public void onActive(ImportWizardDialog parent,
			ImportWizardCardPanel previousPanel) {
		this.parent= parent;

		actSelectUsedParts.setEnabled(parent.importTypeSelectPanel.isImportPartsImages());
		checkMissingParts();
	}
	
	public void checkMissingParts() {
		Collection<PartsIdentifier> importedPartsIdentifiers = this.parent.importPartsSelectPanel.getImportedPartsIdentifiers();
		presetTableModel.checkMissingParts(importedPartsIdentifiers);
	}

	protected void onSelectAll() {
		presetTableModel.selectAll();
	}

	protected void onDeselectAll() {
		presetTableModel.deselectAll();
	}

	protected void onSort() {
		presetTableModel.sort();
		if (presetTableModel.getRowCount() > 0) {
			Rectangle rct = presetTable.getCellRect(0, 0, true);
			presetTable.scrollRectToVisible(rct);
		}
	}

	protected void exportUsedParts() {
		ArrayList<PartsIdentifier> requirePartsIdentifiers = new ArrayList<PartsIdentifier>();
		int[] selRows = presetTable.getSelectedRows();
		for (int selRow : selRows) {
			ImportPresetModel presetModel = presetTableModel.getRow(selRow);
			PartsSet partsSet = presetModel.getPartsSet();
			for (List<PartsIdentifier> partsIdentifiers : partsSet.values()) {
				for (PartsIdentifier partsIdentifier : partsIdentifiers) {
					requirePartsIdentifiers.add(partsIdentifier);
				}
			}
		}
		this.parent.importPartsSelectPanel.selectByPartsIdentifiers(requirePartsIdentifiers);
		checkMissingParts();
	}
	
	@Override
	public boolean isReadyPrevious() {
		return true;
	}
	
	@Override
	public boolean isReadyNext() {
		return false;
	}
	
	@Override
	public boolean isReadyFinish() {
		if (this.parent != null) {
			return true;
		}
		return false;
	}
	
	@Override
	public String doPrevious() {
		
		return ImportPartsSelectPanel.PANEL_NAME;
	}
	
	@Override
	public String doNext() {
		return null;
	}
	
	public Collection<PartsSet> getSelectedPartsSets() {
		return presetTableModel.getSelectedPartsSets();
	}
	
	/**
	 * デフォルトのパーツセットIDとして使用されることが推奨されるパーツセットIDを取得する.<br>
	 * 明示的なデフォルトのパーツセットIDがなければ、もしくは、
	 * 明示的に指定されているパーツセットIDが選択されているパーツセットの中になければ、
	 * 選択されているパーツセットの最初のアイテムを返す.<br>
	 * 選択しているパーツセットが一つもなければnullを返す.<br>
	 * @return デフォルトのパーツセット
	 */
	public String getPrefferedDefaultPartsSetId() {
		String defaultPartsSetId = presetTableModel.getDefaultPartsSetId();
		String firstPartsSetId = null;
		boolean existsDefaultPartsSetId = false;
		for (PartsSet partsSet : getSelectedPartsSets()) {
			if (firstPartsSetId == null) {
				firstPartsSetId = partsSet.getPartsSetId();
			}
			if (partsSet.getPartsSetId().equals(defaultPartsSetId)) {
				existsDefaultPartsSetId = true;
			}
		}
		if (!existsDefaultPartsSetId || defaultPartsSetId == null || defaultPartsSetId.length() == 0) {
			defaultPartsSetId = firstPartsSetId;
		}
		return defaultPartsSetId;
	}
	
	public void initModel(Collection<PartsSet> partsSets, String defaultPartsSetId, CharacterData presetImportTarget) {
		presetTableModel.initModel(partsSets, defaultPartsSetId, presetImportTarget);
	}
}

class ImportPresetModel {

	private boolean cheched;
	
	private PartsSet partsSet;
	
	private boolean overwrite;
	
	private Collection<PartsIdentifier> missingPartsIdentifiers = Collections.emptySet();
	
	public ImportPresetModel(PartsSet partsSet, boolean overwrite, boolean checked) {
		if (partsSet == null) {
			throw new IllegalArgumentException();
		}
		this.partsSet = partsSet;
		this.cheched = checked;
		this.overwrite = overwrite;
	}
	
	public boolean isCheched() {
		return cheched;
	}
	
	public void setCheched(boolean cheched) {
		this.cheched = cheched;
	}
	
	public PartsSet getPartsSet() {
		return partsSet;
	}
	
	public String getPartsSetName() {
		return partsSet.getLocalizedName();
	}
	
	public void setPartsSetName(String name) {
		if (name == null || name.trim().length() == 0) {
			throw new IllegalArgumentException();
		}
		partsSet.setLocalizedName(name);
	}
	
	public Collection<PartsIdentifier> getMissingPartsIdentifiers() {
		return missingPartsIdentifiers;
	}
	
	public boolean hasMissingParts() {
		return true;
	}
	
	public boolean isOverwrite() {
		return overwrite;
	}
	
	public boolean checkMissingParts(Collection<PartsIdentifier> importedPartsIdentifiers) {
		HashSet<PartsIdentifier> missingPartsIdentifiers = new HashSet<PartsIdentifier>();
		for (List<PartsIdentifier> partsIdentifiers : partsSet.values()) {
			for (PartsIdentifier partsIdentifier : partsIdentifiers) {
				boolean exists = false;
				if (importedPartsIdentifiers != null && importedPartsIdentifiers.contains(partsIdentifier)) {
					exists = true;
				}
				if (!exists) {
					missingPartsIdentifiers.add(partsIdentifier);
				}
			}
		}
		
		boolean modified = (!missingPartsIdentifiers.equals(this.missingPartsIdentifiers));
		if (modified) {
			this.missingPartsIdentifiers = missingPartsIdentifiers;
		}
		return modified;
	}
}

class ImportPresetTableModel extends AbstractTableModelWithComboBoxModel<ImportPresetModel> {

	private static final long serialVersionUID = 1L;

	private static final String[] COLUMN_NAMES; // = {"選択", "プリセット名", "不足するパーツ"};
	
	private static final int[] COLUMN_WIDTHS; // = {50, 100, 200};
	
	static {
		Properties strings = LocalizedResourcePropertyLoader.getInstance()
				.getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
		
		COLUMN_NAMES = new String[] {
				strings.getProperty("preset.column.check"),
				strings.getProperty("preset.column.name"),
				strings.getProperty("preset.column.missings"),
		};
		
		COLUMN_WIDTHS = new int[] {
				Integer.parseInt(strings.getProperty("preset.column.check.size")),
				Integer.parseInt(strings.getProperty("preset.column.name.size")),
				Integer.parseInt(strings.getProperty("preset.column.missings.size")),
		};
	}
	
	private String defaultPartsSetId;
	
	public String getDefaultPartsSetId() {
		return defaultPartsSetId;
	}
	
	public void setDefaultPartsSetId(String defaultPartsSetId) {
		this.defaultPartsSetId = defaultPartsSetId;
	}
	
	public int getColumnCount() {
		return COLUMN_NAMES.length;
	}
	
	@Override
	public String getColumnName(int column) {
		return COLUMN_NAMES[column];
	}
	
	@Override
	public Class<?> getColumnClass(int columnIndex) {
		switch (columnIndex) {
		case 0:
			return Boolean.class;
		case 1:
			return String.class;
		case 2:
			return String.class;
		}
		return String.class;
	}
	
	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		if (columnIndex == 0 || columnIndex == 1) {
			return true;
		}
		return false;
	}

	public Object getValueAt(int rowIndex, int columnIndex) {
		ImportPresetModel rowModel = getRow(rowIndex);
		switch (columnIndex) {
		case 0:
			return rowModel.isCheched();
		case 1:
			return rowModel.getPartsSetName();
		case 2:
			return getMissingPartsIdentifiersString(rowModel);
		}
		return "";
	}
	
	private String getMissingPartsIdentifiersString(ImportPresetModel rowModel) {
		StringBuilder buf = new StringBuilder();
		for (PartsIdentifier partsIdentifier : rowModel.getMissingPartsIdentifiers()) {
			if (buf.length() > 0) {
				buf.append(", ");
			}
			buf.append(partsIdentifier.getLocalizedPartsName());
		}
		return buf.toString();
	}
	
	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		ImportPresetModel rowModel = getRow(rowIndex);
		switch (columnIndex) {
		case 0:
			rowModel.setCheched(((Boolean) aValue).booleanValue());
			break;
		case 1:
			String name = (String) aValue;
			name = (name != null) ? name.trim() : "";
			if (name.length() > 0) {
				rowModel.setPartsSetName(name);
			}
		default:
			return;
		}
		fireTableRowsUpdated(rowIndex, rowIndex);
	}

	/**
	 * 指定した行のパーツセットがデフォルトパーツセットであるか?
	 * @param rowIndex 行インデックス
	 * @return デフォルトパーツセットであればtrue、そうでなければfalse
	 */
	public boolean isDefaultPartsSet(int rowIndex) {
		ImportPresetModel rowModel = getRow(rowIndex);
		return rowModel.getPartsSet().getPartsSetId().equals(defaultPartsSetId);
	}
	
	public void adjustColumnModel(TableColumnModel columnModel) {
		int mx = columnModel.getColumnCount();
		for (int idx = 0; idx < mx; idx++) {
			columnModel.getColumn(idx).setWidth(COLUMN_WIDTHS[idx]);
		}
	}
	
	/**
	 * パーツセットリストを構築する.<br>
	 * @param partsSets 登録するパーツセット
	 * @param defaultPartsSetId デフォルトのパーツセットID、なければnull
	 * @param presetImportTarget インポート先、新規の場合はnull (上書き判定のため)
	 */
	public void initModel(Collection<PartsSet> partsSets, String defaultPartsSetId, CharacterData presetImportTarget) {
		clear();
		if (partsSets == null) {
			return;
		}

		// インポート先の既存のパーツセット
		Map<String, PartsSet> currentProfilesPartsSet;
		if (presetImportTarget != null) {
			currentProfilesPartsSet = presetImportTarget.getPartsSets();
		} else {
			// 新規の場合は既存パーツセットは空.
			currentProfilesPartsSet = Collections.emptyMap();
		}
		
		// インポートもとのパーツセットをテープルモデルに登録する.
		for (PartsSet partsSet : partsSets) {
			String partsSetId = partsSet.getPartsSetId();
			if (partsSetId == null || partsSetId.length() == 0) {
				continue;
			}
			PartsSet compatiblePartsSet;
			if (presetImportTarget != null) {
				// 既存のキャラクター定義へのインポート時は、パーツセットのカテゴリを合わせる.
				// 一つもカテゴリが合わない場合は空のパーツセットになる.
				compatiblePartsSet = partsSet.createCompatible(presetImportTarget);
			} else {
				compatiblePartsSet = partsSet; // 新規の場合はフィッティングの必要なし.
			}
			if (!compatiblePartsSet.isEmpty()) {
				// 空のパーツセットは登録対象にしない.
				boolean overwrite = currentProfilesPartsSet.containsKey(partsSetId);
				boolean checked = (presetImportTarget == null); // 新規の場合は既定で選択状態とする.
				ImportPresetModel rowModel = new ImportPresetModel(partsSet, overwrite, checked);
				addRow(rowModel);
			}
		}
		
		// デフォルトのパーツセットIDを設定、存在しない場合はnull
		this.defaultPartsSetId = defaultPartsSetId;
		
		sort();
	}
	
	public Collection<PartsSet> getSelectedPartsSets() {
		ArrayList<PartsSet> partsSets = new ArrayList<PartsSet>();
		for (ImportPresetModel rowModel : elements) {
			if (rowModel.isCheched()) {
				partsSets.add(rowModel.getPartsSet());
			}
		}
		return partsSets;
	}

	public void selectAll() {
		boolean modified = false;
		for (ImportPresetModel rowModel : elements) {
			if (!rowModel.isCheched()) {
				rowModel.setCheched(true);
				modified = true;
			}
		}
		if (modified) {
			fireTableDataChanged();
		}
	}
	
	public void deselectAll() {
		boolean modified = false;
		for (ImportPresetModel rowModel : elements) {
			if (rowModel.isCheched()) {
				rowModel.setCheched(false);
				modified = true;
			}
		}
		if (modified) {
			fireTableDataChanged();
		}
	}
	
	public void sort() {
		Collections.sort(elements, new Comparator<ImportPresetModel>() {
			public int compare(ImportPresetModel o1, ImportPresetModel o2) {
				int ret = (o1.isCheched() ? 0 : 1) - (o2.isCheched() ? 0 : 1);
				if (ret == 0) {
					ret = o1.getPartsSetName().compareTo(o2.getPartsSetName());
				}
				return ret;
			}
		});
		fireTableDataChanged();
	}
	
	public void checkMissingParts(Collection<PartsIdentifier> importedPartsIdentifiers) {
		boolean changed = false;
		
		for (ImportPresetModel rowModel : elements) {
			if (rowModel.checkMissingParts(importedPartsIdentifiers)) {
				changed = true;
			}
		}
		
		if (changed) {
			fireTableDataChanged();
		}
	}
}

