#include <stdio.h>
#include <math.h>
#include "hamming_gen.h"

static int count_ones(int x)
{
    int count = 0;

    while (x != 0) {
        if ((x & 1) == 1) {
            count++;
        }
        x >>= 1;
    }

    return count;
}

int get_parity_bits(int k)
{
    int r = 3;

    while (pow(2, r - 1) < k + r) {
        r++;
    }

    return r;
}

void generate_opt_matrix(int *matrix, int k, int r)
{
    //count number of information bit of extended parity/check matrix
    //width ext_n of exttended matrix is 2**r
    int ext_k = (1 << (r-1)) - r;

    int ext_int_matrix[ext_k]; // extended information part of parity-check matrix, where every column is represented by integer
    int ext_num_ones_raw[ext_k]; // number of '1' bits in every ext_int_matrix number in binary form
    int ext_used_col[ext_k]; //information about each number of ext_int_matrix, if it was already used as output matrix column

    unsigned int parity_col = 0; //
    unsigned int neg_full_parity_col = 0;

    int small_col_value = 3;
    int ext_col_value = 3;
    
    //parity column filling
        for (int i = 0; i < r; i++) {
           parity_col = (parity_col << 1) + 1;
        }

    // store full parity column bitwise negation value
    neg_full_parity_col = ~parity_col;

    //extnded used column filling
    for (int i = 0; i < ext_k; i++) {
    	ext_used_col[i] = 0;
    }


    // create extended matrix with extended columns
    for (int i = 0; i < ext_k; i++) {
        int ones = count_ones(small_col_value);
        if (ones == 1) {
            small_col_value++;
        }
		ext_col_value = (count_ones(small_col_value) & 1) ? (small_col_value << 1) : ((small_col_value << 1) + 1);

		ext_int_matrix[i] = ext_col_value;
		ext_num_ones_raw[i] = count_ones(ext_col_value);
	    small_col_value++;

	}
    

    int searched_col = 0;
    int and_ones = 0;


	for (int i = 0; i < k; i++) {

        // search correct column
		int max_and_ones = -1;
		int min_col_ones = r;
		
		for (int ext_i = 0; ext_i < ext_k; ext_i++) {
			if ((ext_used_col[ext_i] == 0) && (ext_num_ones_raw[ext_i] < min_col_ones)) {
			        min_col_ones = ext_num_ones_raw[ext_i];
                    }		
	    }
		
		for (int ext_i = 0; ext_i < ext_k; ext_i++) {
			if ((ext_used_col[ext_i] == 0) && (ext_num_ones_raw[ext_i] == min_col_ones)) {
			    and_ones = count_ones(parity_col & ext_int_matrix[ext_i]); //xor
		        if (and_ones > max_and_ones) {
		            max_and_ones = and_ones;
                    searched_col = ext_i;
		        }				
		    }
		
		}
        //write searched column into output matrix
		for (int j = 0; j < r; j++) {
			matrix[j * (r + k) + i] = (ext_int_matrix[searched_col] >> (r - 1 - j)) & 1;
        }
		        
        // store information, which column was already used
		ext_used_col[searched_col] = 1;

		// store information, which raws have more bits, then other ones
		if ((parity_col ^ (parity_col & ext_int_matrix[searched_col])) == 0) { //if searched column have 1 on all posions, where paritz col have ones
			parity_col = ~(parity_col | neg_full_parity_col); //bitwise not of column part of parity_col
		}
		parity_col = (parity_col ^ ext_int_matrix[searched_col]); //
	}
    
    //generate right site of matrix
	for (int i = k; i < k + r; i++) {
        for (int j = 0; j < r; j++) {
            matrix[j * (r + k) + i] = (i - k == j);
        }
    }

}


void generate_matrix(int *matrix, int k, int r)
{
    int col_value = 3;

    for (int i = 0; i < k; i++) {
        int ones = count_ones(col_value);
        if (ones == 1) {
            col_value++;
        }
        for (int j = 0; j < r - 1; j++) {
            matrix[j * (r + k) + i] = col_value >> (r - 2 - j) & 1;
        }
        matrix[(r - 1) * (r + k) + i] = !(ones & 1);
        col_value++;
    }

    for (int i = k; i < k + r; i++) {
        for (int j = 0; j < r; j++) {
            matrix[j * (r + k) + i] = (i - k == j);
        }
    }
}

int write_vhdl_pkg(const char *prefix, int bfm, const int *matrix, int k, int r)
{
    const char *bfm_prefix = bfm ? "bfm_" : "";
    char path[512];
    
    if (bfm == 0) {
        sprintf(path, "../../hdl/rtl/%shamm_rom_pkg.vhd", prefix == NULL ? "" : prefix);
    } else {
        sprintf(path, "../../hdl/support/%s%shamm_rom_pkg.vhd", prefix == NULL ? "" : prefix, bfm_prefix);
    }

    FILE *file = fopen(path, "w");
    

    if (file == NULL) {
        return -1;
    }

    fprintf(
        file,
        "-- ** 18b EDAC Package **\n" 
        "--\n"
        "-- Description:     A package for the Extended Hamming Code for single-error\n"
        "--                  correction and double-error detection (SEC-DED)\n"
        "--  \n"
        "-- Project:         UMEL_ŠB\n"
        "-- File name:       edac_18b_pkg.vhd\n"
        "-- Creation date:   May 2, 2016\n"
        "--\n"
        "-- Institution:     Brno University of Technology, FEEC UMEL\n"
        "-- Author:          Martin Stava\n"
        "-- Author's e-mail: stavam1@feec.vutbr.cz\n"
        "--\n"
        "-- Modified by:              Jan Bares\n"
        "-- Modification Date:        June 1, 2016\n"
        "-- Modification File name:   %shamm_rom_pkg.vhd\n"
        "----------------------------------------------------------------------------------------\n"
        "\n"
        "library ieee;\n"
        "use ieee.std_logic_1164.all;\n\n\n"
        "package %shamm_rom_pkg is\n\n"
        "  constant c_INF_DATA_WIDTH : natural := %d;\n"
        "  constant c_CHECK_DATA_WIDTH : natural := %d;\n"
        "  constant c_ENCODED_DATA_WIDTH : natural := c_INF_DATA_WIDTH + c_CHECK_DATA_WIDTH;\n\n"
        "  subtype raw_vector is std_logic_vector (c_ENCODED_DATA_WIDTH-1 downto 0);\n"
        "  type h_ext_type is array (c_CHECK_DATA_WIDTH-1 downto 0) of raw_vector;\n\n"
        "  constant c_H_EXT : h_ext_type := (\n",
        bfm_prefix, bfm_prefix, k, r
    );

    for (int j = 0; j < r; j++) {
        fprintf(file, "    \"");
        for (int i = 0; i < r + k; i++) {
            fprintf(file, "%d", matrix[j * (r + k) + i]);
        }
        fprintf(file, "\"%s\n", j < r - 1 ? "," : "");
    }

    fprintf(
        file,
        "  );\n\n"
        "end %shamm_rom_pkg;\n\n"
        "package body %shamm_rom_pkg is\n"
        "end package body %shamm_rom_pkg;\n",
        bfm_prefix, bfm_prefix, bfm_prefix
    );

    fclose(file);
    return 0;
}

int write_sim_script(int k, int r)
{
	
    FILE *file = fopen("../../verification/simulation/sim_script", "w");


    if (file == NULL) {
        return -1;
    }

    fprintf(
        file,
        "vlib work\n"
        "vmap work work\n"
        "\n"
        "\n"
        "vcom -work work ../../hdl/support/timing_pkg.vhd -vmake \n"
        "vcom -work work ../../hdl/support/messages_pkg.vhd -vmake\n"
        "vcom -work work ../../hdl/support/bfm_hamm_rom_pkg.vhd -vmake\n"
        "vcom -work work ../../hdl/support/coder_pkg.vhd -vmake\n"
        "\n"
        "vcom -work work ../../hdl/rtl/hamm_rom_pkg.vhd -vmake\n"
        "\n"
        "vcom -work work ../../hdl/rtl/coder.rtl.vhd -vmake \n"
        "vcom -work work ../../hdl/rtl/decoder.rtl.vhd -vmake\n"
        "\n"
        "vcom -work work ../../hdl/model/tb_pkg.vhd -vmake\n"
        "vcom -work work ../../hdl/model/rom_pkg.vhd -vmake\n"
        "vcom -work work ../../hdl/model/rom_master_pkg.vhd -vmake\n"
        "vcom -work work ../../hdl/model/osc.beh.vhd -vmake\n"
        "vcom -work work ../../hdl/model/rom.beh.vhd -vmake\n"
        "vcom -work work ../../hdl/model/rom_master.beh.vhd -vmake\n"
        "vcom -work work ../../hdl/model/tc.vhd -vmake\n"
        "vcom -work work ../../hdl/model/tb.vhd\n"
        "\n"
        "vcom -work work ../../hdl/testcase/tc_coder.vhd -vmake\n"
        "vsim -novopt -t ps -batch -logfile tc_coder_%db_transcript work.tb\n"
        "\n"
        "run -all\n"
        "\n"
        "vcom -work work ../../hdl/testcase/tc_decoder.vhd -vmake\n"
        "vsim -novopt -t ps -batch -logfile tc_decoder_%db_transcript work.tb\n"
        "\n"
        "run -all\n",
	k + r, k + r
    );

    fclose(file);
    return 0;
}