/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.awk;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;

import net.morilib.awk.encoding.EncodingDetector;
import net.morilib.awk.encoding.EncodingDetectorFactory;
import net.morilib.awk.misc.NullWriter;
import net.morilib.awk.namespace.AwkNamespace;
import net.morilib.awk.value.AwkString;
import net.morilib.sh.Sh;
import net.morilib.sh.file.ShFileSystemFactory;
import net.morilib.unix.glob.WildcardFileFilter;

/**
 * awkiumのGUIです。
 * 
 * @author MORIGUCHI, Yuichiro 2013/03
 */
public class AwkFrame extends JFrame {

	//
	class AwkImmediate extends JDialog {

		//
		JTextArea left = new JTextArea(), right = new JTextArea();

		AwkImmediate() {
			super(AwkFrame.this, "awkium - immediate");

			//
			Container cp = getContentPane();
			JSplitPane s1;
			JButton b1;

			s1 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
			setDefaultCloseOperation(HIDE_ON_CLOSE);
			setSize(500, 400);

			right.setEditable(false);
			s1.setLeftComponent(new JScrollPane(left));
			s1.setRightComponent(new JScrollPane(right));
			s1.setResizeWeight(0.5);

			b1 = new JButton("execute");
			b1.addActionListener(new ActionListener() {

				public void actionPerformed(ActionEvent ev) {
					StringReader s = new StringReader(t2.getText());
					InputStream  r = new ByteArrayInputStream(
							left.getText().getBytes());
					StringWriter w = new StringWriter();
					Writer err = new NullWriter();

					try {
						Awk.getInstance().execute(
								ShFileSystemFactory.getInstance(),
								s,
								"<immediate>",
								r,
								w,
								err,
								Charset.forName("UTF-8"),
								getprop(new Properties()));
						right.setText(w.toString());
					} catch(IOException e) {
						errbox("I/O error:" + e.getMessage());
					} catch(AwkCompilationException e) {
						errbox("syntax error:" + e.getMessage());
					} catch(AwkRuntimeException e) {
						errbox("runtime error:" + e.getMessage());
					}
				}

			});

			cp.setLayout(new BorderLayout());
			cp.add(s1, BorderLayout.CENTER);
			cp.add(b1, BorderLayout.SOUTH);
		}

	}

	private static final String DEF = "default";
	private static final String CH1 = "1 character";
	private static final String REG = "regular expression";

	private JTextField t1 = new JTextField(50);
	private JTextArea t2 = new JTextArea();
	private JComboBox c3 = new JComboBox();
	private JFileChooser f1 = new JFileChooser();
	private JCheckBox c1 = new JCheckBox("apply for subfolders");
	private JComboBox c4 = new JComboBox();
	private JTextField t3 = new JTextField(25);

	private JMenuBar mb = new JMenuBar();
	private JFileChooser f2 = new JFileChooser("open a script");
	private JFileChooser f3 = new JFileChooser("save the script");
	private CharsetComboBox cs1 = new CharsetComboBox();
	private CharsetComboBox cs2 = new CharsetComboBox();

	private File script;
	private AwkImmediate immediate = new AwkImmediate();

	boolean addfs(List<File> fs, File f, boolean dir) {
		if(f.isDirectory()) {
			for(File g : f.listFiles()) {
				if(g.isFile() || (g.isDirectory() && dir))  fs.add(g);
			}
			return false;
		} else {
			return true;
		}
	}

	void warnbox(String s) {
		JOptionPane.showMessageDialog(
				this, s, "awkium", JOptionPane.WARNING_MESSAGE);
	}

	void errbox(String s) {
		JOptionPane.showMessageDialog(
				this, s, "awkium", JOptionPane.ERROR_MESSAGE);
	}

	void infobox(String... ss) {
		StringBuffer b = new StringBuffer();

		for(String s : ss)  b.append(s).append('\n');
		JOptionPane.showMessageDialog(
				this, b.toString(), "awkium",
				JOptionPane.INFORMATION_MESSAGE);
	}

	Properties getprop(Properties p) {
		String s = t3.getText();

		if(c4.getSelectedItem().equals(DEF)) {
			// do nothing
		} else if(c4.getSelectedItem().equals(CH1)) {
			if(s == null || s.equals("")) {
				warnbox("1 character required");
			} else if(s.equals(" ")) {
				p.setProperty("FS", "[ ]");
			} else if(s.length() > 1) {
				warnbox("1 character required");
			} else {
				p.setProperty("FS", s.substring(0, 1));
			}
		} else if(c4.getSelectedItem().equals(REG)) {
			p.setProperty("FS", s);
		}
		return p;
	}

	AwkNamespace getprop(AwkNamespace p) {
		String s = t3.getText();

		if(c4.getSelectedItem().equals(DEF)) {
			// do nothing
		} else if(c4.getSelectedItem().equals(CH1)) {
			if(s == null || s.equals("")) {
				warnbox("1 character required");
			} else if(s.equals(" ")) {
				p.assign("FS", AwkString.valueOf("[ ]"));
			} else if(s.length() > 1) {
				warnbox("1 character required");
			} else {
				p.assign("FS", AwkString.valueOf(s.substring(0, 1)));
			}
		} else if(c4.getSelectedItem().equals(REG)) {
			p.assign("FS", AwkString.valueOf(s));
		}
		return p;
	}

	void exec1() {
		List<File> fs = new LinkedList<File>();
		Object sf = c3.getSelectedItem();
		String s = t1.getText();
		WildcardFileFilter ff;
		EncodingDetector en;
		Charset ch, ci;
		File f;

		en = EncodingDetectorFactory.getInstance();
		if(s == null || s.equals("")) {
			warnbox("path required");
			return;
		} else if(sf == null || sf.equals("")) {
			warnbox("file filter required");
			return;
		}

		ff = new WildcardFileFilter(sf.toString());
		try {
			fs.add(new File(t1.getText()));
			while(!fs.isEmpty()) {
				f = fs.remove(0);
				if(addfs(fs, f, c1.isSelected()) && ff.accept(f)) {
					if((ch = cs1.getSelectedCharset()) == null) {
						ch = en.detect(f);
					}

					if((ci = cs2.getSelectedCharset()) == null) {
						ci = ch;
					}

					Awk.getInstance().overwrite(
							ShFileSystemFactory.getInstance(),
							t2.getText(),
							ShFileSystemFactory.getInstance().getFile(
									f.toString()),
							new NullWriter(),
							ch,
							ci,
							getprop(new Properties()));
				}
			}
			infobox("execute completed");
		} catch(IOException e) {
			errbox("I/O error:" + e.getMessage());
		} catch(AwkCompilationException e) {
			errbox("syntax error:" + e.getMessage());
		} catch(AwkRuntimeException e) {
			errbox("runtime error:" + e.getMessage());
		}
	}

	private void closeq(Closeable c) {
		if(c != null) {
			try {
				c.close();
			} catch(IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	private void load1(File f) {
		StringWriter wr = new StringWriter();
		Reader rd = null;
		char[] a = new char[2048];
		int l;

		try {
			rd = new InputStreamReader(new FileInputStream(f));
			while((l = rd.read(a)) >= 0)  wr.write(a, 0, l);
			wr.flush();
			t2.setText(wr.toString());
		} catch(IOException e) {
			errbox("I/O error");
		} finally {
			closeq(rd);
		}
		
	}

	private void save1(File f) {
		Writer wr = null;

		try {
			wr = new OutputStreamWriter(new FileOutputStream(f));
			wr.write(t2.getText());
			wr.flush();
		} catch(IOException e) {
			errbox("I/O error");
		} finally {
			closeq(wr);
		}
	}

	AwkFrame() {
		super("awkium");

		//
		Container cp;
		GridBagConstraints ct = new GridBagConstraints();
		JPanel p1 = new JPanel();
		JMenu m1;
		JMenuItem i1;
		JButton b1, b2;
		JPanel p2 = new JPanel();
		JPanel p3 = new JPanel();
		JPanel p4 = new JPanel();

		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setMinimumSize(new Dimension(600, 300));
		setSize(600, 450);
		cp = getContentPane();
		cp.setLayout(new BorderLayout());
		p1.setLayout(new GridBagLayout());
		f1.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

		ct.gridwidth = ct.gridheight = 1;
		ct.weightx = ct.weighty = 1;
		ct.fill = GridBagConstraints.BOTH;
		p1.add(new JLabel("Path:"), ct);

		ct.weightx = 8;
		p1.add(t1, ct);

		ct.weightx = 1;
		ct.gridwidth = GridBagConstraints.REMAINDER;
		b1 = new JButton("File");
		b1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				File f;

				f1.showDialog(AwkFrame.this, "open");
				f = f1.getSelectedFile();
				if(f != null)  t1.setText(f.toString());
			}

		});
		p1.add(b1, ct);

		ct.gridwidth = 1;
		p1.add(new JLabel("Filter:"), ct);

		ct.gridwidth = GridBagConstraints.REMAINDER;
		c3.addItem("*.txt");
		c3.setEditable(true);
		p1.add(c3, ct);

		ct.gridwidth = 2;
		p2.add(c1);
		p1.add(p2, ct);

		b1 = new JButton("immediate");
		ct.gridwidth = GridBagConstraints.REMAINDER;
		b1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				immediate.setVisible(true);
			}

		});
		p1.add(b1, ct);

		ct.gridwidth = GridBagConstraints.REMAINDER;
		c4.addItem(DEF);
		c4.addItem(CH1);
		c4.addItem(REG);
		c4.addItemListener(new ItemListener() {

			public void itemStateChanged(ItemEvent e) {
				t3.setEnabled(!e.getItem().equals("default"));
			}

		});
		p4.add(new JLabel("Field Separator")); p4.add(c4);
		t3.setEnabled(false);
		p4.add(t3);
		p1.add(p4, ct);

		ct.gridwidth = GridBagConstraints.REMAINDER;
		p3.setLayout(new GridLayout(2, 2));
		p3.add(new JLabel("Input Encoding"));
		p3.add(new JLabel("Output Encoding"));
		p3.add(cs1);  p3.add(cs2);
		p1.add(p3, ct);

		// border layout
		cp.add(p1, BorderLayout.NORTH);
		cp.add(new JScrollPane(t2), BorderLayout.CENTER);

		b2 = new JButton("execute");
		b2.addActionListener(new ActionListener() {

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

		});
		cp.add(b2, BorderLayout.SOUTH);

		// menu
		m1 = new JMenu("File");
		i1 = new JMenuItem("Open File...");
		i1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				File f;

				f2.showOpenDialog(AwkFrame.this);
				if((f = f2.getSelectedFile()) != null) {
					load1(script = f);
				}
			}

		});
		i1.setMnemonic('o');
		i1.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_O, InputEvent.CTRL_MASK));
		m1.add(i1);

		i1 = new JMenuItem("Save");
		i1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				save1(script);
			}

		});
		i1.setMnemonic('s');
		i1.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_S, InputEvent.CTRL_MASK));
		m1.add(i1);

		i1 = new JMenuItem("Save As...");
		i1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				File f;

				f3.showOpenDialog(AwkFrame.this);
				if((f = f3.getSelectedFile()) != null) {
					save1(script = f);
				}
			}

		});
		i1.setMnemonic('a');
		m1.add(i1);
		mb.add(m1);

		m1 = new JMenu("Help");
		i1 = new JMenuItem("Show Immediate Window");
		i1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				immediate.setVisible(true);
			}

		});
		m1.add(i1);

		i1 = new JMenuItem("About awkium");
		i1.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				infobox("awkium Version 0.0.0",
						"Copyright 2013 Yuichiro Moriguchi",
						"This software under Apache 2.0 License");
			}

		});
		m1.add(i1);
		mb.add(m1);
		setJMenuBar(mb);
	}

	static boolean isHeadless() {
		return "true".equals(
				System.getProperty("java.awt.headless"));
	}

	static void repl() {
		System.err.println("awkium Ver. " + Awk.VERSION);
		Awk.getInstance().repl(ShFileSystemFactory.getInstance());
		System.exit(0);
	}

	/**
	 * mainメソッドです。
	 * 
	 * @param args コマンドライン引数
	 */
	public static void main(String[] args) {
		String[] a2 = new String[args.length + 1];
		String[] a3;
		int c = 0;

		try {
			if(args.length == 1 && args[0].equals("--repl")) {
				repl();
			} else if(args.length == 1 && args[0].equals("--shell")) {
				a3 = new String[args.length - 1];
				System.arraycopy(args, 1, a3, 0, a3.length);
				System.exit(Sh.invoke(a3));
			} else if(args.length > 0) {
				System.arraycopy(args, 0, a2, 1, args.length);
				a2[0] = "awkium";
				c = Awk.getInstance().invoke(
						ShFileSystemFactory.getInstance(),
						System.in,
						new OutputStreamWriter(System.out),
						new OutputStreamWriter(System.err),
						a2);
				System.exit(c);
			} else if(isHeadless()) {
				repl();
			} else {
				try {
					new AwkFrame().setVisible(true);
				} catch(HeadlessException e) {
					repl();
				}
			}
		} catch(IOException e) {
			e.printStackTrace(System.err);
			System.exit(4);
		}
	}

}
