package jp.ac.osaka_u.sanken.sparql.gui;

import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;

import jp.ac.osaka_u.sanken.sparql.EndpointSettings;
import jp.ac.osaka_u.sanken.sparql.EndpointSettingsManager;
import jp.ac.osaka_u.sanken.sparql.SparqlAccessor;
import jp.ac.osaka_u.sanken.sparql.SparqlAccessorFactory;
import jp.ac.osaka_u.sanken.sparql.SparqlQueryListener;
import jp.ac.osaka_u.sanken.sparql.SparqlResultListener;
import jp.ac.osaka_u.sanken.sparql.SparqlResultSet;
import jp.ac.osaka_u.sanken.sparql.ThreadedSparqlAccessor;
import jp.ac.osaka_u.sanken.util.EditableListItem;
import jp.ac.osaka_u.sanken.util.EditableList;
import jp.ac.osaka_u.sanken.util.StringUtil;

import com.hp.hpl.jena.rdf.model.RDFNode;

public class CrossKeywordSearchPanel extends JPanel {

	private static final long serialVersionUID = 1L;
	private JPanel keywordPanel = null;
	private JLabel keywordLabel = null;
	private JTextField keywordTextField = null;
	private JTable subjectList = null;
	private JScrollPane subjectScrollPane = null;
	private JPanel centerPanel = null;
	private JPanel footerPanel = null;
	private JScrollPane resultListScrollPane = null;  //  @jve:decl-index=0:visual-constraint="153,224"
	private JTable resultList = null;  //  @jve:decl-index=0:visual-constraint="369,21"
	private JButton runQueryButton = null;  //  @jve:decl-index=0:visual-constraint="390,64"
	private JSplitPane mainSplitPane = null;
	private JPanel optionPanel = null;
	private JPanel findTypePanel = null;
	private JRadioButton fullMatchRadioButton = null;
	private JRadioButton partMatchRadioButton = null;
	private JSeparator findSeparator = null;

	private boolean processing = false;
	private JPanel headerPanel = null;
	private JPanel limitPanel = null;
	private JCheckBox limitEnableCheckBox = null;
	private JLabel limitLabel = null;
	private JComboBox limitComboBox = null;
	private JRadioButton findSubjectRadioButton = null;
	private JRadioButton findObjectRadioButton = null;
	private JRadioButton findAllRadioButton = null;
	private JRadioButton findLabelObjectRadioButton = null;
	private Date fromDate;
	private JPanel movePanel = null;
	private JButton prevButton = null;
	private JButton nextButton = null;
	private JPanel limitMainPanel = null;
	private JButton limitPrevButton = null;
	private JButton limitNextButton = null;



	private SparqlAccessorForm parent;

	private DefaultTableModel listModel;
	private DefaultTableModel tableModel;

	private ThreadedSparqlAccessor sa;  //  @jve:decl-index=0:

	/* subjectジャンプヒストリ */
	private int historyIndex = 0;
	private List<String> history;  //  @jve:decl-index=0:
	private LinkedHashMap<String, List<String>> subjectHistoryList;

	/* limitヒストリ */
	private Integer limit = null;  //  @jve:decl-index=0:
	private int page = 0;
	private String word;
	private boolean fullMatch = false;
	private int type;
	private boolean hasLimitNext = false;

	private static final String DEFAULT_PROPERTY_TYPE = "http://www.w3.org/2000/01/rdf-schema#label";


	/**
	 * This is the default constructor
	 */
	public CrossKeywordSearchPanel(SparqlAccessorForm parent) {
		super();
		initialize();
		this.parent = parent;
	}

	/**
	 * This method initializes this
	 *
	 * @return void
	 */
	private void initialize() {
		this.setSize(300, 200);
		this.setLayout(new BorderLayout());
		this.add(getHeaderPanel(), BorderLayout.NORTH);
		this.add(getMainSplitPane(), BorderLayout.CENTER);
	}

	private JSplitPane getMainSplitPane(){
		if (mainSplitPane == null){
			mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, getCenterPanel(), getFooterPanel());
			mainSplitPane.setDividerLocation(200);
		}

		return mainSplitPane;
	}

	/**
	 * This method initializes keywordPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getKeywordPanel() {
		if (keywordPanel == null) {
			keywordPanel = new JPanel();
			keywordPanel.setLayout(new BorderLayout());
			keywordLabel = new JLabel();
			keywordLabel.setText("Enter Keyword");
			keywordPanel.add(keywordLabel, BorderLayout.WEST);
			keywordPanel.add(getKeywordTextField(), BorderLayout.CENTER);
			keywordPanel.add(getRunQueryButton(), BorderLayout.EAST);
		}
		return keywordPanel;
	}


	/**
	 * This method initializes optionPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getOptionPanel() {
		if (optionPanel == null) {
			optionPanel = new JPanel();
			optionPanel.setLayout(new BorderLayout());
			optionPanel.add(getFindTypePanel(), BorderLayout.CENTER);
			optionPanel.add(getLimitPanel(), BorderLayout.EAST);
		}
		return optionPanel;
	}

	/**
	 * This method initializes findTypePanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getFindTypePanel() {
		if (findTypePanel == null) {
			FlowLayout flowLayout = new FlowLayout();
			flowLayout.setHgap(10);
			flowLayout.setVgap(0);
			findTypePanel = new JPanel();
			findTypePanel.setLayout(flowLayout);
			findTypePanel.add(getFullMatchRadioButton(), null);
			findTypePanel.add(getPartMatchRadioButton(), null);
			findTypePanel.add(getFindSeparator(), null);
			findTypePanel.add(getFindAllRadioButton(), null);
			findTypePanel.add(getFindSubjectRadioButton(), null);
			findTypePanel.add(getFindObjectRadioButton(), null);
			findTypePanel.add(getFindLabelObjectRadioButton(), null);
			ButtonGroup bg = new ButtonGroup();
			bg.add(getFullMatchRadioButton());
			bg.add(getPartMatchRadioButton());
			getFullMatchRadioButton().setSelected(true);
			ButtonGroup bg2 = new ButtonGroup();
			bg2.add(getFindAllRadioButton());
			bg2.add(getFindSubjectRadioButton());
			bg2.add(getFindObjectRadioButton());
			bg2.add(getFindLabelObjectRadioButton());
			getFindSubjectRadioButton().setSelected(true);
		}
		return findTypePanel;
	}

	private boolean isFullMatch(){
		return getFullMatchRadioButton().isSelected();
	}

	private int getFindType(){
		if (getFindSubjectRadioButton().isSelected()){
			return SparqlAccessor.FIND_TARGET_SUBJECT;
		}
		if (getFindObjectRadioButton().isSelected()){
			return SparqlAccessor.FIND_TARGET_OBJECT;
		}
		if (getFindLabelObjectRadioButton().isSelected()){
			return SparqlAccessor.FIND_TARGET_SPECIFIC_OBJECT;
		}
		return SparqlAccessor.FIND_TARGET_ALL;
	}

	/**
	 * This method initializes fulMatchRadioButton
	 *
	 * @return javax.swing.JRadioButton
	 */
	private JRadioButton getFullMatchRadioButton() {
		if (fullMatchRadioButton == null) {
			fullMatchRadioButton = new JRadioButton("Full Match");
		}
		return fullMatchRadioButton;
	}

	/**
	 * This method initializes partMatchRadioButton
	 *
	 * @return javax.swing.JRadioButton
	 */
	private JRadioButton getPartMatchRadioButton() {
		if (partMatchRadioButton == null) {
			partMatchRadioButton = new JRadioButton("Part Match");
		}
		return partMatchRadioButton;
	}

	/**
	 * This method initializes findSeparator
	 *
	 * @return JSeparator
	 */
	private JSeparator getFindSeparator() {
		if (findSeparator == null) {
			findSeparator = new JSeparator(SwingConstants.VERTICAL);
			findSeparator.setPreferredSize(new Dimension(5, 20));
		}
		return findSeparator;
	}


	/**
	 * This method initializes keywordTextField
	 *
	 * @return javax.swing.JTextField
	 */
	private JTextField getKeywordTextField() {
		if (keywordTextField == null) {
			keywordTextField = new JTextField();
			keywordTextField.addKeyListener(new KeyAdapter() {
				@Override
				public void keyTyped(KeyEvent arg0) {
					if (arg0.getKeyChar() == KeyEvent.VK_ENTER){
						doSearch();
					}
				}
			});
		}
		return keywordTextField;
	}

	/**
	 * 検索ワードを取得する
	 * @return
	 */
	private String getFindWord(){
		return getKeywordTextField().getText();
	}


	/**
	 * This method initializes subjectList
	 *
	 * @return javax.swing.JList
	 */
	private JTable getSubjectList() {
		if (subjectList == null) {
			subjectList = new JTable(){
				/**
				 *
				 */
				private static final long serialVersionUID = -3850550815070973157L;

				public String getToolTipText(MouseEvent e){
		            // イベントからマウス位置を取得し、テーブル内のセルを割り出す
					Object cell = getModel().getValueAt(rowAtPoint(e.getPoint()), columnAtPoint(e.getPoint()));
		            return cell == null ? "" : StringUtil.makeHtmlString(StringUtil.splitString(cell.toString()));
		        }
			};
			subjectList.setDefaultEditor(Object.class, null);
			subjectList.addMouseListener(new MouseAdapter() {

				@Override
				public void mouseClicked(MouseEvent e) {
					int index = subjectList.getSelectedRow();
					Object val = (subjectList.getValueAt(index, 1));
					String subject = val.toString();
					if (e.getClickCount() >= 2){

						if (findSubjectTriple(subject)){
							addHistory(subject);
						}
					}

					if (SwingUtilities.isRightMouseButton(e)){
						awakePopupIfHtml(val, e.getLocationOnScreen());
					}

				}
			});

		}
		return subjectList;
	}

	private SparqlResultListener createSparqlResultListener2(){
		return new SparqlResultListener() {

			@Override
			public void resultReceived(SparqlResultSet result) {
				// 結果をまずはlistに追加
				setProcessing(false);
				setResults(result);
			}

			@Override
			public void uncaughtException(Thread t, Throwable e) {
				JOptionPane.showMessageDialog(parent, "Execute error:"+e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				setProcessing(false);

			}
		};
	}

	private void setSubjectList(String endpoint, String obj){
		listModel.addRow(new String[]{endpoint, obj});
	}

	private DefaultTableModel clearSubjectList(){
		listModel = new DefaultTableModel(new String[]{"endpoint", "result"}, 0);
		getSubjectList().setModel(listModel);
		return listModel;
	}

	private void clearHistory(){
		subjectHistoryList = new LinkedHashMap<String, List<String>>();
	}

	private void setSubjectList(String endpoint, List<String> list){
		subjectHistoryList.put(endpoint, list);
		for (String item : list){
			listModel.addRow(new String[]{endpoint, item});
		}
		getSubjectList().setModel(listModel);
	}

	/**
	 * This method initializes subjectScrollPane
	 *
	 * @return javax.swing.JScrollPane
	 */
	private JScrollPane getSubjectScrollPane() {
		if (subjectScrollPane == null) {
			subjectScrollPane = new JScrollPane(getSubjectList());
		}
		return subjectScrollPane;
	}

	/**
	 * This method initializes centerPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getCenterPanel() {
		if (centerPanel == null) {
			centerPanel = new JPanel();
			centerPanel.setLayout(new BorderLayout());
			centerPanel.add(getSubjectScrollPane(), BorderLayout.CENTER);
		}
		return centerPanel;
	}

	/**
	 * This method initializes footerPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getFooterPanel() {
		if (footerPanel == null) {
			footerPanel = new JPanel();
			footerPanel.setLayout(new BorderLayout());
			footerPanel.add(getResultListScrollPane(), BorderLayout.CENTER);
		}
		return footerPanel;
	}

	/**
	 * This method initializes resultListScrollPane
	 *
	 * @return javax.swing.JScrollPane
	 */
	private JScrollPane getResultListScrollPane() {
		if (resultListScrollPane == null) {
			resultListScrollPane = new JScrollPane(getResultList());
		}
		return resultListScrollPane;
	}

	/**
	 * This method initializes resultList
	 *
	 * @return javax.swing.JList
	 */
	private JTable getResultList() {
		if (resultList == null) {
			resultList = new JTable(){
				/**
				 *
				 */
				private static final long serialVersionUID = 3332757068706348175L;

				public String getToolTipText(MouseEvent e){
		            // イベントからマウス位置を取得し、テーブル内のセルを割り出す
					Object cell = getModel().getValueAt(rowAtPoint(e.getPoint()), columnAtPoint(e.getPoint()));
		            return cell == null ? "" : StringUtil.makeHtmlString(StringUtil.splitString(cell.toString()));
		        }
			};
			resultList.setDefaultEditor(Object.class, null);
			resultList.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					int index = resultList.getSelectedRow();

					Object val = (resultList.getValueAt(index, 1));
					String o = val.toString();

					if (e.getClickCount() >= 2){

						if (findSubjectTriple(o)){
							clearSubjectList();

							setSubjectList("", o); // TODO endpoint
							addHistory(o);
						}
					}

					if (SwingUtilities.isRightMouseButton(e)){
						awakePopupIfHtml(val, e.getLocationOnScreen());
					}

				}
			});

		}
		return resultList;
	}

	private void setResults(SparqlResultSet result){

		if (result == null || result.getDefaultResult() == null || result.getDefaultResult().size() == 0){
			tableModel = new DefaultTableModel();
			getResultList().setModel(tableModel);
			return;
		}
		List<Map<String, RDFNode>> list = result.getDefaultResult();

		// headerセット
		Map<String, RDFNode> columns = list.get(0);
		List<String> clm = new ArrayList<String>();
		for (String key : columns.keySet()){
			clm.add(key);
		}
		tableModel = new DefaultTableModel(clm.toArray(new String[]{}), 0);


		// 中身セット
		for (Map<String, RDFNode> r : list){
			List<Object> row = new ArrayList<Object>();
			for (String key : clm){
				RDFNode node = r.get(key);
				row.add(node);
			}
			tableModel.addRow(row.toArray(new Object[]{}));
		}

		getResultList().setModel(tableModel);

		parent.setResults(result);

	}

	private void setProcessing(boolean processing){
		this. processing = processing;
		getSubjectList().setEnabled(!processing);
		getRunQueryButton().setEnabled(!processing);
		getResultList().setEnabled(!processing);
		getKeywordTextField().setEnabled(!processing);
		getFullMatchRadioButton().setEnabled(!processing);
		getPartMatchRadioButton().setEnabled(!processing);
//		getFindAllRadioButton().setEnabled(!processing);
		getFindSubjectRadioButton().setEnabled(!processing);
		getFindObjectRadioButton().setEnabled(!processing);
		getFindLabelObjectRadioButton().setEnabled(!processing);

		updateButtonStates();
		updateLimitButtonStates();

		parent.setProcessing(processing);
	}

	private boolean isProcessing(){
		return this.processing;
	}

	/**
	 * This method initializes runQueryButton
	 *
	 * @return javax.swing.JButton
	 */
	private JButton getRunQueryButton() {
		if (runQueryButton == null) {
			runQueryButton = new JButton("Find");
			runQueryButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					doSearch();
				}
			});
		}
		return runQueryButton;
	}

	private void doSearch(){
		if (isProcessing()){
			return;
		}

		initHistory();
		initLimit();

		sa = createSparqlAccessor(true);
		if (sa == null){
			return;
		}
		setResults(null);
		setProcessing(true);


		// page切替用にバックアップ
		this.word = getFindWord();
		this.fullMatch = isFullMatch();
		this.limit = getLimit();
		this.type = getFindType();

		sa.findSubject(word, fullMatch, limit, (limit != null ? (limit * page) : null), type, getTargetPropertyList(), createSparqlResultListener());


	}

	private ThreadedSparqlAccessor createSparqlAccessor(boolean needEndpointDialog){
		List<EndpointSettings> settings = getEndpointSettings(needEndpointDialog);
		if (settings == null || settings.size() == 0){
			return null;
		}
		ThreadedSparqlAccessor tsa = SparqlAccessorFactory.createSparqlAccessor(settings, new SparqlQueryListener() {
			@Override
			public void sparqlExecuted(String query) {
				fromDate = new Date();
				parent.addLogText("----------------");
				parent.addLogText(query);
			}
		});

		return tsa;
	}

	private List<EndpointSettings> getEndpointSettings(boolean withUi){
		List<EndpointSettings> settings = new ArrayList<EndpointSettings>();

		EndpointSelector es = new EndpointSelector(null, EndpointSettingsManager.instance.getSettings());
		if (withUi){
			es.setVisible(true);
			if (!es.isValid()){
				return null;
			}
		}
		EndpointSettings[] result = es.getSettings();

		for (EndpointSettings setting : result){
			if (setting.isTarget()){
				settings.add(setting);
			}
		}

		return settings;
	}

	private SparqlResultListener createSparqlResultListener(){
		return new SparqlResultListener() {

			@Override
			public void resultReceived(SparqlResultSet result) {

				hasLimitNext = result.isHasNext();

				Date now = new Date();
				long time = now.getTime() - fromDate.getTime();
				parent.addLogText("---------------- result:"+time+" ms");
				clearHistory();
				clearSubjectList();
				// 結果をまずはlistに追加
				for (String endpoint : result.getResult().keySet()){
					List<String> resultList = new ArrayList<String>();
					List<Map<String, RDFNode>> results = result.getResult().get(endpoint);
					for (Map<String, RDFNode> item : results){
						RDFNode node = item.get("s");
						resultList.add(node.toString());
					}
					setSubjectList(endpoint, resultList);
				}

				setProcessing(false);
			}

			@Override
			public void uncaughtException(Thread t, Throwable e) {
				JOptionPane.showMessageDialog(parent, "Execute error:"+e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				setProcessing(false);

			}
		};
	}

	/**
	 * This method initializes headerPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getHeaderPanel() {
		if (headerPanel == null) {
			headerPanel = new JPanel();
			headerPanel.setLayout(new BorderLayout());
			headerPanel.add(getKeywordPanel(), BorderLayout.CENTER);
			headerPanel.add(getOptionPanel(), BorderLayout.SOUTH);
			headerPanel.add(getMovePanel(), BorderLayout.WEST);
		}
		return headerPanel;
	}

	/**
	 * This method initializes limitPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getLimitPanel() {
		if (limitPanel == null) {
			limitLabel = new JLabel();
			limitLabel.setText(" LIMIT  ");
			limitLabel.setEnabled(false);
			limitPanel = new JPanel();
			limitPanel.setLayout(new BorderLayout());
			limitPanel.add(getLimitEnableCheckBox(), BorderLayout.WEST);
			limitPanel.add(limitLabel, BorderLayout.CENTER);
			limitPanel.add(getLimitMainPanel(), BorderLayout.EAST);
		}
		return limitPanel;
	}

	private Integer getLimit(){
		try {
			if (getLimitComboBox().isEnabled()){
				return Integer.parseInt((String)getLimitComboBox().getSelectedItem());
			}
		} catch(Exception e){}
		return null;
	}

	/**
	 * This method initializes limitEnableCheckBox
	 *
	 * @return javax.swing.JCheckBox
	 */
	private JCheckBox getLimitEnableCheckBox() {
		if (limitEnableCheckBox == null) {
			limitEnableCheckBox = new JCheckBox();
			limitEnableCheckBox.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					boolean enabled = getLimitEnableCheckBox().isSelected();
					getLimitComboBox().setEnabled(enabled);
					limitLabel.setEnabled(enabled);
					updateLimitButtonStates();
				}
			});
		}
		return limitEnableCheckBox;
	}

	/**
	 * This method initializes limitComboBox
	 *
	 * @return javax.swing.JComboBox
	 */
	private JComboBox getLimitComboBox() {
		if (limitComboBox == null) {
			String[] limits = {"100","250","500","1000"};
			limitComboBox = new JComboBox(limits);
			limitComboBox.setEnabled(false);
		}
		return limitComboBox;
	}

	/**
	 * This method initializes findSubjectRadioButton
	 *
	 * @return javax.swing.JRadioButton
	 */
	private JRadioButton getFindSubjectRadioButton() {
		if (findSubjectRadioButton == null) {
			findSubjectRadioButton = new JRadioButton("Find Subject");
		}
		return findSubjectRadioButton;
	}

	/**
	 * This method initializes findObjectRadioButton
	 *
	 * @return javax.swing.JRadioButton
	 */
	private JRadioButton getFindObjectRadioButton() {
		if (findObjectRadioButton == null) {
			findObjectRadioButton = new JRadioButton("Find All Object");
		}
		return findObjectRadioButton;
	}

	/**
	 * This method initializes findAllRadioButton
	 *
	 * @return javax.swing.JRadioButton
	 */
	private JRadioButton getFindAllRadioButton() {
		if (findAllRadioButton == null) {
			findAllRadioButton = new JRadioButton("Find All");
			findAllRadioButton.setEnabled(false);
		}
		return findAllRadioButton;
	}

	/**
	 * This method initializes findLabelObjectRadioButton
	 *
	 * @return javax.swing.JRadioButton
	 */
	private JRadioButton getFindLabelObjectRadioButton() {
		if (findLabelObjectRadioButton == null) {
			findLabelObjectRadioButton = new JRadioButton("Find Specific Object");
			findLabelObjectRadioButton.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					awakePropertyListPopup(e.getLocationOnScreen());
				}
			});
		}
		return findLabelObjectRadioButton;
	}

	/**
	 * This method initializes movePanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getMovePanel() {
		if (movePanel == null) {
			movePanel = new JPanel();
			movePanel.setLayout(new BoxLayout(movePanel, BoxLayout.X_AXIS));
			movePanel.add(getPrevButton(), null);
			movePanel.add(getNextButton(), null);
		}
		return movePanel;
	}

	/**
	 * This method initializes prevButton
	 *
	 * @return javax.swing.JButton
	 */
	private JButton getPrevButton() {
		if (prevButton == null) {
			prevButton = new JButton("←");
			prevButton.setMargin(new Insets(0, 10, 0, 10));
			prevButton.setEnabled(false);
			prevButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent arg0) {
					String s = getPrev();
					if (s != null){
						clearSubjectList();
						if (historyIndex == 0){
							for (String key : subjectHistoryList.keySet()){
								setSubjectList(key, subjectHistoryList.get(key)); // 全体のヒストリリスト
							}
						} else {
							setSubjectList("", s);
						}
						findSubjectTriple(s);
					}
				}
			});
		}
		return prevButton;
	}

	/**
	 * This method initializes nextButton
	 *
	 * @return javax.swing.JButton
	 */
	private JButton getNextButton() {
		if (nextButton == null) {
			nextButton = new JButton("→");
			nextButton.setMargin(new Insets(0, 10, 0, 10));
			nextButton.setEnabled(false);
			nextButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent arg0) {
					String s = getNext();
					if (s != null){
						clearSubjectList();
						setSubjectList("", s); // TODO endpoint
						findSubjectTriple(s);
					}
				}
			});
		}
		return nextButton;
	}

	private boolean findSubjectTriple(String s){
		if (isProcessing()){
			return false;
		}
		setProcessing(true);
		if (!sa.findTripleFromSubject(s, createSparqlResultListener2())){
			//
			setProcessing(false);
//			awakeIfHtml(s);
			return false;
		}
		// tableクリア
		setResults(null);
		return true;
	}

	private boolean awakePopupIfHtml(Object o, Point p){

		if (o == null || o.toString().trim().isEmpty()){
			return false;
		}
		String s = o.toString();

		if (o instanceof RDFNode){
			if (((RDFNode)o).isLiteral()){
				s = ((RDFNode)o).asLiteral().getString();
			}
		}
		JPopupMenu popup = new JPopupMenu(s);

		if (s.startsWith("http://") || s.startsWith("https://")){
			JMenuItem menu = new JMenuItem("Show on Web Browser");
			menu.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					JMenuItem menu = (JMenuItem)e.getSource();
					JPopupMenu parent = (JPopupMenu)menu.getParent();
					try {
						awakeHtml(parent.getLabel());
					} catch (URISyntaxException ex) {
						ex.printStackTrace();
					} catch (IOException ex) {
						ex.printStackTrace();
					}
				}
			});
			popup.add(menu);
		} else {
			JMenuItem menu1 = new JMenuItem("Search By Google");
			menu1.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					JMenuItem menu = (JMenuItem)e.getSource();
					JPopupMenu parent = (JPopupMenu)menu.getParent();
					try {
						awakeHtml("https://www.google.co.jp/search?q=" + URLEncoder.encode(parent.getLabel(), "UTF-8"));
					} catch (URISyntaxException ex) {
						ex.printStackTrace();
					} catch (IOException ex) {
						ex.printStackTrace();
					}
				}
			});
			popup.add(menu1);
			JMenuItem menu2 = new JMenuItem("Search By GoogleMaps");
			menu2.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					JMenuItem menu = (JMenuItem)e.getSource();
					JPopupMenu parent = (JPopupMenu)menu.getParent();
					try {
						awakeHtml("https://maps.google.com/maps?q=" + URLEncoder.encode(parent.getLabel(), "UTF-8"));
					} catch (URISyntaxException ex) {
						ex.printStackTrace();
					} catch (IOException ex) {
						ex.printStackTrace();
					}
				}
			});
			popup.add(menu2);
		}

		popup.setLocation(p);

		SwingUtilities.convertPointFromScreen(p, this.parent);

		popup.show(this.parent, p.x, p.y);

		return false;
	}

	private void awakeHtml(String url) throws IOException, URISyntaxException{
		Desktop desktop = Desktop.getDesktop();
		URI uri = new URI(url);
		desktop.browse(uri);
	}

	// ここから、property選択用

	private PropertyDialog propDialog;

	private List<EditableListItem> targetObjectTypes;

	/**
	 * proeprty選択用POPUP表示
	 * @param p
	 * @return
	 */
	private boolean awakePropertyListPopup(Point p){
		DefaultListModel model = new DefaultListModel();
		for (EditableListItem tp : getTargetPropertyListItem()){
			model.addElement(tp);
		}

		EditableList properties = new EditableList(model);
		propDialog = new PropertyDialog(this.parent,properties);
		properties.getTextField().addKeyListener(new KeyAdapter() {

			@Override
			public void keyReleased(KeyEvent e) {
				List<String> propList = getPropertyList();
				String word = ((JTextField)e.getSource()).getText();

				if (word.length() > 2){
					List<String> candidates = StringUtil.getPartHitString(propList, word);

					if (candidates.size() < 20){
						awakeCandidateList(candidates, propDialog.getList().getTextField().getLocationOnScreen());
					}
				} else {
					if (popup != null){
						popup.setVisible(false);
						popup = null;
					}
				}

			}
		});
		properties.getModel().addListDataListener(new ListDataListener() {

			@Override
			public void intervalRemoved(ListDataEvent e) {
				propDialog.pack();
			}

			@Override
			public void intervalAdded(ListDataEvent e) {
				propDialog.pack();
			}

			@Override
			public void contentsChanged(ListDataEvent e) {
			}
		});
		properties.getTextField().addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (propDialog.getList().getTextField().getText().isEmpty()){
					//
					propDialog.setOK(true);
					propDialog.setVisible(false);
				}
			}
		});
		propDialog.setLocation(p);
		propDialog.pack();
		propDialog.setModalityType(ModalityType.DOCUMENT_MODAL);
		propDialog.setVisible(true);

		if (popup != null){
			popup.setVisible(false);
		}

		if (!propDialog.isOK()){
			return false;
		}

		model = (DefaultListModel)properties.getModel();

		getTargetPropertyListItem().clear();
		for (int i=0; i<model.getSize(); i++){
			Object item = model.getElementAt(i);
			if (item instanceof EditableListItem){
				getTargetPropertyListItem().add((EditableListItem)item);
			}
		}

		return true;
	}

	private List<String> propertyList;
	private boolean isProcessingProperty = false;

	private List<String> getPropertyList(){
		if (propertyList == null && !isProcessingProperty){
			try {
				ThreadedSparqlAccessor tsa = createSparqlAccessor(false);
				if (tsa != null){
					/*
					List<Map<String, RDFNode>> results = tsa.findPropertyList();
					for (Map<String, RDFNode> result : results){
						for (String nodeStr : result.keySet()){
							if (nodeStr != null && !propertyList.contains(nodeStr)){
								propertyList.add(nodeStr);
							}
						}
					}
					*/
					isProcessingProperty = true;
					tsa.findPropertyList(new SparqlResultListener() {

						@Override
						public void uncaughtException(Thread t, Throwable e) {
							JOptionPane.showMessageDialog(parent, "Execute error:"+e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
							setProcessing(false);
						}

						@Override
						public void resultReceived(SparqlResultSet resultSet) {
							propertyList = new ArrayList<String>();
							Map<String, List<Map<String, RDFNode>>> resultByEndpoint = resultSet.getResult();
							for (List<Map<String, RDFNode>> results : resultByEndpoint.values()){
								for (Map<String, RDFNode> result : results){
									for (String nodeStr : result.keySet()){
										if (nodeStr != null && !propertyList.contains(nodeStr)){
											propertyList.add(nodeStr);
										}
									}
								}
							}
							isProcessingProperty = false;
						}
					});
				}
			} catch (Exception e){
				// エラーは華麗にスルー
			}
		}

		return propertyList;
	}

	private List<EditableListItem> getTargetPropertyListItem(){
		if (targetObjectTypes == null){
			targetObjectTypes = new ArrayList<EditableListItem>();
			targetObjectTypes.add(new EditableListItem(DEFAULT_PROPERTY_TYPE, false));
		}
		return targetObjectTypes;
	}

	JPopupMenu popup;

	private void awakeCandidateList(List<String> candidates, Point p){
		if (popup != null){
			popup.setVisible(false);
		}
		popup = new JPopupMenu();

		for (String candidate : candidates){
			JMenuItem item = new JMenuItem(candidate);
			item.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					propDialog.getList().getTextField().setText(((JMenuItem)e.getSource()).getText());
					popup.setVisible(false);
					popup = null;
				}
			});
			popup.add(item);
		}
		popup.setLocation(p.x + 400, p.y + 20);
		popup.setVisible(true);
	}

	private void initHistory(){
		this.history = new ArrayList<String>();
		this.historyIndex = -1;
		updateButtonStates();
	}

	private void addHistory(String item){

		if (this.historyIndex >= 0 && this.historyIndex < (this.history.size() - 1)){
			for (int i=this.history.size()-1; i > this.historyIndex; i--){
				this.history.remove(i);
			}
		}
		this.history.add(item);
		this.historyIndex++;
		updateButtonStates();
		}

	private String getPrev(){
		if (hasPrev()){
			--historyIndex;
			updateButtonStates();
			return this.history.get(historyIndex);
		}
		return null;
	}

	private String getNext(){
		if (hasNext()){
			++historyIndex;
			updateButtonStates();
			return this.history.get(historyIndex);
		}
		return null;
	}

	private void updateButtonStates(){
		if (!this.processing){
			this.getPrevButton().setEnabled(hasPrev());
			this.getNextButton().setEnabled(hasNext());
		} else {
			this.getPrevButton().setEnabled(false);
			this.getNextButton().setEnabled(false);
		}
	}

	private boolean hasPrev(){
		if (history.size() != 0 && historyIndex > 0){
			return true;
		}
		return false;
	}

	private boolean hasNext(){
		if ((historyIndex + 1) < history.size()){
			return true;
		}
		return false;
	}

	/**
	 * This method initializes limitMainPanel
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getLimitMainPanel() {
		if (limitMainPanel == null) {
			limitMainPanel = new JPanel();
			limitMainPanel.setLayout(new BorderLayout());
			limitMainPanel.add(getLimitComboBox(), BorderLayout.WEST);
			limitMainPanel.add(getLimitPrevButton(), BorderLayout.CENTER);
			limitMainPanel.add(getLimitNextButton(), BorderLayout.EAST);
		}
		return limitMainPanel;
	}

	/**
	 * This method initializes limitPrevButton
	 *
	 * @return javax.swing.JButton
	 */
	private JButton getLimitPrevButton() {
		if (limitPrevButton == null) {
			limitPrevButton = new JButton("←");
			limitPrevButton.setMargin(new Insets(0, 10, 0, 10));
			limitPrevButton.setEnabled(false);
			limitPrevButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					setProcessing(true);
					sa.findSubject(word, fullMatch, limit, (limit * (--page)), type, getTargetPropertyList(), createSparqlResultListener());
				}
			});
		}
		return limitPrevButton;
	}

	/**
	 * This method initializes limitNextButton
	 *
	 * @return javax.swing.JButton
	 */
	private JButton getLimitNextButton() {
		if (limitNextButton == null) {
			limitNextButton = new JButton("→");
			limitNextButton.setMargin(new Insets(0, 10, 0, 10));
			limitNextButton.setEnabled(false);
			limitNextButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					setProcessing(true);

					sa.findSubject(word, fullMatch, limit, (limit * (++page)), type, getTargetPropertyList(), createSparqlResultListener());
				}
			});
		}
		return limitNextButton;
	}

	private void initLimit(){
		limit = this.getLimit();
		page = 0;
	}

	private String[] getTargetPropertyList(){
		List<String> ret = new ArrayList<String>();
		if (targetObjectTypes != null && targetObjectTypes.size() > 0){
			for (EditableListItem item : targetObjectTypes){
				ret.add(item.text);
			}
		} else {
			ret.add(DEFAULT_PROPERTY_TYPE);
		}
		return ret.toArray(new String[0]);
	}

	private void updateLimitButtonStates(){
		if (!this.processing && this.getLimitEnableCheckBox().isSelected()){
			this.getLimitPrevButton().setEnabled(page != 0);
			this.getLimitNextButton().setEnabled(hasLimitNext);
		} else {
			this.getLimitPrevButton().setEnabled(false);
			this.getLimitNextButton().setEnabled(false);
		}
	}

	class PropertyDialog extends JDialog {
		/**
		 *
		 */
		private static final long serialVersionUID = -638100288439239858L;
		private EditableList list;
		private boolean isOK;

		public PropertyDialog(Window parent, EditableList list){
			super(parent);

//			this.setUndecorated(true);
			this.setLayout(new BorderLayout());
			this.list = list;
			this.add(list, BorderLayout.CENTER);
		}

		public EditableList getList(){
			return this.list;
		}

		public void setOK(boolean value){
			this.isOK = value;
		}

		public boolean isOK(){
			return this.isOK;
		}
	}

}
