//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// CommonHelpers satellite library | Author: Sergei Y.Kitáev
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Threading;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace CommonHelpers {

//**************************************************************************************************
[HDbOrmType][HAsyncStatic][HSyncOnlyDisposable]
public abstract partial class HQueryExecContextBase: HDisposable {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[HVolatileAccess]
public static HConnectionStringAccessorCallback? ConnectionStringAccessor {
   get => _ConnectionStringAccessor_;
   set => _ConnectionStringAccessor_ = value;
}

[HAsyncStatic]
public static bool HasCurrentInstance =>
   GetCurrent() != null;

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
[HAsyncStatic][HOptionalValue]
protected static IDictionary<string,int>? FieldIndexer =>
   Current._FieldIndexer_;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic][HOptionalValue]
protected static string? ProcedureName =>
   Current._ProcedureName_;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static SqlDataReader DataReader
{
   get
   {
      VerifyContext( true );
      return HNN._( Current._DataReader_ );
   }
}
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static bool HasDataReader =>
   Current._DataReader_ != null;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static SqlConnection Connection =>
   Current._Connection_;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static bool ConnectionOpenAttempt =>
   Current._ConnectionOpenAttempt_;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static Exception? ConnectionException =>
   Current._ConnectionException_;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static bool HasConnectionOwnership =>
   Current._HasConnectionOwnership_;

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static object ParametersDescriptor
{
   get
   {
      object? parameters = Current._ParametersDescriptor_;
      if ( parameters == null )
      throw new InvalidOperationException(HLocal.TEXT(
         "Async-static parameters descriptor is not specified (\"HQueryExecContextBase\").",
         "Асинхронно-статический дескриптор параметров не задан (\"HQueryExecContextBase\")." ));
      return parameters;
   }
}

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static object AdditionalInfo
{
   get
   {
      object? info = Current._AdditionalInfo_;
      if ( info == null )
      throw new InvalidOperationException(HLocal.TEXT(
         "Async-static additional info is not specified (\"HQueryExecContextBase\").",
         "Асинхронно-статическая дополнительная информация не задана "+
         "(\"HQueryExecContextBase\")." ));
      return info;
   }
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
public HQueryExecContextBase()
: this( false )
{}
/// <exception cref="InvalidOperationException" />
public HQueryExecContextBase(bool openConnection)
: this( null, openConnection )
{}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
protected HQueryExecContextBase([HOptional] string? procedureName)
: this( procedureName, false )
{}
/// <exception cref="InvalidOperationException" />
protected HQueryExecContextBase([HOptional] string? procedureName,bool openConnection)
{
   if ( HasCurrentInstance )
      throw new InvalidOperationException(HLocal.TEXT(
         "Attempt to repeatedly create query execution context.",
         "Попытка повторного создания контекста исполнения запроса." ));
   var connStrAccessor = ConnectionStringAccessor;
   if ( connStrAccessor == null )
      throw new InvalidOperationException(HLocal.TEXT(
         "Access to connection string was not provided.",
         "Не был обеспечен доступ к строке подключения." ));
   string connectionStr = connStrAccessor();
   if ( connectionStr == null )
      throw new InvalidOperationException(HLocal.TEXT(
         "Connection string accessor returned null.",
         "Средство доступа к строке подключения вернуло пустой результат." ));
   var connection = HOuterConnection.Current?.Connection;
   if ( connection == null )
   {
      _HasConnectionOwnership_ = true;
      connection = new SqlConnection( connectionStr );
   }
   if ( openConnection && _HasConnectionOwnership_ )
      connection.Open();
   _FieldIndexer_ = new Dictionary<string,int>();
   _ProcedureName_ = procedureName;
   _Connection_ = connection;
   ValidateDisposable();
   SetCurrent( this );
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void Connect() =>
   Current.DoConnect();
void DoConnect()
{
   if ( _ConnectionOpenAttempt_ )
      throw ConnectionError_RepeatedAttempt;
   DoEnsureConnected();
}
static InvalidOperationException ConnectionError_RepeatedAttempt =>
   new InvalidOperationException(HLocal.TEXT(
      $"Attempt of repeatedly connection from \"HQueryExecContextBase\".",
      $"Попытка повторного подключения из \"HQueryExecContextBase\"." ));

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void EnsureConnected() =>
   Current.DoEnsureConnected();
void DoEnsureConnected()
{
   if ( !_HasConnectionOwnership_ )
      return;
   if ( _ConnectionOpenAttempt_ )
   {
      if ( _ConnectionException_ != null )
         throw _ConnectionException_;
      EnsureConnected_UnexpectedConnectionState( _Connection_ );
      return;
   }
   _ConnectionOpenAttempt_ = true;
   try { _Connection_.Open(); }
   catch (Exception e) {
      _ConnectionException_ = e;
      throw; }
}
/// <exception cref="InvalidOperationException" />
internal static void EnsureConnected_UnexpectedConnectionState( SqlConnection connection )
{
   if ( connection.State != ConnectionState.Open )
      throw new InvalidOperationException(HLocal.TEXT(
         "Connection state is unexpectendly not open connection state.",
         "Статус подключения неожиданно не является статусом открытого подключения." ));
}

/// <exception cref="InvalidOperationException" />
/// <exception cref="Exception" />
[HAsyncStatic]
public static SqlConnection GetOpenConnection() =>
   Current.DoGetOpenConnection();
SqlConnection DoGetOpenConnection()
{
   EnsureConnected();  return _Connection_;
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void VerifyContext() =>
   VerifyContext( false );
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void VerifyContext(bool verifyDataReader)
{
   var curr = Current;
   if ( verifyDataReader && curr._DataReader_ == null )
      throw new InvalidOperationException(HLocal.TEXT(
         "Async-static query execution context is unavailable (\"HQueryExecContextBase\").",
         "Асинхронно-статический контекст исполнения запроса для недоступен "+
         "(\"HQueryExecContextBase\")." ));
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
[HAsyncStatic][HOptionalResult]
public static string? GetProcedureName() =>
   ProcedureName;

/// <exception cref="InvalidOperationException" />
[HAsyncStatic][HStrictResult]
public static string StrictGetProcedureName() =>
   ProcedureName ?? throw new InvalidOperationException(HLocal.TEXT(
      "Stored procedure name is unavailable (because the query is not stored procedure).",
      "Имя хранимой процедуры недоступно (потому что запрос не является хранимой процедурой)."
   ));

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void SetParametersDescriptor(object parameters) =>
   Current._ParametersDescriptor_ = parameters;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void ResetParametersDescriptor() =>
   Current._ParametersDescriptor_ = null;

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static object? GetParametersDescriptor() =>
   Current._ParametersDescriptor_;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static bool HasParametersDescriptor() =>
   GetParametersDescriptor() != null;

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void SetAdditionalInfo(object info) =>
   Current._AdditionalInfo_ = info;
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static void ResetAdditionalInfo() =>
   Current._AdditionalInfo_ = null;

/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static object? GetAdditionalInfo() =>
   Current._AdditionalInfo_;
[HAsyncStatic]
public static bool HastAdditionalInfo() =>
   GetAdditionalInfo() != null;

//--------------------------------------------------------------------------------------------------

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidOperationException" />
[HAsyncStatic]
public static string AppendProcNameToMessage(string message)
{
   HArgChecking.VerifyNotNull( message );
   string? procedureName = ProcedureName;
   return procedureName != null ?
      HString.FormatAsText(HLocal.TEXT(
         "{0}\n(Stored procedure: [{1}].)",
         "{0}\n(Хранимая процедура: [{1}].)" ),
         message, procedureName ) :
      message;
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
[HAsyncStatic][HStrictResult]
protected static HQueryExecContextBase Current
{
   get
   {
      var curr = GetCurrent();
      if ( curr == null )
         throw new InvalidOperationException(HLocal.TEXT(
            "Query execution context is absent (\"HQueryExecContextBase\").",
            "Отсутствует контекст исполнения запроса (\"HQueryExecContextBase\")." ));
      return curr;
   }
}

//--------------------------------------------------------------------------------------------------

[HAsyncStatic]
protected override void OnDispose(bool synchronous)
{
   HAssertion.Verify( synchronous );
   if ( _Connection_ == null )
      return;
   SetCurrent( null );
   var conn = HOps.NullExchange(ref _Connection_);
   var rdr = HOps.NullExchange(ref _DataReader_);
   _FieldIndexer_ = null;
   _ProcedureName_ = null;
   _ConnectionOpenAttempt_ = false;
   _ConnectionException_ = null;
   Exception? err = null;
   if ( rdr != null )
   {
      HOps.Try(ref err,()=>{ rdr.Close(); });
      HOps.Try(ref err,()=>{ rdr.Dispose(); });
   }
   if ( _HasConnectionOwnership_ )
   {
      HOps.Try(ref err,()=>{ conn.Close(); });
      HOps.Try(ref err,()=>{ conn.Dispose(); });
   }
   if ( err != null ) throw err;
}

//==================================================================================================

[HAsyncStatic][HOptionalResult]
static HQueryExecContextBase? GetCurrent() =>
   _AsyncStatic_.Value;
[HAsyncStatic]
static void SetCurrent([HOptional] HQueryExecContextBase? instance) =>
   _AsyncStatic_.Value = instance;

[HAsyncStatic][HOptionalValue]
static SqlTransaction? OuterTransaction =>
   HOuterConnection.Current?.Transaction;

//==================================================================================================

static readonly AsyncLocal<HQueryExecContextBase?>
   _AsyncStatic_ = new AsyncLocal<HQueryExecContextBase?>();
static volatile HConnectionStringAccessorCallback?
   _ConnectionStringAccessor_;

//--------------------------------------------------------------------------------------------------

bool _HasConnectionOwnership_;
SqlConnection _Connection_;
SqlDataReader? _DataReader_;
IDictionary<string,int>? _FieldIndexer_;
string? _ProcedureName_;
bool _ConnectionOpenAttempt_;
Exception? _ConnectionException_;
object? _ParametersDescriptor_;
object? _AdditionalInfo_;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // HQueryExecContextBase
//**************************************************************************************************

} // CommonHelpers
