﻿#include <cstdio>
#include <intrin.h>
#include <array>
#include <climits>
#include <ctime>
#include <thread>
#include <Windows.h>
#include <string>
#include <sstream>
#include <bitset>

#include "FeistelNetwork.h"

#define INPUT_COUNT 12

namespace {
    void Divide64To16(uint64_t value, uint16_t segments[4]) {
        segments[0] = static_cast<uint16_t>(value);
        segments[1] = static_cast<uint16_t>(value >> 16);
        segments[2] = static_cast<uint16_t>(value >> 32);
        segments[3] = static_cast<uint16_t>(value >> 48);
    }

    void Process16Bits(uint16_t data, std::array<std::array<uint8_t, 4>, 4>& matrix, int col) {
        matrix[col][0] = data & 0xF;
        matrix[col][1] = (data >> 4) & 0xF;
        matrix[col][2] = (data >> 8) & 0xF;
        matrix[col][3] = (data >> 12) & 0xF;
    }

    // Замена 4 бит на значение из таблицы 
    void SBlock(uint8_t val, std::array<std::array<uint8_t, 4>, 4>& output,
        const std::array<uint32_t, 16>& table, int col, int row) {
        output[col][row] = table[val & 0xF];
    }

    uint16_t CombineBits(const std::array<std::array<uint8_t, 4>, 4>& data) {
        return (data[3][0] << 12) | (data[3][1] << 8) |
            (data[3][2] << 4) | data[3][3];
    }

    std::string ToBinary(uint64_t num) {
        std::bitset<64> bits(num);
        return bits.to_string();
    }

    std::string CreateBinaryString(const std::vector<uint16_t>& values) {
        std::array<uint64_t, 3> chunks{};

        for (int i = 0; i < values.size(); i++) {
            if (i < 4) chunks[0] = (chunks[0] << 16) | values[i];
            else if (i < 8) chunks[1] = (chunks[1] << 16) | values[i];
            else chunks[2] = (chunks[2] << 16) | values[i];
        }

        return ToBinary(chunks[0]) + ToBinary(chunks[1]) + ToBinary(chunks[2]);
    }
}

int main() {
    SetConsoleCP(1251);
    SetConsoleOutputCP(1251);

    std::cout << "Press any key to begin data collection...\n";
    std::cin.ignore();

    std::vector<uint64_t> timeDeltas;
    timeDeltas.reserve(INPUT_COUNT);

    auto prevTime = __rdtsc();
    char prevKey = 0;

    std::cout << "Enter characters (consecutive duplicates ignored):\n";

    while (timeDeltas.size() < INPUT_COUNT) {
        const char currentKey = std::cin.get();

        if (currentKey == '\n') continue;
        if (currentKey == prevKey) continue;

        const auto currentTime = __rdtsc();
        timeDeltas.push_back(currentTime - prevTime);

        prevTime = currentTime;
        prevKey = currentKey;

        std::cout << "Progress: " << timeDeltas.size() << "/" << INPUT_COUNT << "\n";
    }

    std::vector<uint16_t> processedValues(INPUT_COUNT);
    const std::array<uint32_t, 16> substitutionTable = {
        5, 11, 6, 8, 3, 13, 9, 7, 10, 4, 12, 2, 15, 1, 0, 14
    };

    for (int i = 0; i < timeDeltas.size(); i++) {
        uint16_t segments[4];
        Divide64To16(timeDeltas[i], segments);

        std::array<std::array<uint8_t, 4>, 4> bitMatrix{};

        for (int j = 0; j < 4; j++) {
            Process16Bits(segments[j], bitMatrix, j);
        }

        std::array<std::array<uint8_t, 4>, 4> transformedMatrix{};

        for (int col = 0; col < 4; col++) {
            for (int row = 0; row < 4; row++) {
                const uint8_t mixValue = (col == 0) ? 0 : transformedMatrix[col - 1][row];
                SBlock(mixValue ^ bitMatrix[col][row], transformedMatrix,
                    substitutionTable, col, row);
            }
        }

        processedValues[i] = CombineBits(transformedMatrix);
    }

    const std::string binaryKey = CreateBinaryString(processedValues);
    std::cout << "\nResult A: " << binaryKey << "\n\n";

    std::stringstream binaryStream;
    for (const auto& val : timeDeltas) {
        binaryStream << ToBinary(val);
    }

    const auto hashResult = FeistelNetwork::ComputeHash(binaryStream.str());
    std::cout << "Result B: " << FeistelNetwork::CalculateSum(hashResult) << "\n\n";
    FeistelNetwork::PrintHash(hashResult);

    system("pause");
    return 0;
}