using System;
using System.Linq; // Используется для Concat, но не в этом файле напрямую

// ПЕРЕИМЕНОВАЛИ ПРОСТРАНСТВО ИМЕН! Теперь это LabRab1
namespace LabRab1
{
    // Имя класса BlockCipher - подходящее
    public class BlockCipher
    {
        const int ROUNDS_COUNT = 12; // Количество раундов сети Фейстеля
        private readonly byte[] conversionTable = new byte[256]; // Таблица замен S-блока

        // Конструктор инициализирует таблицу при создании объекта
        public BlockCipher()
        {
            InitializeConversionTable();
        }

        // Инициализация таблицы замен (без изменений)
        private void InitializeConversionTable()
        {
            Console.WriteLine("Initializing BlockCipher conversion table (LabRab1)...");
            for (int i = 0; i < 256; i++)
            {
                // Формула остается прежней
                conversionTable[i] = (byte)(((long)i * (i + 1) >> 1) % 256);
            }
            Console.WriteLine("Conversion table initialized.");
        }

        // Разделение 64-битного блока на L и R (32 бита) (без изменений)
        private uint[] SplittingInput(ulong input)
        {
            uint[] uints = new uint[2];
            uints[0] = (uint)(input & 0xFFFFFFFFUL); // r (младшие)
            uints[1] = (uint)(input >> 32);          // l (старшие)
            return uints;
        }

        // Сборка L и R обратно в 64 бита (без изменений)
        private ulong CombineOutput(uint r, uint l)
        {
            return ((ulong)l << 32) | r; // l - старшие, r - младшие
        }

        // Метод шифрования блока (без изменений)
        public ulong Encrypt(ulong input, uint key)
        {
            uint[] parts = SplittingInput(input);
            uint r = parts[0];
            uint l = parts[1];

            for (int round = 0; round < ROUNDS_COUNT; round++)
            {
                uint fResult = F(l, key); // F(L_i-1, K_i)
                uint r_next = l;          // R_i = L_i-1
                uint l_next = r ^ fResult;// L_i = R_i-1 ^ F(L_i-1, K_i)
                r = r_next;
                l = l_next;
            }
            // После цикла l = L_n, r = R_n
            return CombineOutput(r, l); // Собираем L_n (старшие) и R_n (младшие)
        }

        // Метод расшифрования блока (без изменений)
        public ulong Decrypt(ulong input, uint key)
        {
            uint[] parts = SplittingInput(input);
            uint r = parts[0]; // R_n
            uint l = parts[1]; // L_n

            for (int round = 0; round < ROUNDS_COUNT; round++)
            {
                // Применяем F к R (т.е. к L_{i-1})
                uint fResult = F(r, key); // F(R_i, K_i) -> F(L_{i-1}, K_i)
                uint l_prev = r;          // L_{i-1} = R_i
                uint r_prev = l ^ fResult;// R_{i-1} = L_i ^ F(R_i, K_i) = L_i ^ F(L_{i-1}, K_i)
                r = r_prev;
                l = l_prev;
            }
            // После цикла l = L_0, r = R_0
            return CombineOutput(r, l); // Собираем L_0 (старшие) и R_0 (младшие)
        }

        // Раундовая функция F (без изменений, используем циклический сдвиг влево)
        private uint F(uint x, uint key)
        {
            uint temp = x;
            temp = SBlock(temp); // Подстановка
            temp += key;         // Сложение с ключом (по модулю 2^32)
            temp = (temp << 3) | (temp >> (32 - 3)); // Циклический сдвиг влево на 3
            temp = SBlock(temp); // Еще подстановка
            return temp;
        }

        // S-блок для раундовой функции (без изменений)
        private uint SBlock(uint x)
        {
            byte[] bytes = BitConverter.GetBytes(x);
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = conversionTable[bytes[i]]; // Замена каждого байта по таблице
            }
            return BitConverter.ToUInt32(bytes, 0); // Сборка обратно в uint
        }
    }
}
