package asandatabasebrowser;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Struct;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;

import net.sourceforge.swingx.SxMessageBox;
import net.sourceforge.swingx.SxOptionPane;
import net.sourceforge.swingx.SxSprashFrame;
import net.sourceforge.swingx.SxTextUtilities;
import asandatabasebrowser.model.DbData;
import asandatabasebrowser.model.DbException;
import asandatabasebrowser.model.DbLargeObject;
import asandatabasebrowser.model.FavoriteSQL;
import asandatabasebrowser.model.FavoriteTable;
import asandatabasebrowser.model.ITableInfo;
import asandatabasebrowser.model.SchemaInfo;
import asandatabasebrowser.model.TableInfo;
import asandatabasebrowser.model.VvDatabase;
import asandatabasebrowser.model.VvDocument;
import asandatabasebrowser.view.ConnectDialog;
import asandatabasebrowser.view.DbExecuteFrame;
import asandatabasebrowser.view.DbSqlFrame;
import asandatabasebrowser.view.DbTableFrame2;
import asandatabasebrowser.view.DbTextFrame;
import asandatabasebrowser.view.DbTreeFrame;
import asandatabasebrowser.view.DbTreeView;
import asandatabasebrowser.view.SqlLogView;



/** アプリケーション. */
public class AsanDatabaseBrowser {
	public static final String TITLE = "AsanDatabaseBrowser";
	public static final String ASAN_DATABASE_BROWSER_FILE = "AsanDatabaseBrowser.xml";
	/** @deprecated 上位互換のため残しています。 */
	public static final String VERTICAL_VIEWER_FILE = "VerticalViewer.xml";
    
	 
	
	/** 自分自身 */
	public static AsanDatabaseBrowser theApp;
	/** 引数. */
	public static String[] args;
	/** ツリーフレーム */
	DbTreeFrame treeFrame = null;
	/** 複数のフレーム. */
	private ArrayList frameList = new ArrayList();	// <JFrame>
	public VvDocument doc = new VvDocument();
	public SqlLogView logView = null;
	private AsanDatabaseBrowser() {}

	public boolean initInstance(String[] args) {
		try {
			// プロパティを読み込む。
			File file = new File(ASAN_DATABASE_BROWSER_FILE);
			File file2 = new File(VERTICAL_VIEWER_FILE);
			if (file.exists()) {
				doc.loadXml(file);
			} else if (file2.exists()) {
				doc.loadVerticalViewerXml(file2);
			} else {
				doc.loadProperties();
			}
			return true;
		} catch (DbException e) {
			showMessage(treeFrame, 
					"設定ファイルの読み込みに失敗しました。", 
					e, SxOptionPane.OK_ONLY_OPTION, SxOptionPane.ERROR_MESSAGE);
			return false;
		}
	}
	public void exitInstance() {
		// つながっているDBを切断する。
		for (int i=0; i<doc.databaseList.size(); i++) {
			VvDatabase db = (VvDatabase) doc.databaseList.get(i);
			if (db.getConnection() != null) {
				try {
					db.disconnect();
				} catch (DbException e) {
					e.printStackTrace();
				}
			}
		}
		// 設定を保存する
		try {
			// 設定を保存する。編集されたとき、あるいは初めての VerticalViewer からの移行時。
			File file = new File(ASAN_DATABASE_BROWSER_FILE);
			if (doc.isModified() || ! file.exists()) {
				// 前の設定を残すため、リネームして残す。
				if (file.exists()) {
					// "AsanDatabaseBrowser20080121.xml"
					SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd_HHmm");
					Date now = Calendar.getInstance().getTime();
					String bakname = TITLE+f.format(now)+ ".xml";
					file.renameTo(new File(bakname));
				}
				doc.storeXml(file);
			}
		} catch (DbException e) {
			e.printStackTrace();
		}
		System.exit(0);
	}
	/** 指定されたフレームを閉じる. */
	public void close(JFrame frame) {
		frameList.remove(frame);
	}
	public void openFrame(JFrame newFrame) {
		newFrame.setLocation(300, frameList.size()*20);
		frameList.add(newFrame);
		newFrame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				//System.out.println(e);
				close((JFrame) e.getWindow());
			}
		});
		newFrame.setVisible(true);
	}
	/** SQLフレームを開く. */
	public void openSqlFrame(VvDatabase db, String sql) throws DbException {
		//System.out.println("openSqlFrame("+sql+")");
		DbSqlFrame newFrame = new DbSqlFrame(db);
		newFrame.addSqlTab(sql);
		DbData data = db.getRecordList(sql);
		newFrame.updateData(data);
		openFrame(newFrame);
	}
	/** SQLフレームを開く. */
	public void openSqlFrame(VvDatabase db, FavoriteSQL sql) throws DbException {
		//System.out.println("openSqlFrame("+sql+")");
		DbSqlFrame newFrame = new DbSqlFrame(db);
		for (int i=0; i<sql.getCount(); i++) {
			newFrame.addSqlTab(sql.getSql(i));
		}
		if (sql.getCount() > 0) {
			DbData data = db.getRecordList(sql.getSql(0));
			newFrame.updateData(data);
			newFrame.textarea.setSelectedIndex(0);
		}
		openFrame(newFrame);
	}
	/** テーブルフレームを開く. */
	public void openTableFrame(TableInfo table) throws DbException {
		//System.out.println("openTableFrame("+table+")");
//		DbTableFrame newFrame = new DbTableFrame(table);
//		openFrame(newFrame);
		DbTableFrame2 newFrame2 = new DbTableFrame2(table);
		openFrame(newFrame2);
	}
	public void openTableFrame(FavoriteTable fav) throws DbException {
        // 接続先を取得する。
        VvDatabase db = null;
        for (int i=0; i<doc.databaseList.size(); i++) {
        	VvDatabase d = (VvDatabase) doc.databaseList.get(i);
        	if (d.name.equals(fav.dbName)) {
        		db = d;
        		break;
        	}
        }
        if (db == null) throw new DbException("接続先が存在しません:"+db.name);	// 接続先が存在しない！？
        // コネクションを取得する。
        Connection conn = db.getConnection();
        // 接続されていなかったら接続する。
        if (conn == null) {
        	try {
        		// DBに接続する。必要ならばダイアログを表示してユーザ＆パスワードを入力する。
				boolean rc = connect(db);
				if (!rc) return;
				conn = db.getConnection();
				assert conn != null;
				// Databaseツリー側の枝を生成させる。
		    	DbTreeView dbtree = treeFrame.getTree();
	        	DefaultMutableTreeNode dbnode = dbtree.getNodeByUserObject(db);
				dbtree.接続したので枝を作る(dbnode, db);
			} catch (DbException e1) {
				throw new DbException("データベースの接続に失敗しました", e1);
			}
        }
        try {
            // 該当するカタログ/スキーマを取得する。
            SchemaInfo schema = null;
            ArrayList list = db.getSchemaInfoList();
            for (int i=0; i<list.size(); i++) {
            	SchemaInfo s = (SchemaInfo) list.get(i);
            	if (SxTextUtilities.equalsString(fav.tableCatalog, s.getCatalogName()) &&
            			SxTextUtilities.equalsString(fav.tableSchema, s.getSchemaName()) ) {
            		schema = s;
            		break;
            	}
            }
            if (schema == null) {
            	throw new DbException("そのようなカタログ/スキーマは存在しません。catalog="+
                		fav.tableCatalog+" schema="+fav.tableSchema);
            }
            // 該当するテーブルを取得する。
            TableInfo table = schema.getTableTypeInfo(fav.tableType).getTableInfo(fav.tableName);
            openTableFrame(table);
        }catch (DbException ex) {
        	throw new DbException("テーブルを開くことができません", ex);
        }
	}
	public void openExecuteFrame(VvDatabase db) {
		//System.out.println("openExecuteFrame("+table+")");
		DbExecuteFrame newFrame = new DbExecuteFrame(db);
		openFrame(newFrame);
	}
	
	public void openTextFrame(String title, String text, boolean editable) throws DbException {
		DbTextFrame newFrame = new DbTextFrame(title, editable);
		newFrame.setText(text);
		openFrame(newFrame);
	}
	public void openBinaryFrame(String title, byte[] data) throws DbException {
		String text = SxTextUtilities.dump(data);
		DbTextFrame newFrame = new DbTextFrame(title, false);
		newFrame.setText(text);
		newFrame.setContent(data);
		openFrame(newFrame);
	}

	public void openLargeObject(Object data, ITableInfo tableInfo, Object[] rec, int row) 
					throws DbException {
		if (data instanceof DbLargeObject) {
			DbLargeObject lob = (DbLargeObject) data;
//			String typename = lob.type;
			String title = lob.title;
			data = lob.getValue();
			if (data == null) {
				if (tableInfo instanceof TableInfo) {
					AsanDatabaseBrowser.theApp.showMessage(treeFrame, 
							"メモリ不足のため、データは解放されました。\n再検索しますか？", 
							null, SxOptionPane.OK_CANCEL_OPTION, SxOptionPane.QUESTION_MESSAGE);
					TableInfo table = (TableInfo) tableInfo;
					Object obj = table.db.getColumnValue(table, rec, row);
					if (obj == null) {
						AsanDatabaseBrowser.theApp.showMessage(treeFrame, 
								"NULL です。", 
								null, SxOptionPane.OK_ONLY_OPTION, SxOptionPane.INFORMATION_MESSAGE);
						return;
					}
					lob = (DbLargeObject) obj;
					data = lob.getValue();

				} else {
					AsanDatabaseBrowser.theApp.showMessage(treeFrame, 
							"メモリ不足のため、データは解放されました。", 
							null, SxOptionPane.OK_ONLY_OPTION, SxOptionPane.ERROR_MESSAGE);
					return;
				}
			}

			try {
				// CLOB型の場合、ビューアを開く。
				if (data instanceof String) {
					AsanDatabaseBrowser.theApp.openTextFrame(title, (String) data, false);
				}
				// BLOB, byte[]型(OracleのRAW型)の場合、ビューアを開く。
				else if ( data instanceof byte[]) {
					AsanDatabaseBrowser.theApp.openBinaryFrame(title, (byte[]) data);
				}
				// 出くわしたことがないので、よく分からない・・・
				else if (data instanceof Struct) {
					Struct struct = (Struct) data;
					System.out.println("SQLTypeName="+struct.getSQLTypeName());
					Object[] objs = struct.getAttributes();
					for (int i=0; i<objs.length; i++) {
						System.out.println("["+i+"]="+objs[i]);
						if (objs[i] != null && objs[i] instanceof java.sql.Array) {
							java.sql.Array ary = (java.sql.Array) objs[i];
							System.out.println("BaseTypeName="+ary.getBaseTypeName());
							System.out.println("getArray()="+ary.getArray());
							if (ary.getArray() instanceof Object[]) {
								Object[] o = (Object[]) ary.getArray();
								for (int j=0; j<o.length; j++) {
									System.out.println("o["+j+"]="+o[j]);
								}
							}
							                                          
						}
					}
				}
				else {
					System.out.println(data);
				}
			} catch (SQLException e) {
				throw new DbException("LargeObjectの値の取得に失敗しました。", e);
			}
		}
	}

    public boolean connect(VvDatabase db) throws DbException {
    	// デフォルトのユーザ
    	String user = db.user;
    	String pass = db.password;
    	// 接続時にユーザ名とパスワードを問い合わせる
    	if (db.isShowLoginDialog) {
        	ConnectDialog dialog = new ConnectDialog();
    		dialog.setDatabase(db);
            int ret = JOptionPane.showConfirmDialog(treeFrame, 
            	dialog,
                "データベースへの接続", 
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE);
 	    	if (ret != JOptionPane.OK_OPTION) return false;
	    	user = dialog.getUser();
	    	pass = dialog.getPass();
    	}

    	// 接続
    	try {
    		treeFrame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
			db.connect(user, pass);
			return true;
		} finally {
			treeFrame.setCursor(Cursor.getDefaultCursor());
			treeFrame.updateMainFrame();
		}    	
    }

	public List getFrameList() {
		return frameList;
	}
	public DbTreeFrame getTreeFrame() {
		return treeFrame;
	}

	/**
	 * メッセージボックスを表示する.
	 * @param	messageType	JOptionPane.ERROR_MESSAGE JOptionPane.INFORMATION_MESSAGE 
	 * 	JOptionPane.WARNING_MESSAGE JOptionPane.QUESTION_MESSAGE 
	 * JOptionPane.PLAIN_MESSAGE のいずれか 
	 */
	public int showMessage(final Component parent, final String message, final Throwable t,
			final int optionType, final int messageType) {
		assert SwingUtilities.isEventDispatchThread();
		return SxMessageBox.show(parent, TITLE, message, t, optionType, messageType);
	}
	/**
	 * メッセージボックスを表示する.
	 * @param	messageType	JOptionPane.ERROR_MESSAGE JOptionPane.INFORMATION_MESSAGE 
	 * 	JOptionPane.WARNING_MESSAGE JOptionPane.QUESTION_MESSAGE 
	 * JOptionPane.PLAIN_MESSAGE のいずれか 
	 */
	public int showMessage(final Component parent, final String message, final Throwable t,
			final int messageType) {
		assert SwingUtilities.isEventDispatchThread();
		return showMessage(parent, message, t, SxOptionPane.OK_ONLY_OPTION, messageType);
	}
	/**
	 * メッセージボックスを表示する。
	 * Swingイベントディスパッチスレッド以外から呼ぶときには、こちらを使用すること。
	 */
	public void invokeShowMessage(final Component parent, final String message, final Throwable t, 
			final int optionType, final int messageType) {
		try {
			SwingUtilities.invokeAndWait(new Runnable() {
				public void run() {
					SxMessageBox.show(parent, TITLE, message, t, optionType, messageType);
				}
			});
		} catch (InterruptedException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
	}
	/**
	 * JavaVM引数に -Ddebug=trueを設定すると、デバッグモードで起動します。 
	 * @param args	コマンドライン引数.
	 * @throws DbException
	 */
	public static void main(final String[] args) throws DbException {
		// スッキリとしたフォントにする。
		System.setProperty("swing.plaf.metal.controlFont", "Dialog-12");
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
//                SxSprashFrame sprash = new SxSprashFrame(TITLE, 
//                		Global.getImageIcon("/resources/AsanDatabaseBrowser.png"));
        		AsanDatabaseBrowser.args = args;
        		AsanDatabaseBrowser.theApp = new AsanDatabaseBrowser();
        		if (theApp.initInstance(args)) {
        			theApp.treeFrame = new DbTreeFrame(theApp);
        			theApp.treeFrame.updateMainFrame();
        			theApp.logView = new SqlLogView();
        		}
//        		sprash.invokeDispose(700);	// 0.7sec
            }
        });
	}

}
