// Copyright (C) 2003 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2004, 2005, 2007 panacoran <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id: Connection.cs 306 2010-03-19 13:21:22Z panacoran $

using System;
using System.Collections;
using System.Data;
using System.Data.OleDb;
using System.IO;
using System.Windows.Forms;
using Protra.Lib.Dialogs;

namespace Protra.Lib.Db
{
	/// <summary>
	/// レコードをオブジェクトに変換するメソッドを定義する。
	/// </summary>
	public interface IRecord
	{
		/// <summary>
		/// データベースのレコードをオブジェクトに変換する。
		/// </summary>
		/// <param name="record">ネイティブ形式の値の配列</param>
		/// <returns>変換後のオブジェクトを返す。</returns>
		Object ToObject(Object[] record);
	}

	/// <summary>
	/// OLE DB経由でAccessデータベースを操作するクラス。
	/// DBへの接続をプロセス全体で共有する。
	/// </summary>
	public class Connection
	{
		private bool throwException;

		/// <summary>
		/// 例外を投げるかどうかを設定する。
		/// </summary>
		public bool ThrowException
		{
			set { throwException = value; }
		}

		/// <summary>
		/// OleDbErrorの内容を表示する。
		/// </summary>
		/// <param name="e">OleDbExceptionのインスタンス。</param>
		/// <param name="desc">エラーの原因の記述。</param>
		public void ShowOleDbError(OleDbException e, string desc)
		{
			if (throwException)
				throw e;
			string msg = "データベースのアクセスでエラーが発生しました:\n";
			if (desc != null)
				msg += desc + "\n";
			for (int i = 0; i < e.Errors.Count; i++)
			{
				msg += "Index #" + i + "\n" +
					"Message: " + e.Errors[i].Message + "\n" +
					"NativeError: " + e.Errors[i].NativeError + "\n" +
					"Source: " + e.Errors[i].Source + "\n" +
					"SQLState: " + e.Errors[i].SQLState;
			}
            using (var dialog = new ApplicationError())
            {
                dialog.ErrorMessage = msg;
                dialog.Mode = ApplicationError.ErrorType.Fatal;
                dialog.ShowDialog();
            }
            Environment.Exit(1);
		}

		private static Hashtable connPool = new Hashtable();
		private OleDbConnection conn;

		/// <summary>
		/// コンストラクタ。
		/// </summary>
		public Connection(string dbname)
		{
			lock (typeof(Connection))
			{
				conn = (OleDbConnection)connPool[dbname];
				if (conn != null &&	(conn.State & ConnectionState.Open) != 0)
					return;
				string target = "Provider=Microsoft.JET.OLEDB.4.0;" +
					"data source=" + Path.Combine(Global.DirData, dbname + ".mdb");
				conn = new OleDbConnection(target);
				try
				{
					conn.Open();
				}
				catch (OleDbException e)
				{
					ShowOleDbError(e, target);
				}
				connPool[dbname] = conn;
			}
			
		}

		/// <summary>
		/// クエリの実行結果をオブジェクトのリストで返す。
		/// </summary>
		/// <param name="sql">SQL文を指定する。</param>
		/// <param name="cvtr">変換メソッドを実装したクラスのインスタンスを指定する。</param>
		/// <returns>cvtrによって変換されたオブジェクトのリストを返す。</returns>
		public ArrayList Query(string sql, IRecord cvtr)
		{
			ArrayList list = new ArrayList();
			OleDbCommand comm = new OleDbCommand(sql, conn);
			OleDbDataReader reader = null;
			lock (conn)
			{
				try
				{
					reader = comm.ExecuteReader();
				}
				catch (OleDbException e)
				{
					ShowOleDbError(e, sql);
				}
				try
				{
					while (reader.Read())
					{
						Object[] values = new Object[reader.FieldCount];
						reader.GetValues(values);
						list.Add(cvtr.ToObject(values));
					}
				}
				finally
				{
					reader.Close();
				}
			}
			return list;
		}

		/// <summary>
		/// クエリの結果の最初のレコードの最初のフィールドを返す。
		/// </summary>
		/// <param name="sql">SQL文を指定する。</param>
		/// <returns>最初のレコードの最初のフィールド。</returns>
		public Object QueryScalar(string sql)
		{
			OleDbCommand comm = new OleDbCommand(sql, conn);
			lock (conn)
			{
				try 
				{
					return comm.ExecuteScalar();
				}
				catch (OleDbException e) 
				{
					ShowOleDbError(e, sql);
				}
				return null;
			}
		}

		/// <summary>
		/// SQL文を実行して影響を受けた行の数を返す。
		/// </summary>
		/// <param name="sql">SQL文を指定する。</param>
		/// <returns>影響を受けた行の数を返す。</returns>
		public int Execute(string sql)
		{
			OleDbCommand comm = new OleDbCommand(sql, conn);
			lock (conn)
			{
				try
				{
					return  comm.ExecuteNonQuery();
				}
				catch (OleDbException e)
				{
					ShowOleDbError(e, sql);
				}
				return 0;
			}
		}

		/// <summary>
		/// データベースへの接続を閉じる。
		/// </summary>
		public void Close()
		{
			conn.Close();
		}
	}
}
