package charactermanaj.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
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.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.text.MessageFormat;
import java.util.List;
import java.util.Properties;
import java.util.TooManyListenersException;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import charactermanaj.Main;
import charactermanaj.clipboardSupport.ClipboardUtil;
import charactermanaj.graphics.io.FileImageResource;
import charactermanaj.graphics.io.ImageCachedLoader;
import charactermanaj.model.AppConfig;
import charactermanaj.model.CharacterData;
import charactermanaj.model.io.CharacterDataPersistent;
import charactermanaj.model.io.PartsDataLoader;
import charactermanaj.model.io.PartsDataLoaderFactory;
import charactermanaj.model.io.PartsSpecDecorateLoader;
import charactermanaj.model.io.RecentDataPersistent;
import charactermanaj.util.DesktopUtil;
import charactermanaj.util.ErrorMessageHelper;
import charactermanaj.util.LocalizedResourcePropertyLoader;
import charactermanaj.util.UIUtility;


/**
 * プロファイルを選択するためのダイアログ、およびデフォルトプロファイルを開く
 * @author seraphy
 */
public class ProfileSelectorDialog extends JDialog {

	private static final long serialVersionUID = -6883202891172975022L;

	protected static final String STRINGS_RESOURCE = "strings/profileselectordialog";
	
	/**
	 * サンプルイメージをロードするためのローダー
	 */
	private ImageCachedLoader imageLoader = new ImageCachedLoader(10, 60 * 1000);

	
	/**
	 * サンプルイメージファイルが保存可能であるか?<br>
	 * 有効なキャラクターデータが選択されており、サンプルイメージの更新が許可されていればtrue.<br>
	 */
	private boolean canWriteSamplePicture;
	
	/**
	 * サンプルイメージを表示するパネル
	 */
	private SamplePicturePanel sampleImgPanel;

	
	private JButton btnOK;
	
	private JButton btnProfileNew;
	
	private JButton btnProfileEdit;

	private JButton btnProfileRemove;
	
	private JButton btnProfileBrowse;

	private JButton btnProfileImport;

	private JButton btnProfileExport;

	/**
	 * プロファイル一覧を表示するリストコンポーネント
	 */
	private JList characterList;
	
	/**
	 * プロファイル一覧のリストモデル
	 */
	private DefaultListModel characterListModel;
	
	/**
	 * プロファイルの説明用テキストエリア
	 */
	private JTextArea descriptionArea;
	
	/**
	 * プロファイルリスト
	 */
	private List<CharacterData> characterDatas;
	

	/**
	 * ダイアログをOKで閉じた場合に選択していたキャラクターデータを示す.<br>
	 * nullの場合はキャンセルを意味する.
	 */
	private CharacterData selectedCharacterData;

	
	/**
	 * プロファイルの選択ダイアログを構築する.
	 * @param parent 親フレーム、もしくはnull
	 * @param characterDatas プロファイルのリスト
	 */
	private ProfileSelectorDialog(JFrame parent, List<CharacterData> characterDatas) {
		super(parent, true);
		if (characterDatas == null) {
			throw new IllegalArgumentException();
		}
		this.characterDatas = characterDatas;

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

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

		JPanel centerPanel = new JPanel();
		GridLayout gridLayout = new GridLayout(2, 1);
		centerPanel.setLayout(gridLayout);
		
		JPanel pnlProfiles = new JPanel(new BorderLayout());
		
		characterListModel = new DefaultListModel();
		for (CharacterData characterData : characterDatas) {
			characterListModel.addElement(characterData);
		}
		
		characterList = new JList(characterListModel);
		characterList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		characterList.setCellRenderer(new DefaultListCellRenderer() {
			private static final long serialVersionUID = 1L;
			@Override
			public Component getListCellRendererComponent(JList list,
					Object value, int index, boolean isSelected,
					boolean cellHasFocus) {
				CharacterData characterData = (CharacterData) value;
				StringBuilder displayNameBuf = new StringBuilder();
				displayNameBuf.append(characterData.getName());
				if (MainFrame.isUsingCharacterData(characterData)) {
					displayNameBuf.append(strings.getProperty("profile.opend"));
				}
				if (!characterData.canWrite()) {
					displayNameBuf.append(strings.getProperty("profile.noteditable"));
				}
				if (characterData.isDefaultProfile()) {
					displayNameBuf.append(strings.getProperty("profile.default"));
				}
				return super.getListCellRendererComponent(list, displayNameBuf
						.toString(), index, isSelected, cellHasFocus);
			}
		});
		characterList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
				updateUIState();
			}
		});
		characterList.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() >= 2) {
					onOK();
				}
			}
		});
		
		pnlProfiles.add(characterList, BorderLayout.CENTER);
		
		JPanel pnlProfileEditButtons = new JPanel();
		pnlProfileEditButtons.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3));
		GridBagLayout pnlProfileEditButtonsLayout = new GridBagLayout();
		GridBagConstraints gbc = new GridBagConstraints();
		pnlProfileEditButtons.setLayout(pnlProfileEditButtonsLayout);
		
		btnProfileNew = new JButton(new AbstractAction(strings.getProperty("profile.new")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				boolean makeDefault = ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);
				onProfileNew(makeDefault);
			}
		});
		btnProfileEdit = new JButton(new AbstractAction(strings.getProperty("profile.edit")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onProfileEdit();
			}
		});
		btnProfileRemove = new JButton(new AbstractAction(strings.getProperty("profile.remove")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onProfileRemove();
			}
		});
		btnProfileBrowse = new JButton(new AbstractAction(strings.getProperty("profile.browse")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onProfileBrowse();
			}
		});
		btnProfileImport = new JButton(new AbstractAction(strings.getProperty("profile.import")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onProfileImport();
			}
		});
		btnProfileExport = new JButton(new AbstractAction(strings.getProperty("profile.export")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onProfileExport();
			}
		});
		
		btnProfileNew.setToolTipText(strings.getProperty("profile.new.tooltip"));
		
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridwidth = 1;
		gbc.gridheight = 1;
		gbc.weightx = 0.;
		gbc.weighty = 0.;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.BOTH;
		gbc.ipadx = 0;
		gbc.ipady = 0;
		gbc.insets = new Insets(0, 3, 0, 3);
		pnlProfileEditButtons.add(btnProfileNew,gbc);

		gbc.gridx = 0;
		gbc.gridy = 1;
		pnlProfileEditButtons.add(btnProfileEdit, gbc);

		gbc.gridx = 0;
		gbc.gridy = 2;
		pnlProfileEditButtons.add(btnProfileRemove, gbc);

		gbc.gridx = 0;
		gbc.gridy = 3;
		gbc.weighty = 1.;
		pnlProfileEditButtons.add(Box.createGlue(), gbc);

		gbc.gridx = 0;
		gbc.gridy = 4;
		gbc.weighty = 0.;
		pnlProfileEditButtons.add(btnProfileBrowse, gbc);
		
		gbc.gridx = 0;
		gbc.gridy = 5;
		gbc.weighty = 0.;
		pnlProfileEditButtons.add(btnProfileImport, gbc);

		gbc.gridx = 0;
		gbc.gridy = 6;
		pnlProfileEditButtons.add(btnProfileExport, gbc);
		
		JPanel pnlProfilesGroup = new JPanel(new BorderLayout());
		pnlProfilesGroup.setBorder(BorderFactory.createCompoundBorder(BorderFactory
				.createEmptyBorder(3, 3, 3, 3), BorderFactory
				.createTitledBorder(strings.getProperty("profiles"))));
		pnlProfilesGroup.add(pnlProfiles, BorderLayout.CENTER);
		pnlProfilesGroup.add(pnlProfileEditButtons, BorderLayout.EAST);
		

		
		JPanel infoPanel = new JPanel(new GridLayout(1, 2));
		JPanel descriptionPanel = new JPanel(new BorderLayout());
		descriptionPanel.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory
						.createTitledBorder(strings.getProperty("description"))));
		
		descriptionArea = new JTextArea();
		descriptionArea.setEditable(false);
		descriptionArea.setFont(getFont());
		descriptionPanel.add(new JScrollPane(descriptionArea), BorderLayout.CENTER);

		// サンプルピクャパネル
		sampleImgPanel = new SamplePicturePanel();
		sampleImgPanel.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory
						.createTitledBorder(strings.getProperty("sample-image"))));
		
		// サンプルピクチャのドラッグアンドドロップ
		DropTarget dt = new DropTarget();
		try {
			dt.addDropTargetListener(new DropTargetAdapter() {
				public void drop(DropTargetDropEvent dtde) {
					onDrop(dtde);
				}
			});
		} catch (TooManyListenersException ex) {
			throw new RuntimeException(ex.getMessage(), ex);
		}
		sampleImgPanel.setDropTarget(dt);

		// サンプルピクチャのコンテキストメニュー
		final JPopupMenu popupMenu = new JPopupMenu();
		final JMenuItem popupMenuCut = popupMenu.add(new AbstractAction(strings.getProperty("samplepicture.cut")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onSamplePictureCut();
			}
		});
		final JMenuItem popupMenuPaste = popupMenu.add(new AbstractAction(strings.getProperty("samplepicture.paste")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onSamplePicturePaste();
			}
		});
		sampleImgPanel.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()) {
					popupMenuCut.setEnabled(sampleImgPanel.getSamplePictrue() != null && canWriteSamplePicture);
					popupMenuPaste.setEnabled(canWriteSamplePicture && ClipboardUtil.hasImage());
					popupMenu.show(sampleImgPanel, e.getX(), e.getY());
				}
			}
		});
		
		infoPanel.add(descriptionPanel);
		infoPanel.add(sampleImgPanel);
		
		centerPanel.add(pnlProfilesGroup);
		centerPanel.add(infoPanel);

		
		Container contentPane = getContentPane();
		contentPane.setLayout(new BorderLayout());
		contentPane.add(centerPanel, BorderLayout.CENTER);
		
		// OK/CANCEL ボタンパネル

		JPanel btnPanel = new JPanel();
		BoxLayout boxLayout = new BoxLayout(btnPanel, BoxLayout.LINE_AXIS);
		btnPanel.setLayout(boxLayout);
		btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42));
		
		AbstractAction actOK = new AbstractAction(strings.getProperty("btn.select")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onOK();
			}
		};
		AbstractAction actCancel = new AbstractAction(strings.getProperty("btn.cancel")) {
			private static final long serialVersionUID = 1L;
			public void actionPerformed(ActionEvent e) {
				onCancel();
			}
		};
		
		btnOK = new JButton(actOK);
		JButton btnCancel = new JButton(actCancel);
		if (Main.isMacOSX()) {
			btnPanel.add(btnCancel);
			btnPanel.add(btnOK);
		} else {
			btnPanel.add(btnOK);
			btnPanel.add(btnCancel);
		}
		
		JPanel btnPanel2 = new JPanel(new BorderLayout());
		btnPanel2.add(btnPanel, BorderLayout.EAST);
		
		contentPane.add(btnPanel2, BorderLayout.SOUTH);
		
		JRootPane rootPane = getRootPane();
		rootPane.setDefaultButton(btnOK);
		
		InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
		ActionMap am = rootPane.getActionMap();
		im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeProfileSelectorDialog");
		am.put("closeProfileSelectorDialog", actCancel);
		
		pack();

		setSize(400, 500);
		setLocationRelativeTo(parent);
		
		characterList.requestFocus();
		updateUIState();
	}
	
	/**
	 * キャラクターデータの選択変更に伴い、ボタンやサンプルピクチャなどを切り替える.
	 */
	protected void updateUIState() {
		CharacterData characterData = (CharacterData) characterList.getSelectedValue();
		
		final Properties strings = LocalizedResourcePropertyLoader
				.getInstance().getLocalizedProperties(STRINGS_RESOURCE);

		boolean selected = (characterData != null);
		boolean enableEdit = (characterData != null) && characterData.canWrite() && !MainFrame.isUsingCharacterData(characterData);

		btnOK.setEnabled(selected);
	
		btnProfileNew.setEnabled(true);
		btnProfileEdit.setEnabled(selected);
		btnProfileRemove.setEnabled(selected && enableEdit);
		btnProfileImport.setEnabled(true);
		btnProfileExport.setEnabled(selected);
		btnProfileBrowse.setEnabled(selected);
		
		if (enableEdit) {
			btnProfileEdit.setText(strings.getProperty("profile.edit"));
		} else {
			btnProfileEdit.setText(strings.getProperty("profile.edit.readonly"));
		}

		boolean canWriteSamplePicture = false;
		BufferedImage sampleImage = null;

		if (characterData != null && characterData.isValid()) {
			// description
			StringWriter sw = new StringWriter();
			PrintWriter descriptionBuf = new PrintWriter(sw);
			URL docBase = characterData.getDocBase();
			String author = characterData.getAuthor();
			String description = characterData.getDescription();
			if (docBase != null) {
				descriptionBuf.println("configuration: " + docBase);
			}
			if (author != null && author.length() > 0) {
				descriptionBuf.println("author: " + author);
			}
			Dimension imageSize = characterData.getImageSize();
			if (imageSize != null) {
				descriptionBuf.println("size: "
						+ imageSize.width + "x"	+ imageSize.height);
			}
			if (description != null) {
				description = description.replace("\r\n", "\n");
				description = description.replace("\r", "\n");
				description = description.replace("\n", System.getProperty("line.separator"));
				descriptionBuf.println(description);
			}
			descriptionArea.setText(sw.toString());
			descriptionArea.setSelectionStart(0);
			descriptionArea.setSelectionEnd(0);
			
			// sample picture
			try {
				CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
				sampleImage = persist.loadSamplePicture(characterData, imageLoader);
				canWriteSamplePicture = persist.canSaveSamplePicture(characterData);

			} catch (Exception ex) {
				ex.printStackTrace();
				sampleImage = null;
			}
		}
		
		this.canWriteSamplePicture = canWriteSamplePicture;

		String dropHere = strings.getProperty("dropHere");
		String noPicture = strings.getProperty("nopicture");
		sampleImgPanel.setSamplePicture(sampleImage);
		sampleImgPanel.setAlternateText(canWriteSamplePicture ? dropHere : noPicture);
	}
	

	/**
	 * サンプルピクチャのファイルを削除し、表示されている画像をクリップボードに保存する
	 */
	protected void onSamplePictureCut() {
		CharacterData characterData = (CharacterData) characterList.getSelectedValue();
		BufferedImage img = sampleImgPanel.getSamplePictrue();

		Toolkit tk = Toolkit.getDefaultToolkit();
		if (characterData == null || !characterData.isValid() || !canWriteSamplePicture || img == null) {
			tk.beep();
			return;
		}

		try {
			// クリップボードにイメージをセット
			Color bgColor = AppConfig.getInstance().getSampleImageBgColor();
			ClipboardUtil.setImage(img, bgColor);
	
			// サンプルピクチャを削除
			CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
			persist.saveSamplePicture(characterData, null);
			
			// プレビューを更新
			sampleImgPanel.setSamplePicture(null);

		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
		}
	}
	
	/**
	 * サンプルピクチャをクリップボードから貼り付け、それをファイルに保存する
	 */
	protected void onSamplePicturePaste() {
		CharacterData characterData = (CharacterData) characterList.getSelectedValue();
		Toolkit tk = Toolkit.getDefaultToolkit();
		if (characterData == null || !characterData.isValid() || !canWriteSamplePicture) {
			tk.beep();
			return;
		}

		try {
			BufferedImage img = ClipboardUtil.getImage();
			if (img != null) {
				// 画像が読み込まれた場合、それを保存する.
				CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
				persist.saveSamplePicture(characterData, img);
	
				sampleImgPanel.setSamplePicture(img);
	
			} else {
				// サンプルピクチャは更新されなかった。
				tk.beep();
			}

		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
		}
	}
	
	/**
	 * サンプルピクチャパネルにドロップされた画像ファイルをサンプルピクチャとしてコピーします.<br>
	 * @param dtde ドロップイベント
	 */
	protected void onDrop(DropTargetDropEvent dtde) {
		Toolkit tk = Toolkit.getDefaultToolkit();
		CharacterData characterData = (CharacterData) characterList.getSelectedValue();
		if (characterData == null || !characterData.isValid() || !canWriteSamplePicture) {
			tk.beep();
			return;
		}

		try {
			File dropFile = null;
			// 現在選択されているものが編集可能なキャラクターデータであり、
			// かつ、サンプルピクチャの書き込み先ファイルが有効であれば
			// ドロップされたものが1つのファイルであれば受け入れる。
			if (canWriteSamplePicture) {
				for (DataFlavor flavor : dtde.getCurrentDataFlavors()) {
					if (DataFlavor.javaFileListFlavor.equals(flavor)) {
						dtde.acceptDrop(DnDConstants.ACTION_COPY);
						@SuppressWarnings("unchecked")
						List<File> files = (List) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
						if (files.size() == 1){
							dropFile = files.get(0);
						}
						break;
					}
				}
			}

			// ドロップファイルがあれば、イメージとして読み込む
			BufferedImage img = null;
			if (dropFile != null && dropFile.isFile() && dropFile.canRead()) {
				try {
					img = imageLoader.load(new FileImageResource(dropFile));
					
				} catch (IOException ex) {
					// イメージのロードができない = 形式が不正であるなどの場合は
					// 読み込みせず、継続する.
					img = null;
				}
			}
			if (img != null) {
				// 画像が読み込まれた場合、それを保存する.
				CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
				persist.saveSamplePicture(characterData, img);

				sampleImgPanel.setSamplePicture(img);

			} else {
				// サンプルピクチャは更新されなかった。
				tk.beep();
			}
			
		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
		}
	}
	
	/**
	 * 閉じる場合
	 */
	protected void onClosing() {
		dispose();
	}
	
	/**
	 * OKボタン押下
	 */
	protected void onOK() {
		selectedCharacterData = (CharacterData) characterList.getSelectedValue();
		if (selectedCharacterData == null) {
			Toolkit tk = Toolkit.getDefaultToolkit();
			tk.beep();
			return;
		}
		dispose();
	}
	
	/**
	 * キャンセルボタン押下
	 */
	protected void onCancel() {
		selectedCharacterData = null;
		onClosing();
	}
	
	/**
	 * プロファイルの作成
	 * @param makeDefault デフォルトのプロファイルで作成する場合
	 */
	protected void onProfileNew(boolean makeDefault) {
		CharacterDataPersistent persist = CharacterDataPersistent.getInstance();

		CharacterData cd = (CharacterData) characterList.getSelectedValue();

		boolean generateNewId = false;
		if (makeDefault || cd == null) {
			// デフォルトのプロファイルを使用します.
			cd = persist.createDefaultCharacterData();

		} else {
			// 選択しているプロファイルのID,REVを引き継ぐか確認します.
			final Properties strings = LocalizedResourcePropertyLoader
					.getInstance().getLocalizedProperties(STRINGS_RESOURCE);

			int ret = JOptionPane.showConfirmDialog(this, strings
					.getProperty("useSameIdentifiers"), strings
					.getProperty("confirmNewProfile"), JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.QUESTION_MESSAGE);
			if (ret == JOptionPane.CANCEL_OPTION) {
				// 中断.
				return;
			}
			if (ret != JOptionPane.YES_OPTION) {
				generateNewId = true;
			}
		}
		
		// 基本情報をコピーし、必要ならばIDとREVを新規に割り当てて返す.
		CharacterData newCd = cd.duplicateBasicInfo(generateNewId);
		// DocBaseはnullにする。これにより新規作成と判断される.
		newCd.setDocBase(null);

		// 新規なのでパーツセット情報はリセットする
		newCd.clearPartsSets(false);

		ProfileEditDialog editDlg = new ProfileEditDialog(this, newCd, false);
		editDlg.setVisible(true);
		
		newCd = editDlg.getResult();
		if (newCd == null) {
			// キャンセルされた場合
			return;
		}
		
		// 新規プロファイルを保存する.
		try {
			persist.createProfile(newCd);
			persist.saveFavorites(newCd);

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

		// 作成されたプロファイルを一覧に追加する.
		characterListModel.addElement(newCd);
	}

	/**
	 * プロファィルの編集
	 */
	protected void onProfileEdit() {
		CharacterData cd = (CharacterData) characterList.getSelectedValue();
		int rowIndex = characterList.getSelectedIndex();
		if (cd == null || !cd.isValid() || rowIndex < 0) {
			return;
		}

		CharacterDataPersistent persist = CharacterDataPersistent.getInstance();

		try {
			persist.loadFavorites(cd);

		} catch (IOException ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
			// Favoritesの読み込みに失敗しても継続する.
		}
		
		// 現在開いているプロファイルは参照モード
		boolean openedProfile = MainFrame.isUsingCharacterData(cd);
		
		// 編集用ダイアログを構築して開く
		ProfileEditDialog editDlg = new ProfileEditDialog(this, cd, openedProfile);
		editDlg.setVisible(true);

		// 編集結果を得る. 
		CharacterData newCd = editDlg.getResult();
		if (newCd == null) {
			// キャンセルされた場合
			return;
		}
		
		// 編集結果が編集前と構造的に同じであるか?
		boolean structureCompatible = newCd.isSameStructure(cd);

		try {
			// 保存する.
			persist.updateProfile(newCd, !structureCompatible);
			persist.saveFavorites(newCd);

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

		// プロファイル一覧画面も更新する.
		characterListModel.set(rowIndex, newCd);
		characterList.repaint();
	}

	/**
	 * プロファイルの削除
	 */
	protected void onProfileRemove() {
		CharacterData cd = (CharacterData) characterList.getSelectedValue();
		if (cd == null || !cd.isValid()) {
			return;
		}
		
		final Properties strings = LocalizedResourcePropertyLoader
				.getInstance().getLocalizedProperties(STRINGS_RESOURCE);

		String msgTempl = strings.getProperty("profile.remove.confirm");
		MessageFormat fmt = new MessageFormat(msgTempl);
		String msg = fmt.format(new Object[]{cd.getName()});
		
		JPanel msgPanel = new JPanel(new BorderLayout(5, 5));
		msgPanel.add(new JLabel(msg), BorderLayout.CENTER);
		JCheckBox chkRemoveForce = new JCheckBox(strings.getProperty("profile.remove.force"));
		msgPanel.add(chkRemoveForce, BorderLayout.SOUTH);

		JOptionPane optionPane = new JOptionPane(msgPanel, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION) {
			private static final long serialVersionUID = 1L;
			@Override
			public void selectInitialValue() {
				String noBtnCaption = UIManager.getString("OptionPane.noButtonText");
				for (JButton btn : UIUtility.getInstance().getDescendantOfClass(JButton.class, this)) {
					if (btn.getText().equals(noBtnCaption)) {
						// 「いいえ」ボタンにフォーカスを設定
						btn.requestFocus();
					}
				}
			}
		};
		JDialog dlg = optionPane.createDialog(this, strings.getProperty("confirm.remove"));
		dlg.setVisible(true);
		Object ret = optionPane.getValue();
		if (ret == null || ((Number) ret).intValue() != JOptionPane.YES_OPTION) {
			return;
		}
		
		if (!cd.canWrite() || cd.getDocBase() == null) {
			JOptionPane.showMessageDialog(this, strings.getProperty("profile.remove.cannot"));
			return;
		}
		
		boolean forceRemove = chkRemoveForce.isSelected();
		
		try {
			CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();
			persiste.remove(cd, forceRemove);

		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
			return;
		}
	
		// モデルから該当キャラクターを削除して再描画
		characterListModel.removeElement(cd);
		characterList.repaint();
		updateUIState();
	}
	
	/**
	 * 場所を開く
	 */
	protected void onProfileBrowse() {
		CharacterData cd = (CharacterData) characterList.getSelectedValue();
		if (cd == null || !cd.isValid()) {
			Toolkit tk = Toolkit.getDefaultToolkit();
			tk.beep();
			return;
		}
		try {
			URL docBase = cd.getDocBase();
			if (!DesktopUtil.browseBaseDir(docBase)) {
				JOptionPane.showMessageDialog(this, docBase);
			}
		} catch (Exception ex) {
			ErrorMessageHelper.showErrorDialog(this, ex);
		}
	}
	
	/**
	 * インポート
	 */
	protected void onProfileImport() {
		CharacterData selCd = (CharacterData) characterList.getSelectedValue();

		// キャラクターデータをロードし直す.
		CharacterData cd;
		if (selCd != null) {
			cd = selCd.duplicateBasicInfo(false);
			try {
				loadCharacterData(cd);
				
			} catch (IOException ex) {
				ErrorMessageHelper.showErrorDialog(this, ex);
				// 継続する.
			}
		} else {
			cd = null;
		}
		
		// インポートウィザードの実行
		ImportWizardDialog importWizDialog = new ImportWizardDialog(this, cd, characterDatas);
		importWizDialog.setVisible(true);
		
		if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_CREATED) {
			CharacterData newCd = importWizDialog.getImportedCharacterData();

			// 作成されたプロファイルを一覧に追加する.
			characterListModel.addElement(newCd);
		}
	}

	/**
	 * エクスポート
	 */
	protected void onProfileExport() {
		CharacterData cd = (CharacterData) characterList.getSelectedValue();
		if (cd == null || !cd.isValid()) {
			Toolkit tk = Toolkit.getDefaultToolkit();
			tk.beep();
			return;
		}
		
		try {
			// コピーした情報に対してパーツデータをロードする.
			CharacterData newCd = cd.duplicateBasicInfo(false);
			setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			try {
				PartsDataLoaderFactory loaderFactory = PartsDataLoaderFactory.getInstance();
				PartsDataLoader loader = loaderFactory.createPartsLoader(newCd.getDocBase());
				newCd.loadPartsData(loader);

			} finally {
				setCursor(Cursor.getDefaultCursor());
			}

			// エクスポートウィザードを表示
			BufferedImage sampleImage = sampleImgPanel.getSamplePictrue();
			ExportWizardDialog exportWizDialog = new ExportWizardDialog(this, newCd, sampleImage);
			exportWizDialog.setVisible(true);

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

	
	/**
	 * プロファイル選択ダイアログを表示し、選択されたプロファイルのメインフレームを作成して返す.<br>
	 * プロファイルの選択をキャンセルした場合はnullを返す.<br>
	 * @param parent 親フレーム
	 * @return メインフレーム、もしくはnull
	 * @throws IOException 読み込みに失敗した場合
	 */
	public static MainFrame openProfile(JFrame parent) throws IOException {
		// キャラクタープロファイルのリストをロード
		CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
		List<CharacterData> characterDatas = persist.listProfiles(CharacterDataPersistent.DEFAULT_ERROR_HANDLER);

		// 選択ダイアログを表示
		ProfileSelectorDialog inst = new ProfileSelectorDialog(parent, characterDatas);
		inst.setVisible(true);

		CharacterData characterData = inst.selectedCharacterData;
		if (characterData == null || !characterData.isValid()) {
			// キャンセルしたか、開くことのできないデータ
			return null;
		}

		// メインフレームを準備
		MainFrame newFrame;
		parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		try {
			// キャラクターデータのロード
			loadCharacterData(characterData);

			// メインフレームを構築
			newFrame = new MainFrame(characterData);

			// 最後に使ったプロファイルとして登録
			saveRecent(characterData);

		} finally {
			parent.setCursor(Cursor.getDefaultCursor());
		}
		return newFrame;
	}
	
	/**
	 * 最後にしようしたプロファイル、それがなければデフォルトプロファイルを開いて、そのメインフレームを返す.
	 * @return メインフレーム 
	 * @throws IOException 開けなかった場合
	 */
	public static MainFrame openDefaultProfile() throws IOException {

		AppConfig appConfig = AppConfig.getInstance();
		CharacterDataPersistent persistent = CharacterDataPersistent.getInstance();

		CharacterData characterData;

		// 最後に使用したプロファイルのロードを試行する.
		try {
			characterData = loadRecent();

		} catch (IOException ex) {
			ErrorMessageHelper.showErrorDialog(null, ex);
			characterData = null;
		}

		// 最後に使用したプロファイルの記録がないか、プロファイルのロードに失敗した場合は
		// プロファイル一覧からデフォルトのプロファイルを選択する.
		// 該当がなければ最初のプロファイルを選択する.
		// ひとつもなければダミーのプロファイルを作成する.
		if (characterData == null) {
			List<CharacterData> profiles = persistent.listProfiles(CharacterDataPersistent.DEFAULT_ERROR_HANDLER);

			if (profiles.size() > 0) {
				String defaultProfileName = appConfig.getDefaultProfileId();
				if (defaultProfileName != null && defaultProfileName.trim().length() > 0) {
					for (CharacterData item : profiles) {
						if (item.getId().equals(defaultProfileName)) {
							characterData = item;
							break;
						}
					}
				}
				if (characterData == null) {
					characterData = profiles.get(0);
				}

			} else {
				try {
					// プロファイルが一個もなければ、デフォルトのプロファイルの生成を試行する.
					characterData = persistent.createDefaultCharacterData();
					persistent.createProfile(characterData);

				} catch (IOException ex) {
					ex.printStackTrace();
					
					// キャラクター定義として無効なダミーのインスタンスを生成して返す.
					// 何もできないが、メインフレームを空の状態で表示させることは可能.
					characterData = new CharacterData();
				}
			}
		}

		// キャラクターデータを読み込む
		loadCharacterData(characterData);
		
		// 最後に使用したプロファイルとして記録
		saveRecent(characterData);

		// メインフレームを生成して返す
		return new MainFrame(characterData);
	}
	
	/**
	 * キャラクターデータに、パーツデータとお気に入りをロードする.<br>
	 * @param characterData
	 * @throws IOException 開けなかった場合
	 */
	protected static void loadCharacterData(CharacterData characterData) throws IOException {
		if (characterData != null && characterData.isValid()) {
			PartsDataLoaderFactory loaderFactory = PartsDataLoaderFactory.getInstance();
			PartsDataLoader loader = loaderFactory.createPartsLoader(characterData.getDocBase());
			PartsDataLoader colorGroupInfoDecorater = new PartsSpecDecorateLoader(loader, characterData.getColorGroups());
			characterData.loadPartsData(colorGroupInfoDecorater);

			CharacterDataPersistent persistent = CharacterDataPersistent.getInstance();
			persistent.loadFavorites(characterData);
		}
	}
	

	/**
	 * 最後に使用したキャラクターデータとして記録する.
	 * @param characterData
	 * @throws IOException 保存できなった場合
	 */
	protected static void saveRecent(CharacterData characterData) throws IOException {
		RecentDataPersistent recentPersist = RecentDataPersistent.getInstance();
		recentPersist.saveRecent(characterData);
	}
	
	/**
	 * 最後に使用したキャラクターデータを取得する.
	 * @return キャラクターデータ。最後に使用したデータが存在しない場合はnull
	 * @throws IOException 読み込みに失敗した場合
	 */
	protected static CharacterData loadRecent() throws IOException {
		RecentDataPersistent recentPersist = RecentDataPersistent.getInstance();
		return recentPersist.loadRecent();
	}
}
