let Common   = system.getScript("/driverlib/Common.js");
let Pinmux   = system.getScript("/driverlib/pinmux.js");
let CLBSignals   = system.getScript("/driverlib/clb/clbSignals.js");

let device_driverlib_peripheral = 
    system.getScript("/driverlib/device_driverlib_peripherals/" + 
        Common.getDeviceName().toLowerCase() + "_clb.js");

/* Intro splash on GUI */
let longDescription = `The Configurable Logic Block (CLB) is a collection of configurable blocks that may be inter-connected using software to implement custom digital logic functions.`

let longDescriptionCLBInput = `
The input type for the CLB can be from the local mux, the global mux, or the GPREG. For CLB type 1 and
type 2, the pipelining option which adds an additional 
flop for synchronized inputs is not included.

Note: The synchronizer will add a delay of 2-3 cycles to the input path (the exact value of the 
delay is 2 to 3 cycles, it is not predictable which delay value it will be).

![Image Missing](../../driverlib/.meta/clb/references/CLB_Input.png)
`

let longDescriptionSPI = `
Export CLB data through SPI buffer. Data is HLC R0 regiter

![Image Missing](../../driverlib/.meta/clb/references/SPI_Buffer.png)
`

var MAX_OUTPUTS = 16;
if (Common.CLB_isType1())
{
    MAX_OUTPUTS = 24;
}
else if (Common.CLB_isType2())
{
    MAX_OUTPUTS = 32;
}

var staticSwitchOptions = [
    { name: 0	, displayName: 'Zero'                   },
    { name: 8	, displayName: 'One'                    },
    { name: 24	, displayName: 'Boundary input 0'       },
    { name: 25	, displayName: 'Boundary input 1'       },
    { name: 26	, displayName: 'Boundary input 2'       },
    { name: 27	, displayName: 'Boundary input 3'       },
    { name: 28	, displayName: 'Boundary input 4'       },
    { name: 29	, displayName: 'Boundary input 5'       },
    { name: 30	, displayName: 'Boundary input 6'       },
    { name: 31	, displayName: 'Boundary input 7'       },
    { name: 7	, displayName: 'LUT 0 Output'           },
    { name: 15	, displayName: 'LUT 1 Output'           },
    { name: 23	, displayName: 'LUT 2 Output'           },
    { name: 6	, displayName: 'FSM 0 Output'           },
    { name: 4	, displayName: 'FSM 0 State 0 Value'    },
    { name: 5	, displayName: 'FSM 0 State 1 Value'    },
    { name: 14	, displayName: 'FSM 1 Output'           },
    { name: 12	, displayName: 'FSM 1 State 0 Value'    },
    { name: 13	, displayName: 'FSM 1 State 1 Value'    },
    { name: 22	, displayName: 'FSM 2 Output'           },
    { name: 20	, displayName: 'FSM 2 State 0 Value'    },
    { name: 21	, displayName: 'FSM 2 State 1 Value'    },
    { name: 2	, displayName: 'Counter 0 Equals Zero'   },
    { name: 3	, displayName: 'Counter 0 Equals Match 1' },
    { name: 1	, displayName: 'Counter 0 Equals Match 2' },
    { name: 10	, displayName: 'Counter 1 Equals Zero'   },
    { name: 11	, displayName: 'Counter 1 Equals Match 1' },
    { name: 9	, displayName: 'Counter 1 Equals Match 2' },
    { name: 18	, displayName: 'COUNTER 2 Equals Zero'   },
    { name: 19	, displayName: 'Counter 2 Equals Match 1' },
    { name: 1	, displayName: 'Counter 2 Equals Match 2' },
];

var totatCLBOutputs = [];
for (var i = 0; i < MAX_OUTPUTS; i++)
{
    totatCLBOutputs.push({
        name: "CLB_OUT" + i.toString(), displayName: "Output " + i.toString(),
    })
}

var CLB_INSTANCE = [
    { name: "CLB1_BASE", displayName: "CLB1"},
    { name: "CLB2_BASE", displayName: "CLB2"},
    { name: "CLB3_BASE", displayName: "CLB3"},
    { name: "CLB4_BASE", displayName: "CLB4"}
]

var numberOfCLBs = 4;

if (Common.getDeviceName().includes("F29H85x"))
{
    CLB_INSTANCE = [
        { name: "CLB1_BASE", displayName: "CLB1"},
        { name: "CLB2_BASE", displayName: "CLB2"},
        { name: "CLB3_BASE", displayName: "CLB3"},
        { name: "CLB4_BASE", displayName: "CLB4"},
        { name: "CLB5_BASE", displayName: "CLB5"},
        { name: "CLB6_BASE", displayName: "CLB6"}
    ]
    numberOfCLBs = 6;
}
else if (Common.getDeviceName().includes("F29H58x"))
{
    CLB_INSTANCE = [
        { name: "CLB1_BASE", displayName: "CLB1"},
        { name: "CLB2_BASE", displayName: "CLB2"},
        { name: "CLB3_BASE", displayName: "CLB3"}
    ]
    numberOfCLBs = 3;
}

let INPUT_TYPES = [
    {name: "GP", displayName: "Use Memory Mapped GPREG BIT"},
    {name: "GLOBAL", displayName: "Use Global Mux"},
    {name: "LOCAL", displayName: "Use Local Mux"}
]

function onChangeCLBBase(inst, ui){
    if (Common.CLB_isType2())
    {
    	var clb_with_spi_supprt = ["CLB1_BASE", "CLB2_BASE", "CLB3_BASE", "CLB4_BASE"];
        if (clb_with_spi_supprt.includes(inst.clbBase)){
            ui["spiBufferAccess"].hidden = false;
            ui["spiBufferLoadTriggerSignal"].hidden = false;
            ui["spiBufferR0Shift"].hidden = false;
        }
        else
        {
            ui["spiBufferAccess"].hidden = true;
            ui["spiBufferLoadTriggerSignal"].hidden = true;
            ui["spiBufferR0Shift"].hidden = true;
        }
    }
}

function onChangeInputType(inst, ui)
{
    for (var clb_input_i in device_driverlib_peripheral.CLB_Inputs)
    {
        var clbInputName = device_driverlib_peripheral.CLB_Inputs[clb_input_i].name

        if (inst.inputsUsed.includes(clbInputName))
        {
            if (inst["inputType" + clbInputName] == "GP")
            {
                ui["synchronize" + clbInputName].hidden = true;
                ui["localConfig" + clbInputName].hidden = true;
                ui["globalConfig" + clbInputName].hidden = true;
                ui["gpStartValueConfig" + clbInputName].hidden = false;
            }
            else if (inst["inputType" + clbInputName] == "GLOBAL")
            {
                ui["synchronize" + clbInputName].hidden = false;
                ui["localConfig" + clbInputName].hidden = true;
                ui["globalConfig" + clbInputName].hidden = false;
                ui["gpStartValueConfig" + clbInputName].hidden = true;
            }
            else if (inst["inputType" + clbInputName] == "LOCAL")
            {
                ui["synchronize" + clbInputName].hidden = false;
                ui["localConfig" + clbInputName].hidden = false;
                ui["globalConfig" + clbInputName].hidden = true;
                ui["gpStartValueConfig" + clbInputName].hidden = true;
            }
        }
        else
        {
            ui["synchronize" + clbInputName].hidden = true;
            ui["localConfig" + clbInputName].hidden = true;
            ui["globalConfig" + clbInputName].hidden = true;
            ui["gpStartValueConfig" + clbInputName].hidden = true;
        }

        ui["inputType" + clbInputName].hidden = !inst.inputsUsed.includes(clbInputName);
        ui["filterConfig" + clbInputName].hidden = !inst.inputsUsed.includes(clbInputName);
        if (Common.CLB_isType2()){
            ui["inputPipelineConfig" + clbInputName].hidden = !inst.inputsUsed.includes(clbInputName);
        }
    }
}


/* Array of CAN configurables that are common across device families */
let config = [
    {
        name        : "clbBase",
        displayName : "CLB Instance",
        description : 'Instance of the CLB used.',
        onChange    : onChangeCLBBase,
        hidden      : false,
        default     : CLB_INSTANCE[0].name,
        options     : CLB_INSTANCE
    },
    {
        name: "enableCLB",
        displayName : "Enable CLB",
        description : 'Enable the CLB',
        hidden      : false,
        default     : false
    },
    {
        name: "outputsToOverridePeripheral",
        displayName : "Overriding Outputs",
        description : 'Outputs from the CLB which will intersect the peripheral signals',
        hidden      : false,
        default     : [],
        minSelections : 0,
        options     : totatCLBOutputs
    },
];

if (Common.CLB_isType2())
{
    config = config.concat([
        {
            name: "lockOutputsToOverridePeripheral",
            displayName : "Lock Overriding Outputs Setting",
            description : 'Lock the setting for outputs from the CLB which will intersect the peripheral signals',
            hidden      : false,
            default     : false
        }
    ]);
}

if (Common.CLB_isType1_Type2())
{
    config = config.concat([
        {
            name: "hlcNMI",
            displayName : "HLC Generates NMI",
            description : 'The HLC interrupts are not maskable',
            hidden      : false,
            default     : false
        }
    ]);

    config = config.concat([{
        name: "GROUP_PRESCALAR",
        displayName: "Clock Prescalar",
        description: "CLB input prescalar configuration",
        longDescription: "The prescaled version of the CLB clock which can be used a CLB input",
        config: [
            {
                name: "clken",
                displayName : "Enable Prescalar",
                description : 'Enable the clock prescalar',
                hidden      : false,
                default     : false
            },
            {
                name: "strb",
                displayName : "Enable Strobe Mode",
                description : 'Enable the strobe mode. When disabled, a strobe output will be sent out whenever' + 
                              ' the prescale counter value matches the Prescale Value.' +  
                              ' When enabled, the output is the prescale counter register bit position as selected by ' +
                              'TAP Select Bit.',
                hidden      : false,
                default     : false
            },
            {
                name: "tap",
                displayName : "Tap Select Bit",
                description : 'The prescaler Tap Select Bit in Strobe Mode.',
                hidden      : false,
                default     : 0,
                options     : Common.zero_to_15
            },
            {
                name: "prescale",
                displayName : "Prescale Value",
                description : 'The prescale value for the clock.',
                hidden      : false,
                default     : 1
            }
        ]
    }]);
}

if (Common.CLB_isType2())
{
    config = config.concat([{
        name: "GROUP_SPIDATAEXPORT",
        displayName: "Data Exporting Through SPI Buffer",
        description: "",
        longDescription: longDescriptionSPI,
        config: [
            {
                name: "spiBufferAccess",
                displayName : "Enable SPI Buffer Access",
                description : 'Enable CLB data exporting through SPI buffer.',
                hidden      : false,
                default     : false
            },
            {
                name: "spiBufferLoadTriggerSignal",
                displayName : "SPI Buffer Load Trigger Signal",
                description : 'SPI buffer load trigger signal.',
                hidden      : false,
                default     : staticSwitchOptions[0].name,
                options     : staticSwitchOptions
            },
            {
                name: "spiBufferR0Shift",
                displayName : "HLC R0 Register Shift Value",
                description : 'HLC R0 register shift value.',
                hidden      : false,
                default     : Common.zero_to_16[0].name,
                options     : Common.zero_to_16
            }
        ]
    }]);
}

config = config.concat([
    {
        name: "inputsUsed",
        displayName : "Inputs Used",
        description : 'Used inputs for the CLB',
        longDescription : longDescriptionCLBInput,
        hidden      : false,
        default     : [],
        minSelections : 0,
        options     : device_driverlib_peripheral.CLB_Inputs,
        onChange    : onChangeInputType
    }
]);

for (var clb_input_i in device_driverlib_peripheral.CLB_Inputs)
{
    var clbInputName = device_driverlib_peripheral.CLB_Inputs[clb_input_i].name
    var clbInputDisplayName = device_driverlib_peripheral.CLB_Inputs[clb_input_i].displayName

    var inputTypeConfig = {
        name: "inputType" + clbInputName,
        displayName : "Input Type " + clbInputDisplayName,
        description : 'Input type for the CLB input',
        hidden      : true,
        default     : INPUT_TYPES[0].name,
        options     : INPUT_TYPES,
        onChange    : onChangeInputType
    }

    var synchronizeConfig = {
        name: "synchronize" + clbInputName,
        displayName : "Enable Sync " + clbInputDisplayName,
        description : 'Enable the CLB input synchronization',
        hidden      : true,
        default     : false
    }

    var localConfig = {
        name: "localConfig" + clbInputName,
        displayName : "Local Mux " + clbInputDisplayName,
        description : 'Local Mux input for CLB',
        hidden      : true,
        default     : device_driverlib_peripheral.CLB_LocalInputMux[0].name,
        options     : device_driverlib_peripheral.CLB_LocalInputMux
    }
    
    var globalConfig = {
        name: "globalConfig" + clbInputName,
        displayName : "Global Mux " + clbInputDisplayName,
        description : 'Global Mux input for CLB',
        hidden      : true,
        default     : device_driverlib_peripheral.CLB_GlobalInputMux[0].name,
        options     : device_driverlib_peripheral.CLB_GlobalInputMux
    }
    
    var gpStartValueConfig = {
        name: "gpStartValueConfig" + clbInputName,
        displayName : "GPREG Initial Value " + clbInputDisplayName,
        description : 'GPREG Initial Value for ' + clbInputDisplayName,
        hidden      : true,
        default     : 0,
        options     : [
            { name : 0 },
            { name : 1 }
        ]
    }

    var filterConfig = {
        name: "filterConfig" + clbInputName,
        displayName : "Input Filter " + clbInputDisplayName,
        description : 'Input filter for ' + clbInputDisplayName,
        hidden      : true,
        default     : device_driverlib_peripheral.CLB_FilterType[0].name,
        options     : device_driverlib_peripheral.CLB_FilterType
    }

    var inputConfigs = [
        inputTypeConfig,
        localConfig,
        globalConfig,
        synchronizeConfig,
        filterConfig,
        gpStartValueConfig,
    ]

    if (Common.CLB_isType2())
    {
        var inputPipelineModeConfig = {
            name: "inputPipelineConfig" + clbInputName,
            displayName : "Input Pipeline " + clbInputDisplayName,
            description : 'Enable Input Pipeline for ' + clbInputDisplayName,
            hidden      : true,
            default     : false
        }

        inputConfigs.push(inputPipelineModeConfig);
    }

    
    var clbInputGroupConfig = [{
        name: "GROUP_" + clbInputName,
        displayName: "CLB " + clbInputDisplayName,
        description: "CLB Input Configuration " + clbInputDisplayName,
        longDescription: "",
        config: inputConfigs
    }];

    config = config.concat(clbInputGroupConfig);
    
}

config = config.concat([
    {
        name        : "registerInterrupts",
        displayName : "Register Interrupt Handler",
        description : 'Whether or not to register interrupt handlers in the interrupt module.',
        hidden      : false,
        default     : false
        
    },
    {
        name        : "attachTile",
        displayName : "Attach a TILE Config to this CLB",
        description : 'Whether or not to connect one of the TILE modules to this CLB module.',
        hidden      : false,
        default     : false,
        onChange    : (inst, ui)=> {
            ui["initTile"].hidden = !inst.attachTile;
        }
    },
    {
        name        : "initTile",
        displayName : "Initialize TILE",
        description : 'Whether or not to initialize the TILE within CLB_init function.',
        hidden      : true,
        default     : false,
    },
]);

function findDuplicates(arrayToCheck)
{
    const count = arrayToCheck =>
      arrayToCheck.reduce((a, b) => ({ ...a,
        [b]: (a[b] || 0) + 1
      }), {})

    const duplicates = dict =>
      Object.keys(dict).filter((a) => dict[a] > 1)

    return {count: count(arrayToCheck), duplicates: duplicates(count(arrayToCheck))};
}

function onValidate(inst, validation) {
    var usedCLBInsts = [];
    for (var instance_index in inst.$module.$instances)
    {
        var instance_obj = inst.$module.$instances[instance_index];
        usedCLBInsts.push(instance_obj.clbBase);
    }
    var otherContexts = Common.getOtherContextNames()
    for (var cntx of otherContexts)
    {
        var onOtherCntx = Common.getModuleForCore(inst.$module.$name, cntx);
        if (onOtherCntx)
        {
            for (var instance_index in onOtherCntx.$instances)
            {
                var instance_obj = onOtherCntx.$instances[instance_index];
                usedCLBInsts.push(instance_obj.clbBase);
            }
        }
    }

    var duplicatesResult = findDuplicates(usedCLBInsts)

    if (duplicatesResult.duplicates.length != 0)
    {
        var allDuplicates = "";
        for (var duplicateNamesIndex in duplicatesResult.duplicates)
        {
            allDuplicates = allDuplicates + Common.stringOrEmpty(allDuplicates, ", ") 
                            + duplicatesResult.duplicates[duplicateNamesIndex];
        }
        validation.logError(
            "The CLB Instance used. Duplicates: " + allDuplicates, 
            inst, "clbBase");
    }

    if (inst.prescale < 0 || inst.prescale > 0xFFFF)
    {
        validation.logError(
            "The CLB prescale value must be a valid 16-bit number", 
            inst, "prescale");
    }

    var base = inst["clbBase"];
    var clb_1to4 = false;
    var clb_1 = false;
    if (["CLB1_BASE", 
        "CLB2_BASE", 
        "CLB3_BASE", 
        "CLB4_BASE", ].includes(base))
    {
        clb_1to4 = true;
    }
    if ("CLB1_BASE" == base)
    {
        clb_1 = true;
    }

    // Check global and local sources
    var i = 0;
    for (var clb_input_i in device_driverlib_peripheral.CLB_Inputs)
    {
        var clbInputName = device_driverlib_peripheral.CLB_Inputs[clb_input_i].name
        var clbSyncCheck = CLBSignals.CLBInputSignals[Common.getDeviceName()]

        if (inst.inputsUsed.includes(clbInputName))
        {
            var global_or_local = false;
            var config_pre = "";
            var enum_name = "";
            if (inst["inputType" + clbInputName] == "GLOBAL")
            {
                global_or_local = true;
                config_pre = "globalConfig";
                enum_name = "CLB_GlobalInputMux"
                clbSyncCheck = clbSyncCheck["GLOBAL_MUX"][inst["globalConfigCLB_IN" + i]];
            }
            else if (inst["inputType" + clbInputName] == "LOCAL")
            {
                global_or_local = true; 
                config_pre = "localConfig";
                enum_name = "CLB_LocalInputMux"
                clbSyncCheck = clbSyncCheck["LOCAL_MUX"][inst["localConfigCLB_IN" + i]];
            }

            //clbSyncCheck (from clbSignals.js) states whether synchronization is required
            if((clbSyncCheck == true) && (inst["synchronizeCLB_IN" + i] == false))  //When synchronization is required but not enabled, throw a warning
            {
                validation.logWarning(
                    "Synchronization is required for this signal to prevent metastability; this metastability can cause errors dependent on voltage, temperature, and wafer fab process (adds 2-3 cycle delay to input)", 
                    inst, "synchronize" + clbInputName);
            }
            else if((clbSyncCheck == false) && (inst["inputPipelineConfigCLB_IN" + i] == false))  //When input pipeline is required but not enabled, throw a warning
            {
                validation.logWarning(
                    "Pipelining is required for synchronous signals (adds 1 cycle delay to input)", 
                    inst, "inputPipelineConfig" + clbInputName);
            }
            else if((config_pre != "") && (inst["synchronizeCLB_IN" + i] == false) && (inst["inputPipelineConfigCLB_IN" + i] == false))
            {
                validation.logWarning(
                    "Having both synchronization and pipeline disabled results in a purely asynchronous signal", 
                    inst, config_pre + clbInputName);
            }
            else if((config_pre != "") && (inst["synchronizeCLB_IN" + i] == true) && (inst["inputPipelineConfigCLB_IN" + i] == true))
            {
                validation.logWarning(
                    "Having both synchronization and pipeline enabled results in a 3-4 cycle delay on the input", 
                    inst, config_pre + clbInputName);
            }

            if (global_or_local)
            {
                var result = device_driverlib_peripheral[enum_name].find(input => {
                    return input.name === inst[config_pre + clbInputName]
                })
                var diplayName = result.displayName.replace(/ /g, "")
            }
        }
        i++;
    }

    var temp_arr = [];

    for(var n = 0; n < MAX_OUTPUTS; n++)
    {
        if(inst.outputsToOverridePeripheral[n])
        {
            if(CLBSignals.CLBInputSignals[Common.getDeviceName()]["OUT_OVERRIDE"][inst.clbBase.replace("_BASE", "")][inst.outputsToOverridePeripheral[n]])
            {
                var clbOutputNum = parseInt(inst.outputsToOverridePeripheral[n].replace("CLB_OUT", ""), 10);
                var textOutput = "CLB Output " + clbOutputNum + " is overriding the " + CLBSignals.CLBInputSignals[Common.getDeviceName()]["OUT_OVERRIDE"][inst.clbBase.replace("_BASE", "")][inst.outputsToOverridePeripheral[n]] + " signal.";
                temp_arr.push(
                    {clbOutputNum, textOutput}
                );
            }
        }
    }
    
    temp_arr.sort((s1, s2) => {
        return s1.clbOutputNum - s2.clbOutputNum;
    });

    for(var n = 0; n < temp_arr.length; n++)
    {
        if(temp_arr[n].textOutput.includes("does not override"))
        {
            validation.logInfo("CLB output does not override a signal", inst, "outputsToOverridePeripheral");
        }
        else
        {
            validation.logInfo(temp_arr[n].textOutput, inst, "outputsToOverridePeripheral");
        }
    }
}

var sharedModuleInstances = undefined;

sharedModuleInstances = function (inst) {
    if (inst.attachTile)
    {
        return (
            [
                {
                    name: "tile",      
                    displayName: "TILE Configuration",
                    moduleName: "/clb_syscfg/source/TILE.syscfg.js",
                    collapsed: true,
                },
            ]
        );
    }
    return []
}

config.push(
    {
        name : "Group_Sys",
        displayName : "System Configuration",
        collapsed : true,
        config : []
    }
)

var clbModule = {
    peripheralName: "CLB",
    displayName: "CLB",
    // longDescription : longDescription,
    //longDescription: longDescription + "\n" + (Common.getCollateralFindabilityList("CLB")),
    maxInstances: numberOfCLBs,
    defaultInstanceName: "myCLB",
    description: "Configurable Logic Block",
    config: config,
    moduleInstances: (inst) => {

        var intReturn = [];
        if (inst.registerInterrupts)
        {
            intReturn.push({
                name: "clbInt",      
                displayName: "CLB Interrupt",
                moduleName: "/driverlib/interrupt.js",
                collapsed: true,
                args: {
            		$name : inst.$name + "_INT",
                    //int : "INT_" + inst.$name,
                    typeInterruptName : true,
                    pinmuxPeripheralModule : "",
                    driverlibInt: "INT_" + inst.clbBase.replace("_BASE", ""),
                    interruptHandler : "INT_" + inst.$name + "_ISR"
                }
            })
        }
        
        intReturn = intReturn.concat([
            {
                name: "periphClock",
                group: "Group_Sys",
                displayName: "",
                moduleName: "/driverlib/perClock.js",
                collapsed: false,
                requiredArgs:{
                    pinmuxPeripheralModule : "",
                    peripheralInst: inst.clbBase.replace("_BASE", "")
                }
            },
            {
                name: "periphConfig",
                group: "Group_Sys",
                displayName: "",
                moduleName: "/driverlib/perConfig.js",
                collapsed: false,
                requiredArgs:{
                    pinmuxPeripheralModule : "",
                    peripheralInst: inst.clbBase.replace("_BASE", "")
                }
            },
        ])
        
        return intReturn;
    },
    sharedModuleInstances : sharedModuleInstances,
    templates: {
        boardc : "/driverlib/clb/clb.board.c.xdt",
        boardh : "/driverlib/clb/clb.board.h.xdt"
    },
    validate    : onValidate,
};

exports = clbModule;
