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

namespace CommonHelpers {

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

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidOperationException" />
public HSP(string procedureName)
: base( HArgChecking.NN( procedureName ) )
{}

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

[HThreadSafe] public static string StatusParameterName  => _StatusParameterName_;
[HThreadSafe] public static string MessageParameterName => _MessageParameterName_;

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

/// <exception cref="InvalidOperationException" />
[HAsyncStatic][HNotNull]
public static new string ProcedureName => HQueryExecContextBase.StrictGetProcedureName();

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

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidOperationException" />
/// <exception cref="InvalidCastException" />
/// <exception cref="HDatabaseException" />
[HOptionalResult]
public static T GetOutputParameterValue<T>( SqlParameter parameter,
                                            bool treatNullAsDefaultValue=false ) =>
   HDB.GetOutputParameterValue<T>( ProcedureName, parameter, treatNullAsDefaultValue );

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

/// <exception cref="ArgumentNullException" />
[HAsyncStatic]
public static ( HSPResultParameters ResultParameters, SqlCommand Command )
   PrepareCommand()
{
   var Command = HDB.CreateSPCommandEx( Connection, OuterTransaction, ProcedureName );
   var ResultParameters = AddResultParameters( Command );
   return ( ResultParameters, Command );
}
/// <exception cref="ArgumentNullException" />
[HAsyncStatic]
public static ( HSPResultParameters ResultParameters, SqlCommand Command )
   PrepareCommand( params SqlParameter[] sqlParams )
{
   var Command = HDB.CreateSPCommandEx( Connection, OuterTransaction, ProcedureName, sqlParams );
   var ResultParameters = AddResultParameters( Command );
   return ( ResultParameters, Command );
}

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

/// <exception cref="ArgumentNullException" />
public static HSPResultParameters AddResultParameters( SqlCommand command ) =>
   new HSPResultParameters(
      HSqlParam.AddRetValue( command, SqlDbType.Int, 4 ), // size=4 is not required (may be omitted)
      HSqlParam.AddNullableOut( command, StatusParameterName, SqlDbType.UniqueIdentifier ),
      HSqlParam.AddNullableOut( command, MessageParameterName, SqlDbType.NVarChar, -1 ) );

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

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
[HOptionalResult]
public static int RetValue(HSPResultParameters resultParameters)
{
   HArgChecking.VerifyNotNull( resultParameters );
   return HDB.GetSPReturnValue( ProcedureName, resultParameters.RetValue );
}
/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
[HOptionalResult]
public static Guid Status(HSPResultParameters resultParameters)
{
   HArgChecking.VerifyNotNull( resultParameters );
   object value = resultParameters.Status.Value;
   return value==null || value is DBNull ? Guid.Empty : (Guid)value;
}
/// <exception cref="ArgumentNullException" />
[HOptionalResult]
public static string? OutMessage(HSPResultParameters resultParameters)
{
   HArgChecking.VerifyNotNull( resultParameters );
   string? value = resultParameters.OutMessage.Value as string;
   if ( value is null )
      return null;
   value = value.Trim();
   return value != "" ? value : HLocal.TEXT(
      "Empty message has been returned by database query.",
      "Пустое сообщение было возвращено запросом к БД." );
}

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
public static bool Success(HSPResultParameters resultParameters) =>
   RetValue( resultParameters ) == 0;
/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
/// <exception cref="HDatabaseException" />
public static void VerifySuccess(HSPResultParameters resultParameters)
{
   HArgChecking.VerifyNotNull( resultParameters );
   if ( !Success( resultParameters ) )
      throw HDBExceptionCreator.Create(HLocal.TEXT(
         "Stored procedure [{0}] has unexpectedly returned non-successful result.",
         "Хранимая процедура [{0}] неожиданно вернула неуспешный результат." ),
         ProcedureName );
}

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

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
public static bool HasStatus(HSPResultParameters resultParameters) =>
   Status( resultParameters ) != Guid.Empty;

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
/// <exception cref="HDatabaseException" />
public static void VerifyStatusEmpty(HSPResultParameters resultParameters)
{
   HArgChecking.VerifyNotNull( resultParameters );
   if ( HasStatus( resultParameters ) )
      throw HDBExceptionCreator.Create(HLocal.TEXT(
         "Stored procedure [{0}] has unexpectedly returned non-empty status.",
         "Хранимая процедура [{0}] неожиданно вернула непустой статус." ),
         ProcedureName );
}

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

/// <exception cref="ArgumentNullException" />
/// <exception cref="InvalidCastException" />
/// <exception cref="HDatabaseException" />
public static void VerifyResultParameters(HSPResultParameters resultParameters)
{
   HArgChecking.VerifyNotNull( resultParameters );
   try
   {
      VerifySuccess( resultParameters );
   }
   catch (HDatabaseException e)
   {
      var outMessage = OutMessage( resultParameters );
      if ( outMessage == null )
         throw;
      throw new HDatabaseException( $"{e.Message}\r\n{outMessage}" );
   }
}

/// <exception cref="InvalidCastException" />
/// <exception cref="HDatabaseException" />
static void VerifyResultParamsIfNotNull(HSPResultParameters? resultParameters)
{
   if ( resultParameters != null )
      HSP.VerifyResultParameters( resultParameters );
}

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

/// <exception cref="ArgumentNullException" />
[HThreadSafe]
public static void SpecifyStatusParameterName(string value) =>
   _StatusParameterName_ = HArgChecking.VerifyNotNull( value );
[HThreadSafe]
public static void SpecifyMessageParameterName(string value) =>
   _MessageParameterName_ = HArgChecking.VerifyNotNull( value );

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

/// <exception cref="HDatabaseException" />
[HAsyncStatic]
public static List<RSet_Item> VerifyRSet<RSet_Item>( [HOptional] List<RSet_Item>? list ) =>
   list ?? throw HDBExceptionCreator.Create(HLocal.TEXT(
      "Stored procedure [{0}] has not generetad expected result set.",
      "Хранимая процедура [{0}] не сгенерировала ожидаемый резалт-сет." ),
      ProcedureName );
/// <exception cref="HDatabaseException" />
[HAsyncStatic]
public static List<RSet_Item> VerifyRSet<RSet_Item>( [HOptional] List<RSet_Item>? list,
                                                     int resultSetNo ) =>
   list ?? throw new HDatabaseException(HSP.AppendProcNameToMessage(
      HString.FormatAsText(HLocal.TEXT(
         "Database query has not returned expected result set #{0}.",
         "Запрос к базе данных не вернул ожидаемый резалт-сет #{0}." ),
         resultSetNo ) ));

/// <exception cref="HDatabaseException" />
[HAsyncStatic]
public static TOutData VerifyOutData<TOutData>( [HOptional] TOutData? output )
where TOutData: class =>
   output ?? throw HDBExceptionCreator.Create(HLocal.TEXT(
      "Stored procedure [{0}] has not generetad expected output data.",
      "Хранимая процедура [{0}] не сгенерировала ожидаемые выходные данные." ),
      ProcedureName );

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

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

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

static volatile string
   _StatusParameterName_ = "Status",
   _MessageParameterName_ = "Message";

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // HSP
//**************************************************************************************************

} // CommonHelpers
