/**

* @
*/

$vignette = (function () {

// Set up variables.

// Data model
var _all        = [];
var _vignettes  = [];
var _targets    = [];
var _diffs      = [];
var _targetsHit = [];
var _index      = 0;
var _tableData  = [];
var _cUrl       = '/competencies';
var _markTable  = false; 

// UI elements
var _blocks     = [];
var _tabs       = [];
var _next       = {};
var _previous   = {};
var _table      = undefined;

/**
 * Initialize mini-library
 * (bootstrap variables).
 * 
 * @returns Exposed functions for UI to call
 */
function initialize() {
    // Presume access to page variables.
    _vignettes = window['vignette_vignettes'];
    _targets   = window['vignette_targets'];
    _cUrl      = window['vignette_c_url'];
    _markTable = window['vignette_mark_table'];

    // Defensive checking.
    if(!_vignettes) {
        throw "Necessary data not found in page";
        return;
    }

    // Compute all competencies.
    _all = _combine();

    for(var i = 0; i < _vignettes.length; i++) {
        // Compute differences between vignettes ahead of time.
        _diffs[i] = _diff(_vignettes[i - 1], _vignettes[i]);
        // Compute targets hit per vignette ahead of time.
        _targetsHit[i] = _target(_vignettes[i]);
        // Compute table data ahead of time.
        _tableData[i] = _tableify(i);
    }

    // Access DOM elements.
    registerUI();

    // Set table data.
    _updateTable(0);

    // Move to latest view.
    _updateVignette(_vignettes.length - 1);

    // Expose functions.
    return {
        dump: _dump,
        combine: _combine,
        updateVignette: _updateVignette,
        nextVignette: _nextVignette,
        previousVignette: _previousVignette,
        sort: _sortCurrentVignette
    };
}

/**
 * Retrieve DOM elements
 * for manipulation.
 */
function registerUI() {
    // Get omnipresent pagination components.
    _previous = document.getElementById('tab-previous');
    _next = document.getElementById('tab-next');

    // Get all dynamic elements.
    for(var i = 0; i < _vignettes.length; i++) {
        // Get tab and content block elements.
        _blocks.push(document.getElementById('block-' + i.toString()));
        _tabs.push(document.getElementById('tab-' + i.toString()));
    }

    // Get table.
    _table = document.getElementById('table');
}

// Define UI manipulation functions.

/**
 *
 *
 * @param {*} index
 * @returns
 */
function _updateVignette(index, override = false) {
    // Redundancy check.
    if(_index === index && !override) {
        return;
    }

    let activeClass = 'active';

    // Update previous tab element.
    _tabs[_index].classList.remove(activeClass);

    // Update previous block element.
    _blocks[_index].classList.remove(activeClass);

    if(index === 0) {
        _previous.classList.add('disabled');
    } else {
        _previous.classList.remove('disabled');
    }

    if(index === _vignettes.length - 1) {
        _next.classList.add('disabled');
    } else {
        _next.classList.remove('disabled');
    }

    // Update data model.
    _index = index;

    // Update tab element.
    _tabs[_index].classList.add(activeClass);

    // Update block element.
    _blocks[_index].classList.add(activeClass);

    // Update competency table.
    _updateTable(index);
}

function _nextVignette() {
    if(_index != _vignettes.length - 1) {
        _updateVignette(_index + 1);
    }
}

function _previousVignette() {
    if(_index != 0) {
        _updateVignette(_index - 1);
    }
}

/**
 * Update the skill table based
 * on the current vignette.
 *
 * @param {*} index The index of the current vignette.
 */
function _updateTable(index) {    
    // Remove previous rows (if any).
    _clearTable(_table);

    _table.innerHTML = _tableData[index];
}

function _sortTable() {
    // Perform sort on current table using key (column).
}

// Define utility functions.

/**
 * Output all notable variables.
 *
 * @returns An object containing variable contents
 */
function _dump(index) {
    if(index) {
        return {
            vignette:   _vignettes[index],
            targets:    _targets,
            diff:       _diffs[index],
            targetsHit: _targetsHit[index]
        };
    } else {
        return {
            vignettes:  _vignettes,
            targets:    _targets,
            diffs:      _diffs,
            targetsHit: _targetsHit
        };
    }
}

function _sortCurrentVignette(sortKey, ascending) {
    // Sort the table.
    for(var i = 0; i < _vignettes.length; i++) {
        // Recompute table data ahead of time.
        _tableData[i] = _tableify(i, sortKey, ascending);
    }

    // Update table HTML.
    _updateVignette(_index, true);
}

function _tableify(index, sortKey = 'competencyId', ascending = true) {
    // Build HTML for all unique skills...
    var tableRowsData = [];
    var tableRowsHTML = [];

    for(let competency of _all) {
        // Build table row data.
        var tableRowData = {
            competencyId: competency,
            linked: (linked => {
                for(let v of _vignettes) {
                    for(let c of v['competencies']) {
                        if(c['id'] === competency) {
                            return c['linked'];
                        }
                    }
                }
                return false;
            })(),
            target: _targets.indexOf(competency) != -1,
            count: (total => {
                for(let c of _vignettes[index]['competencies']) {
                    if(c['id'] === competency) {
                        return c['count'];
                    }
                }
                return 0;
            })(),
            removed: Math.abs(_indexDiffWithType(competency, index, 'removed')),
            unchanged: _indexDiffWithType(competency, index, 'unchanged'),
            added: _indexDiffWithType(competency, index, 'added')
        };
        tableRowsData.push(tableRowData);
    }

    // Sort rows by Id (default).
    tableRowsData.sort(function(a, b){
        var nameA = a[sortKey], nameB = b[sortKey];
        if(nameA < nameB) {
            if(ascending) {
                return -1;
            }
            else {
                return 1;
            }
        }
        if(nameA > nameB) {
            if(ascending) {
                return 1;
            } else {
                return -1;
            }
        }
        return 0;
    });

    for(let tableRowData of tableRowsData) {
        // Convert table row data into HTML.
        tableRowsHTML.push(_tableRowDataToHTML(tableRowData));
    }

    // Create new table.
    var headerRow = '<tr id="table-header"><th>Competency ';
    if(sortKey === 'competencyId') {
        if(ascending) {
            headerRow += '<i class="fas fa-sort-down" onclick="javascript:$vignette.sort(\'competencyId\', false)"></i>';
        } else {
            headerRow += '<i class="fas fa-sort-up" onclick="javascript:$vignette.sort(\'competencyId\', true)"></i>'
        }
    } else {
        headerRow += '<i class="fas fa-sort" onclick="javascript:$vignette.sort(\'competencyId\', false)"></i>';
    }
    headerRow += '</th>';

    if(_targets) {
        headerRow += '<th>Target ';
        if(sortKey === 'target') {
            if(ascending) {
                headerRow += '<i class="fas fa-sort-down" onclick="javascript:$vignette.sort(\'target\', false)"></i>';
            } else {
                headerRow += '<i class="fas fa-sort-up" onclick="javascript:$vignette.sort(\'target\', true)"></i>'
            }
        } else {
            headerRow += '<i class="fas fa-sort" onclick="javascript:$vignette.sort(\'target\', false)"></i>';
        }
        headerRow += '</th>';
    }

    headerRow +=      '<th>Count ';
    if(sortKey === 'count') {
        if(ascending) {
            headerRow += '<i class="fas fa-sort-down" onclick="javascript:$vignette.sort(\'count\', false)"></i>';
        } else {
            headerRow += '<i class="fas fa-sort-up" onclick="javascript:$vignette.sort(\'count\', true)"></i>'
        }
    } else {
        headerRow += '<i class="fas fa-sort" onclick="javascript:$vignette.sort(\'count\', false)"></i>';
    }
    headerRow += '</th>';

    headerRow += '<th>Removed ';
    if(sortKey === 'removed') {
        if(ascending) {
            headerRow += '<i class="fas fa-sort-down" onclick="javascript:$vignette.sort(\'removed\', false)"></i>';
        } else {
            headerRow += '<i class="fas fa-sort-up" onclick="javascript:$vignette.sort(\'removed\', true)"></i>'
        }
    } else {
        headerRow += '<i class="fas fa-sort" onclick="javascript:$vignette.sort(\'removed\', false)"></i>';
    }
    headerRow += '</th>';

    headerRow += '<th>Unchanged ';
    if(sortKey === 'unchanged') {
        if(ascending) {
            headerRow += '<i class="fas fa-sort-down" onclick="javascript:$vignette.sort(\'unchanged\', false)"></i>';
        } else {
            headerRow += '<i class="fas fa-sort-up" onclick="javascript:$vignette.sort(\'unchanged\', true)"></i>'
        }
    } else {
        headerRow += '<i class="fas fa-sort" onclick="javascript:$vignette.sort(\'unchanged\', false)"></i>';
    }
    headerRow += '</th>';

    headerRow += '<th>Added ';
    if(sortKey === 'added') {
        if(ascending) {
            headerRow += '<i class="fas fa-sort-down" onclick="javascript:$vignette.sort(\'added\', false)"></i>';
        } else {
            headerRow += '<i class="fas fa-sort-up" onclick="javascript:$vignette.sort(\'added\', true)"></i>'
        }
    } else {
        headerRow += '<i class="fas fa-sort" onclick="javascript:$vignette.sort(\'added\', false)"></i>';
    }
    headerRow += '</th></tr>';

    var finalHTML = headerRow;

    for(let row of tableRowsHTML) {
        finalHTML += row;
    }

    return finalHTML;
}

/**
 * Shortcut method to get count of a competency
 * within a category of change (if existent).
 *
 * @param {*} competency The competency Id
 * @param {*} index      The vignette iteration
 * @param {*} type       The type of change
 * @returns              The total occurences
 */
function _indexDiffWithType(competency, index, type) {
    for(let c of _diffs[index][type]) {
        if(c['id'] === competency) {
            return c['countChange'];
        }
    }
    return 0;
}

/**
 * Convert table row data to raw HTML.
 *
 * @param {*} tableRowData HTML to inject.
 */
function _tableRowDataToHTML(tableRowData) {
    var encoded =   '<tr';

    if(_markTable && tableRowData.target && tableRowData.count > 0) {
        encoded += ' class="table-success"';
    }

    encoded += '><td>';
    if(tableRowData['linked']) {
        encoded += '<a href="' +
                    _cUrl +
                    '#' +
                    tableRowData['competencyId'] +
                    '">' +
                    tableRowData['competencyId'] +
                    '</a></td><td>';
    } else {
        encoded += '<span>' +
                    tableRowData['competencyId'] +
                    '</span></td><td>';
    }
    if(_targets) {
        if(tableRowData['target']) {
            encoded += 'Yes';
        } else {
            encoded += 'No';
        }
        encoded += '</td><td>';
    }
    encoded +=  tableRowData['count'] +
                '</td><td';
    if(_markTable && tableRowData['removed'] < 0) {
        encoded += ' class="table-danger"';
    }
    encoded +=  '>' +
                tableRowData['removed'] +
                '</td><td>' +
                tableRowData['unchanged'] +
                '</td><td';
    if(_markTable && tableRowData['added'] > 0) {
        encoded += ' class="table-success"';
    }
    encoded +=  '>' +
                tableRowData['added'] +
                '</td></tr>';
    return encoded;
}

/**
 * Remove contents of table.
 *
 * @param {*} table The table DOM element
 */
function _clearTable(table) {
    table.innerHTML = '';
}

/**
 * Return an array of all competencies
 * mentioned in any vignette or targets.
 * 
 * @returns Array of unique competency Ids
 */
function _combine() {
    allCompetencies = []

    // Add all from vignettes.
    for(let vignette of _vignettes) {
        for(let competency of vignette['competencies']) {
            allCompetencies.push(competency['id']);
        }
    }

    // Add all from targets.
    allCompetencies = allCompetencies.concat(_targets);

    // De-duplicate.
    return _uniqueBy(allCompetencies, k => k);
}

/**
 * Compare differences in competencies reported in vignettes.
 *
 * @param {*} v1 Previous vignette
 * @param {*} v2 New vignette
 * @returns      An object containing change information
 */
function _diff(v1, v2) {
    // Hoist output declarations.
    var differences = [], removed = [], unchanged = [], added = [];

    // If it is the first vignette...
    if(!v1) {
        // All competencies are added, so get a list of them.
        for(let competency of v2['competencies']) {
            var c = { 'id': competency['id'], 'countChange': competency['count'] };
            differences.push(c);
            added.push(c);
        }
    } else {
        // Create an index of all competencies in both vignettes.
        differences = _uniqueBy(
            v1['competencies'].concat(v2['competencies']),
            k => k['id']
        ).map(c => {
            return { 'id': c['id'], 'countChange': 0 };
        });

        // For every competency, check its state before and after.
        for(let competency of differences) {
            // Find out how many were in the both vignettes.
            var numInV1 = 0, numInV2 = 0;

            for(let c1 of v1['competencies']) {
                if(c1['id'] === competency['id']) {
                    numInV1 = c1['count'];
                    break;
                }
            }

            for(let c2 of v2['competencies']) {
                if(c2['id'] === competency['id']) {
                    numInV2 = c2['count'];
                    break;
                }
            }

            // Compute the change.
            competency['countChange'] = numInV2 - numInV1;
        }

        // Perform extra formatting for view.
        for(let competency of differences) {
            if(competency['countChange'] < 0) {
                removed.push(competency);
            } else if(competency['countChange'] > 0) {
                added.push(competency);
            } else {
                unchanged.push(competency);
            }
        }
    }

    return {
        differences: differences,
        removed: removed,
        unchanged: unchanged,
        added: added
    };
}

/**
 * Find which target Ids have been
 * included in a vignette.
 *
 * @param {*} vignette  The vignette to check
 * @returns             Targets that have been hit
 */
function _target(vignette) {
    // Defensive sanity check.
    if(!_targets) {
        console.log('No targets defined for this project.');
        return;
    }

    targetsHit = [];

    // For every target Id...
    for(let target of _targets) {
        // Construct the outcome.
        var outcome = {
            'id': target,
            'hit': false
        };

        // Search for it in the vignette.
        for(let competency of vignette['competencies']) {
            outcome['hit'] |= target === competency['id'];
            if(outcome['hit']) {
                break;
            }
        }

        targetsHit.push(outcome);
    }

    return targetsHit;
}

/**
 * De-duplicate an array by an object property.
 *
 * @param {*} a     Array
 * @param {*} key   Callback returning object property
 * @returns         De-duplicated array
 */
function _uniqueBy(a, key) {
    var seen = {};
    return a.filter(function(item) {
        var k = key(item);
        return seen.hasOwnProperty(k) ? false : (seen[k] = true);
    })
}

return initialize();

})();