using Gma.QrCodeNet.Encoding.Masking;

namespace Gma.QrCodeNet.Encoding.EncodingRegion;

/// <summary>
/// 6.9 Format information
/// The Format Information is a 15 bit sequence containing 5 data bits, with 10 error correction bits calculated using the (15, 5) BCH code.
/// </summary>
/// <remarks>ISO/IEC 18004:2000 Chapter 8.9 Page 53</remarks>
internal static class FormatInformation
{
    /// <summary>
    /// From Appendix C in JISX0510:2004 (p.65).
    /// </summary>
    private const int FormatInfoPoly = 0x537;

    /// <summary>
    /// From Appendix C in JISX0510:2004 (p.65).
    /// </summary>
    private const int FormatInfoMaskPattern = 0x5412;

    /// <summary>
    /// Embed format information to tristatematrix.
    /// Process combination of create info bits, BCH error correction bits calculation, embed towards matrix.
    /// </summary>
    /// <remarks>ISO/IEC 18004:2000 Chapter 8.9 Page 53</remarks>
    internal static void EmbedFormatInformation(this TriStateMatrix triMatrix, ErrorCorrectionLevel errorLevel, Pattern pattern)
    {
        BitList formatInfo = GetFormatInfoBits(errorLevel, pattern);
        int width = triMatrix.Width;
        for (int index = 0; index < 15; index++)
        {
            MatrixPoint point = PointForInfo1(index);
            bool bit = formatInfo[index];
            triMatrix[point.X, point.Y, MatrixStatus.NoMask] = bit;

            if (index < 7)
            {
                triMatrix[8, width - 1 - index, MatrixStatus.NoMask] = bit;
            }
            else
            {
                triMatrix[width - 8 + (index - 7), 8, MatrixStatus.NoMask] = bit;
            }
        }
    }

    private static MatrixPoint PointForInfo1(int bitsIndex)
    {
        if (bitsIndex <= 7)
        {
            return bitsIndex >= 6
                ? new MatrixPoint(bitsIndex + 1, 8)
                : new MatrixPoint(bitsIndex, 8);
        }
        else
        {
            return bitsIndex == 8
                ? new MatrixPoint(8, 8 - (bitsIndex - 7))
                : new MatrixPoint(8, 8 - (bitsIndex - 7) - 1);
        }
    }

    private static BitList GetFormatInfoBits(ErrorCorrectionLevel errorLevel, Pattern pattern)
    {
        int formatInfo = (int)pattern.MaskPatternType;

        // Pattern bits length = 3
        formatInfo |= GetErrorCorrectionIndicatorBits(errorLevel) << 3;

        int bchCode = BCHCalculator.CalculateBCH(formatInfo, FormatInfoPoly);

        // bchCode length = 10
        formatInfo = (formatInfo << 10) | bchCode;

        // xor maskPattern
        formatInfo ^= FormatInfoMaskPattern;

        BitList resultBits = new()
        {
            { formatInfo, 15 }
        };

        if (resultBits.Count != 15)
        {
            throw new Exception("FormatInfoBits length is not 15");
        }
        else
        {
            return resultBits;
        }
    }

    /// <summary>
    /// According Table 25 — Error correction level indicators
    /// Using these bits as enum values would destroy their order which currently corresponds to error correction strength.
    /// </summary>
    internal static int GetErrorCorrectionIndicatorBits(ErrorCorrectionLevel errorLevel)
    {
        // L 01
        // M 00
        // Q 11
        // H 10
        return errorLevel switch
        {
            ErrorCorrectionLevel.H => 0x02,
            ErrorCorrectionLevel.L => 0x01,
            ErrorCorrectionLevel.M => 0x00,
            ErrorCorrectionLevel.Q => 0x03,
            _ => throw new ArgumentException($"Unsupported error correction level [{errorLevel}]", nameof(errorLevel))
        };
    }
}