// Chattr - Twitter client on .NET
//
// Copyright (c) 2007 Katsuhiko Ichinose <ichi@users.sourceforge.jp>
//
// Chattr is Free Software released under GNU General Public License.
//
// $Id: TableAdapterTransactor.cs 31 2007-10-22 07:36:27Z ichi $

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

using System.Data;
using System.Data.SQLite;
using System.Reflection;

namespace Chattr
{
    /// <summary>
    /// TableAdapterにトランザクション機能を実装するクラス
    /// </summary>
    /// <remarks>
    /// 利用方法の例
    ///     TableAdapterTransactor tat = new TableAdapterTransactor();
    ///     Data1TableAdapter taData1 = new Data1TableAdapter();
    ///     Data2TableAdapter taData2 = new Data2TableAdapter();
    ///     
    ///     tat.AddTableAdapter(taData1);
    ///     tat.AddTableAdapter(taData2);
    ///     tat.BeginTransaction();
    ///     try {
    ///         taData1.Insert...();
    ///         taData2.Insert...();
    ///         tat.Commit();
    ///     }
    ///     catch (Exception ex) {
    ///         tat.Rollback();
    ///     }
    ///
    /// http://www.ailight.jp/blog/unaap/archive/2007/02/13/13566.aspx
    /// </remarks>
    class TableAdapterTransactor
    {
        private SQLiteConnection _conn = null;
        private SQLiteTransaction _trans = null;
        private List<object> _tableAdapters = new List<object>();

        #region "Pプロシージャ - GetConnection                     [TableAdapterのConnectionを取得する]"

        /// <summary>
        /// TableAdapterのConnectionを取得する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <returns>取得したConnection</returns>
        /// <remarks></remarks>
        private SQLiteConnection GetConnection(object tableAdapter)
        {
            Type type = tableAdapter.GetType();
            PropertyInfo connectionProperty = type.GetProperty("Connection", BindingFlags.NonPublic | BindingFlags.Instance);
            SQLiteConnection connection = (SQLiteConnection)connectionProperty.GetValue(tableAdapter, null);
            return connection;
        }
        #endregion

        #region "Pプロシージャ - SetConnection                     [TableAdapterのConnectionを設定する]"

        /// <summary>
        /// TableAdapterのConnectionを設定する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <param name="connection">Connection</param>
        /// <remarks></remarks>
        private void SetConnection(object tableAdapter, SQLiteConnection connection)
        {
            Type type = tableAdapter.GetType();
            PropertyInfo connectionProperty = type.GetProperty("Connection", BindingFlags.NonPublic | BindingFlags.Instance);
            connectionProperty.SetValue(tableAdapter, connection, null);
        }
        #endregion

        #region "Pプロシージャ - GetAdapter                        [TableAdapterのDataAdapterを取得する]"

        /// <summary>
        /// TableAdapterのDataAdapterを取得する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <returns>取得したDataAdapter</returns>
        /// <remarks></remarks>
        private SQLiteDataAdapter GetAdapter(object tableAdapter)
        {
            Type type = tableAdapter.GetType();
            FieldInfo adapterField = type.GetField("_adapter", BindingFlags.NonPublic | BindingFlags.Instance);
            SQLiteDataAdapter adapter = (SQLiteDataAdapter)adapterField.GetValue(tableAdapter);
            return adapter;
        }
        #endregion
        #region "Pプロシージャ - SetAdapter                        [TableAdapterのDataAdapterを設定する]"
        /// <summary>
        /// TableAdapterのDataAdapterを設定する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <param name="adapter">DataAdapter</param>
        /// <remarks></remarks>
        private void SetAdapter(object tableAdapter, SQLiteDataAdapter adapter)
        {
            Type type = tableAdapter.GetType();
            FieldInfo adapterField = type.GetField("_adapter", BindingFlags.NonPublic | BindingFlags.Instance);
            adapterField.SetValue(tableAdapter, adapter);
        }
        #endregion
        #region "Pプロシージャ - InitAdapter                       [TableAdapterのInitAdapterメソッドを呼び出す]"
        /// <summary>
        /// TableAdapterのInitAdapterメソッドを呼び出す
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <remarks>利用しない</remarks>
        private void InitAdapter(object tableAdapter)
        {
            Type type = tableAdapter.GetType();
            MethodInfo mi = type.GetMethod("InitAdapter", BindingFlags.NonPublic | BindingFlags.Instance);
            mi.Invoke(tableAdapter, null);
        }
        #endregion
        #region "Pプロシージャ - InitCommandCollection             [TableAdapterのInitCommandCollectionメソッドを呼び出す]"
        /// <summary>
        /// TableAdapterのInitCommandCollectionメソッドを呼び出す
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <remarks>利用しない</remarks>
        private void InitCommandCollection(object tableAdapter)
        {
            Type type = tableAdapter.GetType();
            MethodInfo mi = type.GetMethod("InitCommandCollection", BindingFlags.NonPublic | BindingFlags.Instance);
            mi.Invoke(tableAdapter, null);
        }
        #endregion
        #region "Pプロシージャ - SetTransactionCommands            [TableAdapterのCommandCollectionのTransactionを設定する]"
        /// <summary>
        /// TableAdapterのCommandCollectionのTransactionを設定する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <param name="transaction">Transaction</param>
        /// <remarks></remarks>
        private void SetTransactionCommands(object tableAdapter, SQLiteTransaction transaction)
        {
            Type type = tableAdapter.GetType();
            PropertyInfo commandsProperty = type.GetProperty("CommandCollection", BindingFlags.NonPublic | BindingFlags.Instance);
            SQLiteCommand[] commands = (SQLiteCommand[])commandsProperty.GetValue(tableAdapter, null);
            foreach (SQLiteCommand command in commands)
            {
                command.Transaction = transaction;
            }
            this.SetConnection(tableAdapter, transaction.Connection);
        }
        #endregion
        #region "Pプロシージャ - SetTransactionAdapter             [TableAdapterのDataAdapterのTransactionを設定する]"
        /// <summary>
        /// TableAdapterのDataAdapterのTransactionを設定する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <param name="transaction">Transaction</param>
        /// <remarks></remarks>
        private void SetTransactionAdapter(object tableAdapter, SQLiteTransaction transaction)
        {
            SQLiteDataAdapter adp = this.GetAdapter(tableAdapter);
            if (adp.InsertCommand != null)
            {
                adp.InsertCommand.Transaction = transaction;
            }
            if (adp.DeleteCommand != null)
            {
                adp.DeleteCommand.Transaction = transaction;
            }
            if (adp.UpdateCommand != null)
            {
                adp.UpdateCommand.Transaction = transaction;
            }
            this.SetAdapter(tableAdapter, adp);
            //this.InitCommandCollection(tableAdapter)
        }
        #endregion
        #region "メソッド - AddTableAdapter                        [TableAdapterを追加する]"
        /// <summary>
        /// TableAdapterを追加する
        /// </summary>
        /// <param name="tableAdapter">TableAdapter</param>
        /// <remarks></remarks>
        public void AddTableAdapter(object tableAdapter)
        {
            this._tableAdapters.Add(tableAdapter);
        }
        #endregion
        #region "メソッド - BeginTransaction                       [トランザクションを開始する]"
        /// <summary>
        /// トランザクションを開始する
        /// </summary>
        /// <remarks>開始する前に、AddTableAdapterメソッドで対象となるTableAdapterを追加すること</remarks>
        public void BeginTransaction()
        {
            int counter = 0;
            foreach (object adapter in this._tableAdapters)
            {
                counter += 1;
                if (counter == 1)
                {
                    this._conn = this.GetConnection(adapter);
                    if (this._conn.State != ConnectionState.Open)
                    {
                        this._conn.Open();
                    }
                    this._trans = this._conn.BeginTransaction();
                }
                this.SetConnection(adapter, this._conn);
                this.SetTransactionAdapter(adapter, this._trans);
                this.SetTransactionCommands(adapter, this._trans);
            }
        }
        /// <summary>
        /// トランザクションを開始する
        /// </summary>
        /// <param name="conn">コネクション</param>
        /// <remarks></remarks>
        public void BeginTransaction(SQLiteConnection conn)
        {
            this._conn = conn;
            if (this._conn.State != ConnectionState.Open)
            {
                this._conn.Open();
            }
            this._trans = this._conn.BeginTransaction();
            foreach (object adapter in this._tableAdapters)
            {
                this.SetConnection(adapter, this._conn);
                this.SetTransactionAdapter(adapter, this._trans);
                this.SetTransactionCommands(adapter, this._trans);
            }
        }
        /// <summary>
        /// トランザクションを開始する
        /// </summary>
        /// <param name="trans">トランザクション</param>
        /// <remarks></remarks>
        public void BeginTransaction(SQLiteTransaction trans)
        {
            this._conn = trans.Connection;
            this._trans = trans;
            foreach (object adapter in this._tableAdapters)
            {
                this.SetConnection(adapter, this._conn);
                this.SetTransactionAdapter(adapter, this._trans);
                this.SetTransactionCommands(adapter, this._trans);
            }
        }
        #endregion
        #region "メソッド - Commit                                 [トランザクションをコミットする]"
        /// <summary>
        /// トランザクションをコミットする
        /// </summary>
        /// <remarks></remarks>
        public void Commit()
        {
            try
            {
                this._trans.Commit();
            }
            catch (Exception ex)
            {
                this._trans.Rollback();
                throw ex;
            }
            finally
            {
                if ((this._conn.State == ConnectionState.Open))
                {
                    this._conn.Close();
                }
            }
        }
        #endregion
        #region "メソッド - Rollback                               [トランザクションをロールバックする]"
        /// <summary>
        /// トランザクションをロールバックする
        /// </summary>
        /// <remarks></remarks>
        public void Rollback()
        {
            this._trans.Rollback();
            if ((this._conn.State == ConnectionState.Open))
            {
                this._conn.Close();
            }
        }
        #endregion
    }
}
