package com.ftinc.si.assist.run;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.AbstractAction;
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.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.Caret;

import com.ftinc.si.assist.test.ClassOutLine;
import com.ftinc.si.assist.test.CodeProcessor;
import com.ftinc.si.assist.test.FakeMethodRecord;
import com.ftinc.si.assist.test.Fson;
import com.ftinc.si.assist.test.Tool;
import com.ftinc.si.assist.test.gui.CodingAssistMenu;

public class FakeEditorOnRun extends JDialog {
	private String m_source;
	private String m_checked;
	private boolean changed;
	private FakeMethodRecord m_fake;
	private JTextArea textSource;
	
	private HashMap<String, String> m_methods;
	private int prevCombo = 0;
	
	private Object[] m_objs;
	private String m_cname;
	private HashMap<String, Class<?>> m_cmap = null;
	private String m_rtype;
	private JTextField superName;
	
	private static ArrayList<String> not_editList = null;
	public static void reset() {
		not_editList = null;
	}
	
	public boolean beSkipped = false;
	public boolean isExiting = false;
	
	//t_orはFakeの主語。
	public FakeEditorOnRun(FakeMethodRecord frec, Class<?> vtype, Object subj, Object[] objs, Exception exp, String ant) {
		setAlwaysOnTop(true);
		setModal(true);
		setResizable(false);
		
		if (not_editList == null) {
			not_editList = new ArrayList<String>();
		}
		
		changed = false;
		
		m_objs = objs;
		m_cname = frec.className;

		m_rtype = vtype.getName();

		m_cmap = new HashMap<String, Class<?>>();
		m_cmap.put("$_", vtype); //$NON-NLS-1$
		if (objs != null) {
			for (int i = 0; i < objs.length; i++) {
				m_cmap.put("$" + Integer.toString(i), objs[i].getClass()); //$NON-NLS-1$
			}
		}
		
		getContentPane().setLayout(null);
		Dimension t_d = new Dimension(860, 480);
		setMinimumSize(t_d);
		
		JLabel lblNewLabel = new JLabel(Messages.getString("FakeEditorOnRun.3")); //$NON-NLS-1$
		lblNewLabel.setBounds(382, 10, 162, 13);
		getContentPane().add(lblNewLabel);
		
		JLabel lblNewLabel_1 = new JLabel(Messages.getString("FakeEditorOnRun.4")); //$NON-NLS-1$
		lblNewLabel_1.setBounds(12, 57, 132, 13);
		getContentPane().add(lblNewLabel_1);
		
		m_fake = frec;
		m_source = frec.source;
		
		if (m_source == null) {
			m_source = ""; //$NON-NLS-1$
		}
		if (m_source.startsWith("{")) { //$NON-NLS-1$
			//もしあれば{}を取る。
			m_source = m_source.substring(1, m_source.length() - 1);
		}

		final JCheckBox chckbxEditAgain = new JCheckBox(Messages.getString("FakeEditorOnRun.chckbxEditAgain.text")); //$NON-NLS-1$
		chckbxEditAgain.setBounds(581, 236, 103, 21);
		getContentPane().add(chckbxEditAgain);

		JLabel lblFieldAndMethod = new JLabel(Messages.getString("FakeEditorOnRun.1")); //$NON-NLS-1$
		lblFieldAndMethod.setBounds(12, 240, 90, 13);
		getContentPane().add(lblFieldAndMethod);
		
		JButton btnUpdate = new JButton(Messages.getString("FakeEditorOnRun.0")); //$NON-NLS-1$
		btnUpdate.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//targetObjの構文の返却型チェック。
				m_source = textSource.getText();

				if (isSyntaxOK()) {
					changed = true;
					if (m_source.length() > 0) {
						m_fake.source = m_source;
						m_fake.changed();
					}
					if (chckbxEditAgain.isSelected()) {
						not_editList.add(m_fake.className + "#" + m_fake.methodName); //$NON-NLS-1$
					}
					setVisible(false);
				} else {
					if (m_source.length() == 0) {
						Tool.alertMSG(null, Messages.getString("FakeEditorOnRun.9")); //$NON-NLS-1$
					} else {
						int option = JOptionPane.showConfirmDialog(null, m_checked + Messages.getString("FakeEditorOnRun.10"), Messages.getString("FakeEditorOnRun.11"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$
						if (option == JOptionPane.YES_OPTION){
							m_fake.source = m_source;
							m_fake.changed();
							changed = true;
						}
						if (chckbxEditAgain.isSelected()) {
							not_editList.add(m_fake.className + "#" + m_fake.methodName); //$NON-NLS-1$
						}
						setVisible(false);
					}
				}
			}
		});
		btnUpdate.setBounds(262, 426, 91, 21);
		getContentPane().add(btnUpdate);
		
		JButton btnCancel = new JButton(Messages.getString("FakeEditorOnRun.12")); //$NON-NLS-1$
		btnCancel.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (chckbxEditAgain.isSelected()) {
					not_editList.add(m_fake.className + "#" + m_fake.methodName); //$NON-NLS-1$
					beSkipped = true;
				}
				
				setVisible(false);
			}
		});
		btnCancel.setBounds(406, 426, 91, 21);
		getContentPane().add(btnCancel);	
		
		JLabel lblSuperclass = new JLabel(Messages.getString("FakeEditorOnRun.13")); //$NON-NLS-1$
		lblSuperclass.setBounds(12, 10, 91, 13);
		getContentPane().add(lblSuperclass);
		
		superName = new JTextField(frec.className);
		superName.setBounds(12, 28, 351, 19);
		getContentPane().add(superName);
		superName.setColumns(10);
				
		JScrollPane scrollPane_1 = new JScrollPane();
		scrollPane_1.setBounds(12, 263, 831, 153);
		getContentPane().add(scrollPane_1);
		
		//このメソッドのソース
		textSource = new JTextArea();
		scrollPane_1.setViewportView(textSource);
		textSource.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//右クリックならポップアップを開く。
				if(SwingUtilities.isRightMouseButton(e)){
					showPopup(e);
				}
			}
		});
		textSource.addKeyListener(new KeyAdapter() {
			@Override
			public void keyTyped(KeyEvent e) {
				if (e.getKeyChar() == '.') {
					popupCandidate(e);
				}
			}
		});
		textSource.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e) {
				//マウスホイールの操作により、フォントサイズを変える。
				Font t_font = Tool.resizeFont(e, textSource.getFont());
				if (t_font != null) {
					textSource.setFont(t_font);
				}
			}
		});
		
				textSource.setText(m_source);
		
		//もしUpdate済なら、ant（更新日付）をタイトルに表示する。
		//デフォルトのままなら、未定義と表示。さらにテキストエリアを桃色にする。
		String ant_str = ant + "(Updated)"; //$NON-NLS-1$
		if (ant == null || ant.length() == 0) {
			ant_str = Messages.getString("FakeEditorOnRun.14"); //$NON-NLS-1$
			textSource.setBackground(Color.PINK);
		}
		setTitle(Messages.getString("FakeEditorOnRun.2") + ">>>" + ant_str); //$NON-NLS-1$ //$NON-NLS-2$
		chckbxEditAgain.setSelected(true);

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(382, 28, 461, 202);
		getContentPane().add(scrollPane);
		
		JTextArea stackText = new JTextArea();
		scrollPane.setViewportView(stackText);
		stackText.setEditable(false);
		stackText.setFont(new Font("Monospaced", Font.PLAIN, 13)); //$NON-NLS-1$
		stackText.setText(Tool.getStackMessage(exp, 1, 10));
		
		JScrollPane scrollPane_2 = new JScrollPane();
		scrollPane_2.setBounds(12, 80, 351, 150);
		getContentPane().add(scrollPane_2);
		
		JTextArea textState = new JTextArea();
		scrollPane_2.setViewportView(textState);
		
		//このクラスのFakeメソッドのリスト。現メソッド以外は参照のみ。
		final JComboBox<String> methodCombo = new JComboBox<String>();
		methodCombo.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				int i = methodCombo.getSelectedIndex();
				if (i== 0) {//編集対象のメソッドである。
					textSource.setText(m_source);//編集中のソースを表示する。
					textSource.setEnabled(true);
					prevCombo = i;

				} else if (i > 0) {//編集対象ではないメソッドである。
					if (prevCombo == 0) {
						//今、呼ばれているメソッドのみ編集可能なので、常にm_sourceに保持する。
						m_source = textSource.getText();
					}
					textSource.setText(m_methods.get(methodCombo.getSelectedItem()));
					textSource.setEnabled(false);
					prevCombo = i;
				}
			}
		});
		methodCombo.setBounds(75, 237, 384, 19);
		getContentPane().add(methodCombo);
		
		if (objs != null) {     //Statusの表示
			String t_area1 = ""; //$NON-NLS-1$
			t_area1 += "$_ :" + vtype.toString() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
			if (subj != null) {
				String t_str = ""; //$NON-NLS-1$
				try {
					t_str = subj.toString();//toStringをoverrideすると、往々にして不完全な状態でここに至り、エラーになる。
				} catch (Error e) {
					t_str = Messages.getString("FakeEditorOnRun.18"); //$NON-NLS-1$
				} catch (Exception e) {
					t_str = Messages.getString("FakeEditorOnRun.19"); //$NON-NLS-1$
				}
				
				t_area1 += "$0 :" + t_str + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
			} else {
				//nullの場合
				t_area1 += "$0 : null\n"; //$NON-NLS-1$ //$NON-NLS-2$
			}
			
			for (int i = 0; i < objs.length; i++) {
				t_area1 += "$" + Integer.toString(i + 1) + " :" + String.valueOf(objs[i]) + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
			if (subj != null) {
				ArrayList<Object> t_ans = Fson.valueByJsonPath(subj, "$"); //$NON-NLS-1$
				if (t_ans != null && t_ans.size() > 0) {
					@SuppressWarnings({"unchecked" })
					HashMap<String, Object> v_map = (HashMap<String, Object>)t_ans.get(0);
					HashMap<String, Object> v2_map = new HashMap<String, Object>();
					for (Entry<String, Object> entry : v_map.entrySet()) {
						if (entry.getKey().indexOf("_class") < 0) { //$NON-NLS-1$ //$NON-NLS-2$
							v2_map.put(entry.getKey(), entry.getValue());
						}
					}
					if (v2_map.size() > 0) {
						t_area1 += "fields=\n" + Tool.getJSONfromObject(v2_map, "^m_").replace(",\"", ",\n\"");
					}
				}
			}
			textState.setText(t_area1);
		}
		initMethodCombo(methodCombo);
		
		JButton btnExit = new JButton(Messages.getString("FakeEditorOnRun.btnExit.text")); //$NON-NLS-1$
		btnExit.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				isExiting = true;
				setVisible(false);
			}
		});
		btnExit.setBounds(700, 426, 143, 21);
		getContentPane().add(btnExit);
		
	}
	
	//MethodComboの初期化
	private void initMethodCombo(JComboBox<String> combo) {
		m_methods = new HashMap<String, String>(); //キーはmethosName(argTypes), 値はSource
		String t_types = Tool.getJSONfromObject(m_fake.argTypes);
		
		m_methods.put(m_fake.methodName + "(" + t_types + ")", m_fake.source);  //$NON-NLS-1$//$NON-NLS-2$
		combo.addItem(m_fake.methodName + "(" + t_types + ")"); //$NON-NLS-1$ //$NON-NLS-2$
		
		ArrayList<FakeMethodRecord> t_fakes = Tool._db().getFakeMethods(m_fake.className, Tool.version);
		for (int i = 0; i < t_fakes.size(); i++) {
			String t_key = t_fakes.get(i).methodName + "(" + Tool.getJSONfromObject(t_fakes.get(i).argTypes) + ")"; //$NON-NLS-1$ //$NON-NLS-2$
			if (!m_methods.containsKey(t_key)) {
				String t_src = t_fakes.get(i).source;
				if (t_src.startsWith("{")) { //$NON-NLS-1$
					//編集に余計な括弧を取る。
					t_src = t_src.substring(1, t_src.length() - 1);
				}
				
				m_methods.put(t_key, t_src);
				combo.addItem(t_key);
			}
		}
	}
	
	
	// Dialogがソースを取得するために使用。
	public String getSource() {
		return m_source;
	}
	
	//このDialogの呼び出し元で使用。変更があったかどうかを判断する。Updateが押されたら変更ありとする。
	public boolean changed() {
		return changed;
	}
	

	private boolean isSyntaxOK() {
		String src = CodeProcessor.preProcessMain(null, null, m_source);
		
		//本当は、別の場所の方がよいかも知れないが、Updateの際には必ずここを通過するので、ここで実装する。
		//仮想メンバ変数を抽出し、Fakeに反映
		if (src != null && CodeProcessor._memVars != null) {
			for(Entry<String, String> entry : CodeProcessor._memVars.entrySet()) { 
				if (entry.getValue() != null) {
					m_fake.addField(entry.getValue(), entry.getKey());
				}
			}
		}
		
		//仮想メンバ変数が複数のクラスで登録されている場合
		ClassOutLine t_clo = Tool.getClassOutLine(m_fake.className);
		if (t_clo.getDeclaredFields(m_fake) == null) {
			m_checked = Messages.getString("FakeEditorOnRun.30"); //$NON-NLS-1$
			return false;
		}
		
		try {
			String t_str = CodeProcessor.checkSyntaxOfCode(m_source);
			if (t_str == null) {
				//nullなら注意事項はチェック済み。
				//次に返却値のチェックをする。指摘箇所がなければOK。
				return ((m_checked = CodeProcessor.checkReturnType(m_rtype, m_source, m_cmap)) == null);
			}
		} catch (SecurityException e) {
			Tool.logIfDebug(e, "@FakeEditorOnRun#IsSyntaxOK >>" + m_checked); //$NON-NLS-1$
		}
		return false;
	}
	
	
	//"."を受けて、
	//①現在の入力テキストを解析し、文字列とクラスのマップを作る。
	//②直前の文字列を解析し、クラスを特定する。必要な候補を表示する。$*.xxxまではフィールド、そこから先はpublicメソッド。
	//③選択された文字列をcaret位置に挿入する。
	private void popupCandidate(KeyEvent e) {
		String src = CodeProcessor.preProcess4Import(textSource.getText());
		CodeProcessor.preProcess4MemInfos(src); //$NON-NLS-1$
		HashMap<String, String> mems = CodeProcessor._memVars;
		
		//クラスマップの作成
		HashMap<String, Class<?>> _cmap = new HashMap<String, Class<?>>();
		for(Entry<String, String> entry : mems.entrySet()) {
			String t_var = entry.getKey();
			String t_cname = entry.getValue();
			
			Class<?> t_c;
			try {
				t_c = Tool.forName(t_cname);
				_cmap.put(t_var, t_c);
			} catch (ClassNotFoundException e1) {
				Tool.logIfDebug(e1, Messages.getString("FakeEditorOnRun.32")); //$NON-NLS-1$
			}
		}
		Class<?> t_c = null;
		try {
			t_c = Tool.forName(m_cname);
			_cmap.put("$0", t_c); //$NON-NLS-1$
		} catch (ClassNotFoundException e1) {
			Tool.logIfDebug(e1, Messages.getString("FakeEditorOnRun.34")); //$NON-NLS-1$
		}

		//$nのクラスマップを作る。
		for (int j = 0; j < m_objs.length; j++) {
			t_c = m_objs[j].getClass();
			_cmap.put("$" + Integer.toString(j + 1), t_c); //$NON-NLS-1$
		}
		
		Caret tc = textSource.getCaret();
		Point op = textSource.getLocationOnScreen();
		Point pt = tc.getMagicCaretPosition();
		Point pos = new Point(op.x + pt.x, op.y + pt.y);
		
		//popup表示フォーカス委譲（タイマーで）
		final CodingAssistMenu _popup = new CodingAssistMenu(this, m_fake, textSource.getText(), textSource.getCaretPosition(), _cmap, pos, null);
		
		//候補がある場合のみポップアップする。
		if (_popup.countCandidate() > 0) {
			//このスレッドが終わるまではフォーカスされないのでタイマーでやる。（表示後でないとフォーカスされないため）
			Thread t_thread = new Thread() {
				@Override
				public void run() {
					try {
						sleep(1000);
						SwingUtilities.getWindowAncestor(_popup).toFront();
						_popup.requestFocusInWindow();
					} catch (InterruptedException e) {
						Tool.logIfDebug(e, Messages.getString("FakeEditorOnRun.36")); //$NON-NLS-1$
					}
				}
			};
			t_thread.start();
			
			_popup.setVisible(true);
			
			EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() {
					insertAtCaret(_popup.m_answer, _popup.m_catches, _popup.m_catches);
				}
			});
		}
	}
	

	//caretの位置にテキストを差し込む。
	//str: 差し込む文字列　　　tgt：差し込む直前の一連の命令　　　ctch：必要ならcatch文。不要ならnull。
	public void insertAtCaret(String str, String tgt, String ctch) {
		if (str != null) {
			int pos = textSource.getCaretPosition();
			String str_h = textSource.getText();
			String str_tail = str_h.substring(pos);

			str_h = str_h.substring(0, pos);
			
			if (ctch != null) {
				//例外がある場合。
				str_h = str_h.replaceFirst(Pattern.quote(tgt + ".")+ "$", Matcher.quoteReplacement("try{\n" + tgt + ".")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
				str_h += ctch + str_tail; 
			} else {
				str_h += str + str_tail;
			}
			textSource.setText(str_h);
			textSource.setCaretPosition(pos + str.length());
		}
	}

	public String getSuperClassName() {
		return superName.getText();
	}
	
	//実行時に同一クラス・メソッドの組を複数回実行しないようにする仕掛け。
	public void setVisible(boolean b) {
		if (b && not_editList.contains(m_fake.className + "#" + m_fake.methodName)) { //$NON-NLS-1$
			super.setVisible(false);
			beSkipped = true;
			return;
		}
		
		super.setVisible(b);
	}
	
	//入力支援ポップアップを開く。
	private void showPopup(MouseEvent e) {
		JPopupMenu t_pop = new JPopupMenu();
		t_pop.add(new JMenuItem(new FakeEditorAction("insert 'MOCK(...)'", this))); //$NON-NLS-1$
		t_pop.add(new JMenuItem(new FakeEditorAction("insert 'VGET(...)'", this))); //$NON-NLS-1$
		t_pop.show(e.getComponent(), e.getX(), e.getY());
	}
	
	class FakeEditorAction extends AbstractAction {
		FakeEditorOnRun m_editor;
		String m_cmd;

		FakeEditorAction(String menu_str, FakeEditorOnRun ed) {
			m_editor = ed;
			m_cmd = menu_str;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			if (m_cmd.matches("^.*insert.*OBJECT.*$")) { //$NON-NLS-1$
				m_editor.insertAtCaret("MOCK(\"" + m_rtype + "\")", null, null);
			} else if (m_cmd.matches("^.*insert.*VGET.*$")) { //$NON-NLS-1$
				String cname = m_cname.replaceFirst("^.*\\.([^\\.]+)$", "$1");
				m_editor.insertAtCaret("VGET(\"" + cname + "#"+ cname + "\")", null, null);
			}
		}
	}
}
