# -*- encoding: utf-8 -*-
from __future__ import division
import itertools
import operator
import collections

debug = True

to_bin = lambda integer: zero_filler(bin(integer)[2:])

def zero_filler(filling_string):
    result = ""
    if len(filling_string) != 4:
        result = filling_string
    else:
        return filling_string
    while len(result) != 4:
        result = "0" + result
    return result

def prettify_table(table, table_type, report_file=None):
    out_table = {
        "values_table": [],
        "pretty_table": []
    }
    if table_type == "S1" or table_type == "S2":
        for i in range(0, 2):
            for j in range(0, 8):
                value = table[i][j]
                binary = to_bin(value)
                out_table["pretty_table"].append(binary)
                out_table["values_table"].append(value)
    elif table_type == "S3":
        arr1 = out_table; arr2 = out_table; arr = out_table
        for j in range(0, 4):
            for k in range(0, 2):
                value = table[k][j]
                binary = to_bin(value)
                arr1["pretty_table"].append(binary)
                arr1["values_table"].append(value)
            for k in range(2, 4):
                value = table[k][j]
                binary = to_bin(value)
                arr2["pretty_table"].append(binary)
                arr2["values_table"].append(value)
            arr.update(arr1); arr.update(arr2)
            out_table.update(arr)
    elif table_type == "delta_C_table":
        iteration = 0
        report_file.write("|\tInp 1\t|\tInp 2\t|\tOut 1\t|\tOut 2\t|\tDeltaC\t|\n")
        report_file.write("=============================================================\n")
        for i in table:
            if iteration == 16:
                report_file.write("=============================================================\n")
                iteration = 0
            report_file.write("|\t%s\t|\t%s\t|\t%s\t|\t%s\t|\t%s\t|\n" % (
                to_bin(i["input_1"]),
                to_bin(i["input_2"]),
                to_bin(i["output_1"]),
                to_bin(i["output_2"]),
                to_bin(i["delta_C"])
            ))
            iteration += 1
        report_file.write("=============================================================\n")

    return out_table

def gen_delta_A_tables():
    values = {
        "outputs": [],
        "inputs": []
    }
    for i in range(0, 16):
        values["outputs"].append(i)
        for j in range(0, 16):
            data = {
                "first": j,
                "second": i ^ j
            }
            values["inputs"].append(data)
    return values

def gen_delta_C_tables(S_block_table, delta_A_table):
    values = []
    for i in delta_A_table["inputs"]:
        input_data = {
            "input_1": i["first"],
            "input_2": i["second"],
        }
        output_data = {
            "output_1": S_block_table[input_data["input_1"]],
            "output_2": S_block_table[input_data["input_2"]],
        }
        delta_C_data = {
            "delta_C": output_data["output_1"] ^ output_data["output_2"]
        }
        final_dict = dict()
        final_dict.update(input_data)
        final_dict.update(output_data)
        final_dict.update(delta_C_data)
        values.append(final_dict)
    return values

def block_analysis_table(delta_C_table, t_type):
    global report

    # Объявляем переменные для этой функции
    values = {
        'table': {},        # Таблица количества значений
        'probability': {},  # Таблица вероятностей
        'max': [],          # Максимальная вероятность
        'bytes': [None for x in range(16)]  # Массив индексов, у 
                                            # которых встречается
                                            # максимальная вероятность
        }

    index = 0
    j_divider = 0

    # Устанавливаем граничное значение для заполнения таблиц
    # вероятностей и количества значений
    if t_type == "S1" or t_type == "S2":
        j_divider = 8
    elif t_type == "S3":
        j_divider = 4

    # Генерируем таблицы количества значений и вероятностей
    for i in range(0, 16):
        # Для 16ти элементов dA
        arr1 = []; arr2 = []
        for j in range(0, 16):
            # Для 16ти элементов dC
            value = delta_C_table[index]["delta_C"]
            # Заполняем построчно, пока не встретим граничное
            # значение счётчика
            if j < j_divider:
                arr1.append(value)
ValueError:
            pass
        index += 1
    report.write("\n=====================\n")
    index = 0
    arr = []
    for i in values['bytes']:
        if i != None:
            report.write("|\t%s\t|\t%s\t|\n" % (to_bin(index), to_bin(i)))
            arr.append(to_bin(index))
        index += 1
    report.write("=====================\n\n")
    values['bytes'] = arr
    return values

def input_diff_summ(delta_A_summary):
    result = []
    for i in delta_A_summary[0]:
        for j in delta_A_summary[1]:
            for k in delta_A_summary[2]:
                result.append(i + j + k)
    # print(result)
    return result

def wrapper(S_value, delta_A, report, delta_A_summary, table_count):
    table = "S%d" % table_count
    delta_C = gen_delta_C_tables(S_value, delta_A)
    report.write("\n\ndC table for %s:\n" % table_count)
    prettify_table(delta_C, "delta_C_table", report)
    result = block_analysis_table(delta_C, table)
    delta_A_summary.append(result['bytes'])
    return result

S1_table =  [[6, 3, 1, 7, 1, 4, 7, 3], [3, 2, 5, 4, 6, 7, 2, 5]]
S2_table =  [[6, 2, 3, 2, 6, 1, 3, 4], [7, 5, 4, 5, 2, 1, 7, 5]]
S3_table =  [[1, 1, 1, 2], [1, 2, 2, 1], [3, 2, 2, 3], [3, 3, 3, 1]]
P_table =   [8, 7, 3, 2, 5, 4, 1, 6]
EP_table =  [2, 5, 7, 3, 8, 6, 1, 4, 2, 6, 3, 5]
# print(gen_delta_A_tables())
report = open("report.txt", "w")
probability = open("probability.txt", "w")

pretty_S1 = prettify_table(S1_table, "S1")["pretty_table"]
S1_values = prettify_table(S1_table, "S1")["values_table"]
pretty_S2 = prettify_table(S2_table, "S2")["pretty_table"]
S2_values = prettify_table(S2_table, "S2")["values_table"]
pretty_S3 = prettify_table(S3_table, "S3")["pretty_table"]
S3_values = prettify_table(S3_table, "S3")["values_table"]

delta_A_summary = []

report.write("S1 table:\n")
for i in range(0, len(pretty_S1)):
    report.write("|\t%s\t|\t%s\t|\n" % (to_bin(i), pretty_S1[i]))
report.write("S2 table:\n")
for i in range(0, len(pretty_S2)):
    report.write("|\t%s\t|\t%s\t|\n" % (to_bin(i), pretty_S2[i]))
report.write("S3 table:\n")
for i in range(0, len(pretty_S3)):
    report.write("|\t%s\t|\t%s\t|\n" % (to_bin(i), pretty_S3[i]))

delta_A = gen_delta_A_tables()

wrapper(S1_values, delta_A, report, delta_A_summary, 1)
wrapper(S2_values, delta_A, report, delta_A_summary, 2)
result = wrapper(S3_values, delta_A, report, delta_A_summary, 3)

for i in result["probability"].values():
    probability.write("%s\n" % i)

diff = input_diff_summ(delta_A_summary)
print(len(diff))
arr = []
for i in diff:
    needed = i[1] + i[5] + i[2] + i[4]
    having = i[:4]
    if having == needed:
        arr.append(i)
        probability.write("%s " % i)
# print(arr)

probability.close()
report.close()


                arr2.append(value / 16)
            values['table'].update({i : arr1})
            values['probability'].update({i : arr2})
            index += 1
        m = max(arr2)
        values['max'].append(m)
    values['max'] = max(values['max'])
    
    if debug:
        print("Maximum is %.4f" % values['max'])
        for i in values['probability'].values():
            probability.write("%s\n" % i)

    maximum = values['max']
    index = 0
    for i in values['probability'].values():
        try:
            values['bytes'][index] = i.index(maximum)
        except 