using System;
using System.Collections.Generic;
using System.Text;

using System.Data;
using System.Data.Common;
using System.Globalization;

namespace MyKarte.Database
{
    /// <summary>
    /// データベース クラス
    /// </summary>
    public abstract class DbBase : IDisposable
    {
        // 接続
        private DbConnection _conn;

        // データアダプタ
        private DbDataAdapter _da;

        // トランザクション
        private DbTransaction _tran;

        private bool _opened = false;

        private bool _disposed = false;

        #region プロパティ
        /// <summary>
        /// 接続
        /// </summary>
        protected DbConnection Connection
        {
            get
            {
                return _conn;
            }

            set
            {
                _conn = value;
            }
        }
        #endregion

        /// <summary>
        /// DbProviderFactoryを取得する。
        /// </summary>
        /// <returns>DbProviderFactory</returns>
        protected abstract DbProviderFactory GetFactory();

        /// <summary>
        /// DbCommandを作成する。
        /// </summary>
        /// <returns>DbCommand</returns>
        public DbCommand CreateCommand()
        {
            DbProviderFactory factory = GetFactory();

            return factory.CreateCommand();
        }

        /// <summary>
        /// DbConnectionを作成する。
        /// </summary>
        /// <returns>DbConnection</returns>
        protected DbConnection CreateConnection()
        {
            DbProviderFactory factory = GetFactory();

            return factory.CreateConnection();
        }

        /// <summary>
        /// DbDataAdapterを作成する。
        /// </summary>
        /// <returns>DbDataAdapter</returns>
        private DbDataAdapter CreateDataAdapter()
        {
            DbProviderFactory factory = GetFactory();

            return factory.CreateDataAdapter();
        }

        /// <summary>
        /// DbCommandBuilderを作成する。
        /// </summary>
        /// <returns>DbCommandBuilder</returns>
        private DbCommandBuilder CreateCommandBuilder()
        {
            DbProviderFactory factory = GetFactory();

            return factory.CreateCommandBuilder();
        }

        /// <summary>
        /// 接続文字列を取得する。
        /// </summary>
        /// <returns>接続文字列</returns>
        protected abstract string GetConnectionString();

        /// <summary>
        /// 接続を開く。
        /// </summary>
        public virtual void Open()
        {
            _conn.Open();
        }

        /// <summary>
        /// 接続を閉じる。
        /// </summary>
        public virtual void Close()
        {
            _conn.Close();
        }

        /// <summary>
        /// データベースからデータを取得する。
        /// </summary>
        /// <param name="query">SQLクエリ</param>
        /// <returns>データテーブル</returns>
        public DataTable GetDataTable(string query)
        {
            DbCommand dbCmd = CreateCommand();
            dbCmd.CommandText = query;

            return GetDataTable(dbCmd);
        }

        /// <summary>
        /// データベースからデータを取得する。
        /// </summary>
        /// <param name="dbCmd">DbCommand</param>
        /// <returns>データテーブル</returns>
        public DataTable GetDataTable(DbCommand dbCmd)
        {
            dbCmd.Connection = _conn;

            _da = CreateDataAdapter();
            _da.SelectCommand = dbCmd;
            _da.SelectCommand.Transaction = _tran;

            DataTable dt = new DataTable();
            dt.Locale = CultureInfo.InvariantCulture;

            // データ取得
            _da.Fill(dt);

            dbCmd.Parameters.Clear();

            return dt;
        }

        /// <summary>
        /// データベースにデータを設定する。
        /// </summary>
        /// <param name="dt">データテーブル</param>
        /// <returns>処理件数</returns>
        public int SetDataTable(DataTable dt)
        {
            int count = 0;

            bool opened = false;
            bool transacted = false;

            try
            {
                if (_conn.State == ConnectionState.Closed)
                {
                    _conn.Open();

                    opened = true;
                }

                if ((_tran == null) || (_tran.Connection == null))
                {
                    _tran = _conn.BeginTransaction();

                    transacted = true;
                }

                _da.SelectCommand.Transaction = _tran;

                DbCommandBuilder cb = CreateCommandBuilder();
                cb.DataAdapter = _da;

                _da.InsertCommand = cb.GetInsertCommand();
                _da.UpdateCommand = cb.GetUpdateCommand();
                _da.DeleteCommand = cb.GetDeleteCommand();

                _da.InsertCommand.Transaction = _tran;
                _da.UpdateCommand.Transaction = _tran;
                _da.DeleteCommand.Transaction = _tran;

                // 更新
                count = _da.Update(dt);

                if (transacted)
                {
                    // コミット
                    _tran.Commit();
                }
            }
            catch (Exception)
            {
                if (transacted)
                {
                    // ロールバック
                    _tran.Rollback();
                }

                // 例外を再スロー
                throw;
            }
            finally
            {
                if (transacted)
                {
                    _tran.Dispose();
                }

                if (opened)
                {
                    _conn.Close();
                }
            }

            return count;
        }

        /// <summary>
        /// SQLクエリを実行する。
        /// </summary>
        /// <param name="query">SQLクエリ</param>
        /// <returns>処理件数</returns>
        public int ExecuteSql(string query)
        {
            DbCommand dbCmd = CreateCommand();
            dbCmd.CommandText = query;

            return ExecuteSql(dbCmd);
        }

        /// <summary>
        /// DbCommandを実行する。
        /// </summary>
        /// <param name="dbCmd">DbCommand</param>
        /// <returns>処理件数</returns>
        public int ExecuteSql(DbCommand dbCmd)
        {
            int count = 0;

            bool opened = false;

            try
            {
                if (_conn.State == ConnectionState.Closed)
                {
                    _conn.Open();

                    opened = true;
                }

                if (_tran != null)
                {
                    dbCmd.Transaction = _tran;
                }

                dbCmd.Connection = _conn;

                // 実行
                count = dbCmd.ExecuteNonQuery();
            }
            finally
            {
                if (opened)
                {
                    _conn.Close();
                }
            }

            return count;
        }

        /// <summary>
        /// トランザクションを開始する。
        /// </summary>
        public void BeginTransaction()
        {
            if (_conn.State == ConnectionState.Closed)
            {
                _conn.Open();

                _opened = true;
            }

            _tran = _conn.BeginTransaction();
        }

        /// <summary>
        /// コミットする。
        /// </summary>
        public void Commit()
        {
            _tran.Commit();
            _tran.Dispose();

            if (_opened)
            {
                _conn.Close();

                _opened = false;
            }
        }

        /// <summary>
        /// ロールバックする。
        /// </summary>
        public void Rollback()
        {
            _tran.Rollback();
            _tran.Dispose();

            if (_opened)
            {
                _conn.Close();

                _opened = false;
            }
        }

        /// <summary>
        /// テーブル一覧を取得する。
        /// </summary>
        /// <returns>テーブル一覧</returns>
        public abstract string[] GetTables();

        /// <summary>
        /// テーブルが存在するか確認する。
        /// </summary>
        /// <param name="tableName">テーブル名</param>
        /// <returns>true：存在する, false：存在しない</returns>
        public bool ExistTable(string tableName)
        {
            string[] tables = GetTables();

            bool ret = Array.Exists<string>(tables,
                delegate(string param)
                {
                    return param == tableName;
                });

            return ret;
        }

        /// <summary>
        /// デストラクタ
        /// </summary>
        ~DbBase()
        {
            Dispose(false);
        }

        /// <summary>
        /// リソースを解放する。
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// リソースを解放する。
        /// </summary>
        /// <param name="disposing">
        /// マネージ リソースとアンマネージ リソースの両方を解放する場合は true。
        /// アンマネージ リソースだけを解放する場合は false。
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            _disposed = true;

            if (disposing)
            {
                _conn.Dispose();

                if (_da != null)
                {
                    _da.Dispose();
                }
            }
        }
    }
}
