(function () {
    'use strict';
    angular.module('uniflash').directive('ufMemoryBrowser', memoryBrowser);
    memoryBrowser.$inject = ['$window', '$rootScope', '$log'];
    function memoryBrowser($window, $rootScope, $log) {
        var directive = {
            restrict: 'E',
            controller: MemoryBrowser,
            controllerAs: 'vm',
            templateUrl: 'memory/memory_browser.template.html',
            scope: {},
            link: function (scope, ele, attr, ctrl) {
                var browserWindowEle = angular.element($window);
                var browserWindow = browserWindowEle[0];
                browserWindowEle.bind('resize', function () {
                    var viewHeight = browserWindow['innerHeight'] / 2;
                    ctrl.viewHeight = viewHeight;
                    $log.debug('window resized, viewHeight = ' + viewHeight);
                });
                scope.$on('$destroy', function () {
                    browserWindowEle.unbind('resize');
                });
                var consoleViewEle = angular.element(document.getElementById('console-view'));
                scope.$on('$destroy', $rootScope.$on('ds.opStarted', function () {
                    ctrl.dsOpInProgress = true;
                }));
                scope.$on('$destroy', $rootScope.$on('ds.opFinished', function () {
                    ctrl.dsOpInProgress = false;
                }));
            }
        };
        return directive;
    }
    MemoryBrowser.$inject = ['$uibModal', '$q', '$scope', '$rootScope', '$window', '$log',
        'ufDS', 'ufDsProgressDialog', 'ufConsole', 'ufContext', 'ticFileDialogs', 'ticExecutionPlatform', 'ticAnalyticsSrv', 'ufMemoryRanges'];
    function MemoryBrowser($uibModal, $q, $scope, $rootScope, $window, $log, ufDS, ufDsProgressDialog, ufConsole, ufContext, ticFileDialogs, ticExecutionPlatform, ticAnalyticsSrv, ufMemoryRanges) {
        var vm = this;
        var coreName = ufContext.getCurrentCore().name;
        //TODO: grab these from some service that knows the target
        var isC28 = coreName.match(/.*c28.*/i);
        var is430 = coreName.match(/.*msp430.*/i);
        var currentValTypeLength = isC28 ? 2 : 4;
        var currentMAUSize = isC28 ? 2 : 1;
        var currentWordSize = (isC28 || is430) ? 2 : 4;
        var currentCellSize = currentValTypeLength / currentMAUSize;
        var newAddr;
        var dsConfigObj;
        vm.format = 'TISTYLEHEX';
        vm.valType = 'INT'; //'CHAR'; //should be inferred
        vm.bufferCount = 150; //rows to read before the target addr 
        vm.cols = 16; // default to max num of cols
        vm.rows = [];
        vm.isInit = false;
        vm.showInitOverlay = true;
        vm.viewHeight = 450;
        vm.curPage = '';
        vm.ufMemoryRanges = ufMemoryRanges;
        vm.selectedMemoryRange = "0";
        vm.updateTableWithPageInfo = function (pageInfo) {
            currentValTypeLength = pageInfo.wordSize / 8;
            currentMAUSize = pageInfo.mauSize / 8;
            currentCellSize = currentValTypeLength / currentMAUSize;
            $log.debug('currentValTypeLength = ' + currentValTypeLength +
                ' currentMAUSize = ' + currentMAUSize +
                ' currentCellSize = ' + currentCellSize);
        };
        function getPageInfo(page) {
            var ret;
            angular.forEach(vm.pageInfoList, function (pageInfo) {
                if (pageInfo.name === page) {
                    ret = pageInfo;
                }
            });
            return ret;
        }
        vm.init = function () {
            ticAnalyticsSrv.record('InitMemoryBrowser');
            var initializedDeferred = $q.defer();
            vm.initializing = initializedDeferred.promise;
            vm.showInitOverlay = false;
            //just running an empty operation will do nothing, but will setup the dialog handlers and console handlers in the process.
            ufDsProgressDialog.configureDS();
            vm.core = ufDS.getDSCore(coreName);
            vm.core.getTargetInfo()
                .then(function (ret) {
                $log.debug('InitMemoryBrowser ' + JSON.stringify(ret));
                vm.pageInfoList = ret.pageInfoList;
                angular.forEach(vm.pageInfoList, function (pageInfo) {
                    if (pageInfo.isDefault) {
                        vm.curPage = pageInfo.name;
                        vm.updateTableWithPageInfo(getPageInfo(vm.curPage));
                    }
                });
            })
                .then(function () {
                return vm.goToMem('0x0');
            })
                .then(function () {
                var cellEle = angular.element(document.getElementsByClassName(vm.cellClass));
                vm.cellEleWidth = cellEle ? cellEle[0].clientWidth : null;
                vm.isInit = true;
                initializedDeferred.resolve();
            })
                .catch(function () {
                vm.isInit = false;
                vm.showInitOverlay = true;
                initializedDeferred.reject();
            });
            vm.initializing.then(function () {
                updateTableDimensions();
            });
        };
        //width control the number of columns
        vm.memoryBrowserId = 'memory-browser';
        vm.cellClass = 'memory-browser-cell';
        function updateTableDimensions() {
            var memoryBrowserEle = angular.element(document.getElementById(vm.memoryBrowserId));
            var memoryBrowserEleWidth = (memoryBrowserEle
                && memoryBrowserEle[0]
                && memoryBrowserEle[0].clientWidth) ? memoryBrowserEle[0].clientWidth : null;
            if (vm.cellEleWidth && memoryBrowserEleWidth) {
                var bestFitCols = Math.floor(memoryBrowserEleWidth / vm.cellEleWidth); // - 1; //minus the address col
                if (bestFitCols >= 16) {
                    reRenderTable(16);
                }
                else if (bestFitCols > 8 && bestFitCols < 16) {
                    reRenderTable(8);
                }
                else if (bestFitCols <= 8) {
                    reRenderTable(4);
                }
            }
        }
        ;
        function reRenderTable(cols, force) {
            if (force || (vm.cols !== cols && vm.scrollInfo && !(vm.currentTopRow === null))) {
                $log.debug('reRenderTable: ' + cols);
                var curCols = vm.cols;
                var skippedRows = Math.round(vm.scrollInfo.scrollTop / vm.scrollInfo.rowHeight);
                var curTopLeftAddr = (vm.currentTopRow === undefined || vm.currentTopRow === null) ?
                    0 : (vm.currentTopRow + skippedRows) * curCols * currentCellSize;
                if (!vm.dsOpInProgress) {
                    if (cols) {
                        vm.cols = cols;
                    }
                    vm.goToMem('0x' + curTopLeftAddr.toString(16));
                }
                else {
                    $log.debug('reRenderTable skipped');
                }
            }
        }
        vm.refreshTable = function () {
            reRenderTable(null, true);
        };
        vm.toolTipDirection = function (cellAddr) {
            var curCols = vm.cols;
            var skippedRows = Math.round(vm.scrollInfo.scrollTop / vm.scrollInfo.rowHeight);
            var curTopLeftAddr = (vm.currentTopRow === undefined || vm.currentTopRow === null) ?
                0 : (vm.currentTopRow + skippedRows) * curCols * currentCellSize;
            var valsPerRow = curCols * currentCellSize;
            var lowBound = (curTopLeftAddr - 2 * valsPerRow) > 0 ? curTopLeftAddr - valsPerRow : 0;
            var highBound = curTopLeftAddr + valsPerRow;
            if (lowBound <= cellAddr && cellAddr < highBound) {
                return 'bottom';
            }
            else {
                return 'top';
            }
        };
        angular.element($window).on('resize', updateTableDimensions);
        vm.updateScrollInfo = function (info) {
            vm.scrollInfo = info;
        };
        function generateDummyVals() {
            var errArray = [];
            var valArray = [];
            for (var i = 0; i < vm.bufferCount * vm.cols; i++) {
                var val = '';
                for (var j = 0; j < currentValTypeLength; j++) {
                    val += '??';
                }
                valArray.push(val);
                errArray.push('Memory browser not initialized.');
            }
            var ret = { values: valArray, indexedErrors: errArray };
            return ret;
        }
        function generateRows(startAddr, values, highlight, length) {
            var valArray = values.values;
            var errsArray = values.indexedErrors;
            var rows = [];
            //rows are addr in the first col followed by a number of values defined by cols
            var assertMustBeTrue = (valArray.length % vm.cols) === 0;
            if (!assertMustBeTrue) {
                $log.error('generateRows assert failed! must be zero => ' + valArray.length % vm.cols);
            }
            var numOfRows = (valArray.length === 0) ? 0 : (valArray.length / vm.cols);
            //go through row by row, each has an cell to display the starting address of the row.
            for (var i = 0; i < numOfRows; i++) {
                var newRow = {};
                var newRowRawAddr = startAddr + i * vm.cols * currentCellSize;
                newRow.addrStr = intToHexStr(newRowRawAddr);
                var rowVals = valArray.splice(0, vm.cols);
                newRow.cells = [];
                for (var j = 0; j < rowVals.length; j++) {
                    var cell = {};
                    var cellRawAddress = newRowRawAddr + j * currentCellSize;
                    cell.addr = cellRawAddress;
                    cell.addrStr = intToHexStr(cellRawAddress);
                    if (highlight && cellMatch(cellRawAddress, length)) {
                        cell.style = {
                            'background-color': 'rgba(17, 136, 153, 0.05)',
                            'font-weight': '500',
                            'color': '#888',
                        };
                    }
                    cell.val = rowVals[j];
                    cell.err = errsArray[i * vm.cols + j];
                    newRow.cells.push(cell);
                }
                rows.push(newRow);
            }
            return rows;
        }
        function generateDummyRows() {
            var dummyVals = generateDummyVals();
            var startAddr = 0;
            //rows are addr in the first col followed by a number of values defined by cols
            var rows = generateRows(startAddr, dummyVals);
            vm.isAtTopLimit = true;
            return rows;
        }
        vm.rows = generateDummyRows();
        function findAddrGivenRowIdx(rowIdx) {
            return rowIdx * vm.cols * currentCellSize;
        }
        function findRowIdxGivenAddr(addr) {
            return Math.floor(addr / vm.cols / currentCellSize);
        }
        vm.readMem = function (rowIdx, rowCount, page) {
            $log.debug('memory browser: readMem: rowIdx=' + rowIdx + ', rowCount=' + rowCount + ', page=' + page);
            if (vm.dsOpInProgress) {
                return $q.reject('dsOpInProgress, readMem skipped');
            }
            vm.updateTableWithPageInfo(getPageInfo(vm.curPage));
            var numOfValsToRead = rowCount * vm.cols;
            var addr_decimal = findAddrGivenRowIdx(rowIdx);
            if (page) {
                vm.curPage = page;
                vm.initializing = vm.core.readMemory(addr_decimal + '@' + vm.curPage, vm.valType, numOfValsToRead, vm.format);
            }
            else {
                vm.initializing = vm.core.readMemory(addr_decimal, vm.valType, numOfValsToRead, vm.format);
            }
            return vm.initializing.then(function (ret) {
                vm.showInitOverlay = false;
                return ret;
            });
        };
        function findLoadingTopRowIdx(addr) {
            var topRowIdx = findRowIdxGivenAddr(addr) - vm.bufferCount;
            topRowIdx = (topRowIdx < 0) ? 0 : topRowIdx;
            return topRowIdx;
        }
        function findBottomRowIdx(addr) {
            var botRowIdx = findRowIdxGivenAddr(addr) + vm.bufferCount;
            botRowIdx = (findAddrGivenRowIdx(botRowIdx) >= 0xFFFFFFFF) ? findRowIdxGivenAddr(0xFFFFFFFF) : botRowIdx;
            return botRowIdx;
        }
        function validateAddressRegex() {
            return /^0x(#)?([0-9a-fA-F]{1,8})$/i;
        }
        function validLocationRegex() {
            return /^0x(#)?([0-9a-fA-F]{1,8})(@.+)?$/i;
        }
        vm.validateAddressRegex = validateAddressRegex();
        vm.validLocationRegex = validLocationRegex();
        vm.goToMemHighlight = function (addrStr) {
            return vm.goToMem(addrStr, { highlight: true });
        };
        vm.goToMemRange = function () {
            var curMemoryRange = this.ufMemoryRanges.getMemoryRange(parseInt(vm.selectedMemoryRange));
            return vm.goToMem(curMemoryRange.address, { highlight: true }, curMemoryRange.length);
        };
        vm.goToMem = function (addrStr, options, length) {
            $log.debug('memory browser: goToMem: addrStr=' + addrStr + (options) ? ', options=' + JSON.stringify(options) : '');
            if (!addrStr || !addrStr.match(vm.validateAddressRegex)) {
                var err = 'invalid hexadecimal: ' + addrStr;
                $log.error('memory browser: goToMem: ' + err);
                vm.isAddrInputPopoverOpen = true;
                return $q.reject(err);
            }
            else if (vm.dsOpInProgress) {
                var err = 'vm.dsOpInProgress, goToMem skipped';
                $log.error('memory browser: goToMem: ' + err);
                return $q.reject(err);
            }
            vm.isAddrInputPopoverOpen = false;
            newAddr = parseInt(addrStr, 16);
            var currentCenterRow = findRowIdxGivenAddr(newAddr);
            var currentTopRow = findLoadingTopRowIdx(newAddr);
            var topRowIdxAddr = findAddrGivenRowIdx(currentTopRow);
            var rowCount = findBottomRowIdx(newAddr) - currentTopRow;
            // let page = (addrStr.indexOf('@') > 0) ? addrStr.substring(addrStr.indexOf('@')) : '';
            return vm.readMem(currentTopRow, rowCount, vm.curPage)
                .then(function (values) {
                if (options && options.highlight) {
                    return generateRows(topRowIdxAddr, values, options.highlight, length);
                }
                else {
                    return generateRows(topRowIdxAddr, values);
                }
            })
                .then(function (rows) {
                vm.currentTopRow = currentTopRow;
                vm.currentCenterRow = currentCenterRow;
                vm.isAtTopLimit = isAtTop();
                vm.isAtBottomLimit = isAtBottom();
                vm.rows = rows;
                $rootScope.$emit('memory_browser.afterMemoryLoad');
                return;
            })
                .catch(function (e) {
                $log.error('readMem failed:' + e);
            });
        };
        vm.loadUp = function () {
            $log.debug('memory browser: loadUp');
            var currentCenterRow = findLoadingTopRowIdx(findAddrGivenRowIdx(vm.currentCenterRow));
            var currentTopRow = findLoadingTopRowIdx(findAddrGivenRowIdx(currentCenterRow));
            var newRowCountRead = currentCenterRow - currentTopRow;
            return vm.readMem(currentTopRow, newRowCountRead)
                .then(function (values) {
                return generateRows(findAddrGivenRowIdx(currentTopRow), values);
            })
                .then(function (newRows) {
                vm.rows.splice(vm.rows.length - newRows.length, newRows.length);
                vm.currentTopRow = currentCenterRow - newRows.length;
                vm.currentCenterRow = currentCenterRow;
                vm.isAtTopLimit = isAtTop();
                vm.isAtBottomLimit = isAtBottom();
                vm.rows = newRows.concat(vm.rows);
            })
                .catch(function (e) {
                $log.error('readMem failed:' + e);
            });
        };
        vm.loadDown = function () {
            $log.debug('memory browser: loadDown');
            var oldCenterRow = vm.currentCenterRow;
            var currentCenterRow = findBottomRowIdx(findAddrGivenRowIdx(oldCenterRow));
            var newRowCountRead = currentCenterRow - oldCenterRow;
            return vm.readMem(currentCenterRow, newRowCountRead)
                .then(function (values) {
                return generateRows(findAddrGivenRowIdx(currentCenterRow), values);
            })
                .then(function (newRows) {
                vm.rows.splice(0, vm.rows.length - vm.bufferCount);
                vm.currentTopRow = oldCenterRow;
                vm.currentCenterRow = currentCenterRow;
                vm.isAtTopLimit = isAtTop();
                vm.isAtBottomLimit = isAtBottom();
                vm.rows = vm.rows.concat(newRows);
            })
                .catch(function (e) {
                $log.error('readMem failed:' + e);
            });
        };
        function isAtTop() {
            var topRow = findLoadingTopRowIdx(findAddrGivenRowIdx(vm.currentCenterRow));
            return (topRow === 0);
        }
        function isAtBottom() {
            var botRow = findBottomRowIdx(findAddrGivenRowIdx(vm.currentCenterRow));
            var botRowLastAddr = findAddrGivenRowIdx(botRow) + vm.cols * currentCellSize;
            if (botRowLastAddr >= 0xFFFFFFFF) {
                return true;
            }
            return false;
        }
        function cellMatch(cellAddr, length) {
            if (length) {
                return (cellAddr >= newAddr) && (cellAddr < newAddr + length);
            }
            else {
                return (cellAddr <= newAddr) && (cellAddr + currentCellSize > newAddr);
            }
        }
        function intToHexStr(int) {
            return '0x' + int.toString(16).toUpperCase();
        }
        function hexStrToInt(str) {
            if (str && str.match(vm.validateAddressRegex)) {
                return parseInt(str, 16);
            }
            else {
                return undefined;
            }
        }
        vm.cellTootip = function (cell) {
            var ret = [];
            if (cell.err) {
                ret.push(cell.err);
            }
            else {
                for (var i = 0; i < currentCellSize; i++) {
                    var addr = intToHexStr(parseInt(cell.addrStr, 16) + i);
                    var tooltipAddrVal = void 0;
                    if (currentMAUSize === 1) {
                        tooltipAddrVal = (parseInt(cell.val, 16) >> (i * 8)) & 0xFF;
                        ret.push(addr + ': ' + ('0' + tooltipAddrVal.toString(16).toUpperCase()).slice(-2));
                    }
                    else if (currentMAUSize === 2) {
                        tooltipAddrVal = parseInt(cell.val, 16);
                        ret.push(addr + ': ' + ('000' + tooltipAddrVal.toString(16).toUpperCase()).slice(-4));
                    }
                }
            }
            return ret;
        };
        vm.export = function (core, pageInfoList) {
            var modalInstance = $uibModal.open({
                templateUrl: 'memory/memory_export.html',
                controllerAs: 'vm',
                controller: OpenExportDialogController
            });
            function OpenExportDialogController() {
                var vm = this;
                vm.title = 'Export Memory';
                vm.format = 'BIN';
                vm.exportConfig = {};
                vm.exportConfig.ranges = [];
                vm.mauSizeInBits = 8 * currentMAUSize;
                vm.isNW = function () {
                    return ticExecutionPlatform.getPlatform() === ti_common.PlatformType.NW;
                };
                vm.getExtension = function () {
                    if (vm.format === 'BIN') {
                        return '.bin';
                    }
                    else {
                        return '.out';
                    }
                };
                vm.browseExportFile = function () {
                    return ticFileDialogs.saveFile('Select Export File', 'exported-image', vm.getExtension())
                        .then(function (file) {
                        if (file) {
                            vm.path = file.path;
                        }
                        return file;
                    });
                };
                vm.exportToCloudStorage = function () {
                    modalInstance.close();
                };
                vm.validateAddressRegex = validateAddressRegex();
                vm.pageInfoList = pageInfoList;
                angular.forEach(pageInfoList, function (pageInfo) {
                    if (pageInfo.isDefault) {
                        vm.curPage = pageInfo.name;
                    }
                });
                function setPageToRanges(addrRanges) {
                    var retRanges = [];
                    angular.forEach(addrRanges, function (range) {
                        var locationRange = {
                            location: range.address + '@' + vm.curPage,
                            length: getWordsCountInput(range.length)
                        };
                        retRanges.push(locationRange);
                    });
                    return retRanges;
                }
                function getWordsCountInput(byteCountNumber) {
                    if (byteCountNumber) {
                        var wordSize = Math.ceil(byteCountNumber * currentMAUSize / currentWordSize);
                        return wordSize;
                    }
                    else {
                        return undefined;
                    }
                }
                vm.exportToLocal = function () {
                    var exportPromise;
                    var defaultPath = vm.isNW() ? undefined : vm.path;
                    if (vm.format === 'BIN') {
                        var range = {
                            location: hexStrToInt(vm.addrInput) + '@' + vm.curPage,
                            length: getWordsCountInput(Number(vm.bytesCountInput))
                        };
                        $log.debug('exportAsBin: range=' + JSON.stringify(range));
                        exportPromise = core.exportMemoryAsBin(range, vm.mauSizeInBits, defaultPath);
                    }
                    else {
                        var ranges = setPageToRanges(vm.exportConfig.ranges);
                        $log.debug('exportAsCoff: ranges=' + JSON.stringify(ranges));
                        exportPromise = core.exportMemoryAsCoff(ranges, defaultPath);
                    }
                    return exportPromise
                        .then(function (res) {
                        if (vm.isNW()) {
                            if (vm.path) {
                                var fs = require('fs');
                                fs.createReadStream(res.path).pipe(fs.createWriteStream(vm.path));
                                ufConsole.addMessage(ufConsole.msgTypes.STATUS, 'Memory exported to: ' + vm.path + ', ' + res.length * currentWordSize + 'B of binary data.');
                            }
                            else {
                                ufConsole.addMessage(ufConsole.msgTypes.STATUS, 'Please provide an export file path.');
                            }
                        }
                        else {
                            ufConsole.addMessage(ufConsole.msgTypes.STATUS, 'Memory exported to: ' + res.path + ', ' + res.length * currentWordSize + 'B of binary data.');
                        }
                    })
                        .finally(function () {
                        modalInstance.close();
                    });
                };
                vm.showTable = function () {
                    return vm.format === 'COFF';
                };
                vm.addRange = function (addr, len) {
                    if (validateRange(addr, len)) {
                        vm.exportConfig.ranges.push({
                            address: hexStrToInt(addr),
                            length: Number(len)
                        });
                        vm.addrInput = undefined;
                        vm.bytesCountInput = undefined;
                    }
                };
                vm.removeRange = function (range) {
                    for (var i = 0; i < vm.exportConfig.ranges.length; i++) {
                        if (vm.exportConfig.ranges[i].address === range.address) {
                            vm.exportConfig.ranges.splice(i, 1);
                            return;
                        }
                    }
                };
                function isInt(n) {
                    if (!n) {
                        return n === 0;
                    }
                    return n % 1 === 0;
                }
                function validateRange(addr, length) {
                    var addrNum = hexStrToInt(addr);
                    var lenNum = Number(length);
                    var addrValid = (addrNum === undefined) ? false : isInt(addrNum) && addrNum >= 0;
                    var lenValid = isInt(lenNum) && lenNum > 0;
                    vm.msg = '';
                    if (vm.bytesCountInput && !addrValid) {
                        vm.msg += 'Address must be a valid hexadecimal number. eg. 0x12345678';
                    }
                    if (vm.bytesCountInput && !lenValid) {
                        vm.msg += 'Length must be an integer larger than zero. ';
                    }
                    var ret = lenValid && addrValid;
                    return ret;
                }
                vm.readyToExport = function () {
                    if (vm.format === 'COFF') {
                        return (vm.exportConfig.ranges.length > 0);
                    }
                    else {
                        return validateRange(vm.addrInput, vm.bytesCountInput);
                    }
                };
                vm.quickGuide = function () {
                    return (vm.format === 'COFF') ?
                        'Export an array of memory ranges to an .out file in COFF format.' :
                        'Export a single piece of continous memory range to a .bin file in binary byte stream.';
                };
            }
            return modalInstance.result;
        };
    }
})();
