using System;
using System.IO;
using System.Text;
using System.Globalization;

using CommonHelpers;

using BookRegistry.Server.DB;

namespace BookRegistry.Server {

//**************************************************************************************************
/// <summary>
/// User password simple masking and de-masking
/// THIS APPROACH IS NOT FOR REAL APPLICATION.
/// HASH ALGORITH OR STRONG ENCRYPTION MUST BE USED THERE INSTEAD OF PASSWORD MASKING.
/// </summary>
/// <remarks>
/// Password masking is used only for general user (not for service administrator).
/// </remarks>
static class PasswordMasking {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// <exception cref="InvalidOperationException" />
public static byte[] UserPasswordMask =>
   _UserPasswordMask_ ?? throw new InvalidOperationException(
      "User password mask has not been initialized (\"{PasswordMasking}\").");

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

/// <exception cref="InvalidOperationException" />
/// <exception cref="OperationFailedException" />
public static void Prepare()
{
   string userPasswordMask = StartupData.UserPasswordMask;
   try {
      DoPrepare( userPasswordMask );
   } catch (Exception e) {
      throw StartupData.ConfigParameterError( nameof(StartupData.UserPasswordMask), e );
   }
}
/// <exception cref="InvalidOperationException" />
static void DoPrepare(string configMaskStr)
{
   int
      binMaskLen = DBConstants.UserPasswordByteCapacity,
      strMaskLen = binMaskLen * 2;
   if ( !configMaskStr.StartsWith("0x") )
      throw new HUnsuccessException( "User password mask must start with \"0x\"-prefix." );
   configMaskStr = configMaskStr.Substring( 2 );
   if ( configMaskStr.Length != strMaskLen ||
        configMaskStr.Length != DBConstants.UserPasswordMaxLength * 4 )
      throw new HUnsuccessException( "Incorrect user password mask length of string." );
   using (var stream = new MemoryStream())
   {
      for (int i=0; i<strMaskLen; i+=2)
      {
         string byteCodeStr = configMaskStr.Substring( i, 2 );
         if ( !byte.TryParse( byteCodeStr, NumberStyles.HexNumber,
                              NumberFormatInfo.InvariantInfo, out var @byte ) )
            throw new HUnsuccessException( "Incorrect character(s) in user password mask." );
         stream.WriteByte( @byte );
      }
      byte[] binaryMask = stream.ToArray();
      if ( binaryMask.Length != binMaskLen )
         throw HAssertionFailedExceptionCreator.Create(
            "User password binary mask length {0} differs from expected value {1}.",
            binaryMask.Length,  binMaskLen );
      _UserPasswordMask_ = binaryMask;
   }
}

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

/// <exception cref="InvalidOperationException" />
public static byte[]? MaskUserPassword(string password)
{
   int len = password.Length;
   if ( len > DBConstants.UserPasswordMaxLength )
      return null;
   byte[] binMask = UserPasswordMask;
   using (var stream = new MemoryStream())
   {
      for (int i=0; i<password.Length; i++)
      {
         int charCode = password[ i ];
         for (int j=0; j<2; j++)
         {
            int sh = ((1-j)*8);
            int code = (charCode >> sh) ^ binMask[i*2+j];
            stream.WriteByte( (byte)code );
         }
      }
      return stream.ToArray();
   }
}

/// <exception cref="InvalidOperationException" />
public static string? UnmaskUserPassword(byte[] passwordData)
{
   int dataLen = passwordData.Length;
   if ( dataLen > DBConstants.UserPasswordByteCapacity || HArith.IsOdd( dataLen ) )
      return null;
   byte[] binMask = UserPasswordMask;
   var sb = new StringBuilder();
   for (int i=0; i<dataLen; i+=2)
   {
      int charCode = 0;
      for (int j=0; j<2; j++)
         charCode |= ((int)passwordData[i+j] ^ (int)binMask[i+j]) << ((1-j)*8);
      sb.Append( (char)charCode );
   }
   return sb.ToString();
}

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

static byte[]? _UserPasswordMask_;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // PasswordMasking
//**************************************************************************************************

} // BookRegistry.Server
