//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// DataHelpers.SQL satellite library | Author: Sergei Y.Kitáev
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Data;
using System.Data.SqlClient;
using u = user_rtx;

namespace DataHelpers.SQL {

/// <summary>
/// </summary>
[h_db_orm_type][u.async_local][u.sync_only_disposable]
public abstract partial class HQueryExecContextBase: u.DisposableSyncImpl<HQueryExecContextBase> {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[u.volatile_access]
public static HConnectionStringAccessorFN? ConnectionStringAccessor {
   get => _connectionStringAccessor;
   set => _connectionStringAccessor = value; }

[u.async_local]
public static bool HasCurrentInstance =>
   GetCurrent() != null;

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

/// <exception cref="InvalidOperationException" />
[u.async_local]
protected static IDictionary<string,int>? FieldIndexer =>
   Current._fieldIndexer;
/// <exception cref="InvalidOperationException" />
[u.async_local]
protected static string? ProcedureName =>
   Current._procedureName;
/// <exception cref="InvalidOperationException" />
[u.async_local]
public static SqlDataReader DataReader {
   get {
      VerifyContext( true );
      return u.not_null._( Current._dataReader );
   }}
/// <exception cref="InvalidOperationException" />
[u.async_local]
public static bool HasDataReader =>
   Current._dataReader != null;
/// <exception cref="InvalidOperationException" />
[u.async_local]
public static SqlConnection Connection =>
   Current.ConnectionNN;
/// <exception cref="InvalidOperationException" />
[u.async_local]
public static bool ConnectionOpenAttempt =>
   Current._connectionOpenAttempt;
/// <exception cref="InvalidOperationException" />
[u.async_local]
public static Exception? ConnectionException =>
   Current._connectionException;
/// <exception cref="InvalidOperationException" />
[u.async_local]
public static bool HasConnectionOwnership =>
   Current._hasConnectionOwnership;

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

/// <exception cref="InvalidOperationException" />
[u.async_local]
public static object AdditionalInfo {
   get {
      object? info = Current._additionalInfo;
      if ( info == null )
      throw new InvalidOperationException(u.local.T(
         "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( string? procedureName )
: this( procedureName, false )
{}
/// <exception cref="InvalidOperationException" />
[MethodImpl( MethodImplOptions.NoInlining )]
protected HQueryExecContextBase( string? procedureName, bool openConnection )
{
   if ( HasCurrentInstance )
      throw new InvalidOperationException(u.local.T(
         "Attempt to repeatedly create query execution context.",
         "Попытка повторного создания контекста исполнения запроса." ));
   var connStrAccessor = ConnectionStringAccessor;
   if ( connStrAccessor == null )
      throw new InvalidOperationException(u.local.T(
         "Access to connection string was not provided.",
         "Не был обеспечен доступ к строке подключения." ));
   string connectionStr = connStrAccessor();
   if ( connectionStr == null )
      throw new InvalidOperationException(u.local.T(
         "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" />
[u.async_local]
public static void Connect() =>
   Current.DoConnect();
void DoConnect()
{
   if ( _connectionOpenAttempt )
      throw ConnectionError_RepeatedAttempt;
   DoEnsureConnected();
}
static InvalidOperationException ConnectionError_RepeatedAttempt =>
   new InvalidOperationException(u.local.T(
      $"Attempt of repeatedly connection from \"HQueryExecContextBase\".",
      $"Попытка повторного подключения из \"HQueryExecContextBase\"." ));

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

/// <exception cref="InvalidOperationException" />
[u.async_local]
public static void EnsureConnected() =>
   Current.DoEnsureConnected();
void DoEnsureConnected()
{
   if ( !_hasConnectionOwnership )
      return;
   if ( _connectionOpenAttempt )
   {
      if ( _connectionException != null )
         throw _connectionException;
      EnsureConnected_UnexpectedConnectionState( ConnectionNN );
      return;
   }
   _connectionOpenAttempt = true;
   try { ConnectionNN.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(u.local.T(
         "Connection state is unexpectendly not open connection state.",
         "Статус подключения неожиданно не является статусом открытого подключения." ));
}

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

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

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

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

/// <exception cref="InvalidOperationException" />
[u.async_local]
public static string? GetProcedureName() =>
   ProcedureName;

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

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

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

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

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

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

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

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

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

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

/// <exception cref="InvalidOperationException" />
[u.async_local][u.strict_result]
protected static HQueryExecContextBase Current =>
   GetCurrent()?.VerifyNotDisposed() ??
      throw new InvalidOperationException(u.local.T(
         "Query execution context is absent (“HQueryExecContextBase”).",
         "Отсутствует контекст исполнения запроса (“HQueryExecContextBase”)." ));

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

protected override HQueryExecContextBase DisposableSelf => this;

[u.async_local]
protected override void OnSyncDispose()
{
   if ( _connection == null )
      return;
   SetCurrent( null );
   var conn = u.ops.NullExchange(ref _connection).NN();
   var rdr = u.ops.NullExchange(ref _dataReader);
   _fieldIndexer = null;
   _procedureName = null;
   _connectionOpenAttempt = false;
   _connectionException = null;
   Exception? err = null;
   if ( rdr != null ) {
      u.ops.TryAgain(ref err,()=>{ rdr.Close(); });
      u.ops.TryAgain(ref err,()=>{ rdr.Dispose(); });
   }
   if ( _hasConnectionOwnership ) {
      u.ops.TryAgain(ref err,()=>{ conn.Close(); });
      u.ops.TryAgain(ref err,()=>{ conn.Dispose(); });
   }
   if ( err != null )
      throw err;
}

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

/// <exception cref="u.ENullValue" />
SqlConnection ConnectionNN =>
   _connection.NN();

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

[u.async_local]
static HQueryExecContextBase? GetCurrent() =>
   _asyncStatic.Value;
[u.async_local]
static void SetCurrent( HQueryExecContextBase? instance ) =>
   _asyncStatic.Value = instance;

[u.async_local]
static SqlTransaction? OuterTransaction =>
   HOuterConnection.Current?.Transaction;

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

static readonly AsyncLocal<HQueryExecContextBase?>
   _asyncStatic = new ();
static volatile HConnectionStringAccessorFN?
   _connectionStringAccessor;

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

readonly bool _hasConnectionOwnership;
SqlConnection? _connection;
SqlDataReader? _dataReader;
IDictionary<string,int>? _fieldIndexer;
string? _procedureName;
bool _connectionOpenAttempt;
Exception? _connectionException;
object? _parametersDescriptor;
object? _additionalInfo;

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

} // DataHelpers.SQL
