"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Board = exports.Programmer = exports.ConfigOption = exports.InstalledBoardWithPackage = exports.BoardWithPackage = exports.BoardsPackage = exports.Port = exports.BoardsService = exports.BoardsServicePath = exports.AttachedBoardsChangeEvent = void 0;
var os_1 = require("@theia/core/lib/common/os");
var utils_1 = require("./../utils");
var AttachedBoardsChangeEvent;
(function (AttachedBoardsChangeEvent) {
    function isEmpty(event) {
        var _a = diff(event), detached = _a.detached, attached = _a.attached;
        return !!detached.boards.length && !!detached.ports.length && !!attached.boards.length && !!attached.ports.length;
    }
    AttachedBoardsChangeEvent.isEmpty = isEmpty;
    function toString(event) {
        var e_1, _a, e_2, _b, e_3, _c, e_4, _d;
        var rows = [];
        if (!isEmpty(event)) {
            var _e = diff(event), attached = _e.attached, detached = _e.detached;
            var visitedAttachedPorts = [];
            var visitedDetachedPorts = [];
            try {
                for (var _f = __values(attached.boards), _g = _f.next(); !_g.done; _g = _f.next()) {
                    var board = _g.value;
                    var port = board.port ? " on " + Port.toString(board.port, { useLabel: true }) : '';
                    rows.push(" - Attached board: " + Board.toString(board) + port);
                    if (board.port) {
                        visitedAttachedPorts.push(board.port);
                    }
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
                }
                finally { if (e_1) throw e_1.error; }
            }
            try {
                for (var _h = __values(detached.boards), _j = _h.next(); !_j.done; _j = _h.next()) {
                    var board = _j.value;
                    var port = board.port ? " from " + Port.toString(board.port, { useLabel: true }) : '';
                    rows.push(" - Detached board: " + Board.toString(board) + port);
                    if (board.port) {
                        visitedDetachedPorts.push(board.port);
                    }
                }
            }
            catch (e_2_1) { e_2 = { error: e_2_1 }; }
            finally {
                try {
                    if (_j && !_j.done && (_b = _h.return)) _b.call(_h);
                }
                finally { if (e_2) throw e_2.error; }
            }
            var _loop_1 = function (port) {
                if (!visitedAttachedPorts.find(function (p) { return Port.sameAs(port, p); })) {
                    rows.push(" - New port is available on " + Port.toString(port, { useLabel: true }));
                }
            };
            try {
                for (var _k = __values(attached.ports), _l = _k.next(); !_l.done; _l = _k.next()) {
                    var port = _l.value;
                    _loop_1(port);
                }
            }
            catch (e_3_1) { e_3 = { error: e_3_1 }; }
            finally {
                try {
                    if (_l && !_l.done && (_c = _k.return)) _c.call(_k);
                }
                finally { if (e_3) throw e_3.error; }
            }
            var _loop_2 = function (port) {
                if (!visitedDetachedPorts.find(function (p) { return Port.sameAs(port, p); })) {
                    rows.push(" - Port is no longer available on " + Port.toString(port, { useLabel: true }));
                }
            };
            try {
                for (var _m = __values(detached.ports), _o = _m.next(); !_o.done; _o = _m.next()) {
                    var port = _o.value;
                    _loop_2(port);
                }
            }
            catch (e_4_1) { e_4 = { error: e_4_1 }; }
            finally {
                try {
                    if (_o && !_o.done && (_d = _m.return)) _d.call(_m);
                }
                finally { if (e_4) throw e_4.error; }
            }
        }
        return rows.length ? rows.join('\n') : 'No changes.';
    }
    AttachedBoardsChangeEvent.toString = toString;
    function diff(event) {
        // In `lefts` AND not in `rights`.
        var diff = function (lefts, rights, sameAs) {
            return lefts.filter(function (left) { return rights.findIndex(function (right) { return sameAs(left, right); }) === -1; });
        };
        var newBoards = event.newState.boards;
        var oldBoards = event.oldState.boards;
        var newPorts = event.newState.ports;
        var oldPorts = event.oldState.ports;
        var boardSameAs = function (left, right) { return Board.sameAs(left, right); };
        var portSameAs = function (left, right) { return Port.sameAs(left, right); };
        return {
            detached: {
                boards: diff(oldBoards, newBoards, boardSameAs),
                ports: diff(oldPorts, newPorts, portSameAs)
            },
            attached: {
                boards: diff(newBoards, oldBoards, boardSameAs),
                ports: diff(newPorts, oldPorts, portSameAs)
            }
        };
    }
    AttachedBoardsChangeEvent.diff = diff;
})(AttachedBoardsChangeEvent = exports.AttachedBoardsChangeEvent || (exports.AttachedBoardsChangeEvent = {}));
exports.BoardsServicePath = '/services/boards-service';
exports.BoardsService = Symbol('BoardsService');
var Port;
(function (Port) {
    var Protocol;
    (function (Protocol) {
        function toProtocol(protocol) {
            if (protocol === 'serial') {
                return 'serial';
            }
            else if (protocol === 'network') {
                return 'network';
            }
            else {
                return 'unknown';
            }
        }
        Protocol.toProtocol = toProtocol;
    })(Protocol = Port.Protocol || (Port.Protocol = {}));
    function is(arg) {
        return !!arg && 'address' in arg && typeof arg['address'] === 'string' && 'protocol' in arg && typeof arg['protocol'] === 'string';
    }
    Port.is = is;
    function toString(port, options) {
        if (options === void 0) { options = { useLabel: false }; }
        if (options.useLabel && port.label) {
            return port.address + " " + port.label;
        }
        return port.address;
    }
    Port.toString = toString;
    function compare(left, right) {
        // Board ports have higher priorities, they come first.
        if (isBoardPort(left) && !isBoardPort(right)) {
            return -1;
        }
        if (!isBoardPort(left) && isBoardPort(right)) {
            return 1;
        }
        var result = utils_1.naturalCompare(left.protocol.toLocaleLowerCase(), right.protocol.toLocaleLowerCase());
        if (result !== 0) {
            return result;
        }
        result = utils_1.naturalCompare(left.address, right.address);
        if (result !== 0) {
            return result;
        }
        return utils_1.naturalCompare(left.label || '', right.label || '');
    }
    Port.compare = compare;
    function equals(left, right) {
        if (left && right) {
            return left.address === right.address
                && left.protocol === right.protocol
                && (left.label || '') === (right.label || '');
        }
        return left === right;
    }
    Port.equals = equals;
    // Based on: https://github.com/arduino/Arduino/blob/93581b03d723e55c60caedb4729ffc6ea808fe78/arduino-core/src/processing/app/SerialPortList.java#L48-L74   
    function isBoardPort(port) {
        var address = port.address.toLocaleLowerCase();
        if (os_1.isWindows) {
            // `COM1` seems to be the default serial port on Windows.
            return address !== 'COM1'.toLocaleLowerCase();
        }
        // On macOS and Linux, the port should start with `/dev/`.
        if (!address.startsWith('/dev/')) {
            return false;
        }
        if (os_1.isOSX) {
            // Example: `/dev/cu.usbmodem14401`
            if (/(tty|cu)\..*/i.test(address.substring('/dev/'.length))) {
                return [
                    '/dev/cu.MALS',
                    '/dev/cu.SOC',
                    '/dev/cu.Bluetooth-Incoming-Port'
                ].map(function (a) { return a.toLocaleLowerCase(); }).every(function (a) { return a !== address; });
            }
        }
        // Example: `/dev/ttyACM0`
        if (/(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}/i.test(address.substring('/dev/'.length))) {
            // Default ports were `/dev/ttyS0` -> `/dev/ttyS31` on Ubuntu 16.04.2.
            if (address.startsWith('/dev/ttyS')) {
                var index = Number.parseInt(address.substring('/dev/ttyS'.length), 10);
                if (!Number.isNaN(index) && 0 <= index && 31 >= index) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
    Port.isBoardPort = isBoardPort;
    function sameAs(left, right) {
        if (left && right) {
            if (left.protocol !== 'serial') {
                console.log("Unexpected protocol for 'left' port: " + JSON.stringify(left) + ". Ignoring 'protocol', comparing 'addresses' with " + JSON.stringify(right) + ".");
            }
            if (typeof right === 'string') {
                return left.address === right;
            }
            if (right.protocol !== 'serial') {
                console.log("Unexpected protocol for 'right' port: " + JSON.stringify(right) + ". Ignoring 'protocol', comparing 'addresses' with " + JSON.stringify(left) + ".");
            }
            return left.address === right.address;
        }
        return false;
    }
    Port.sameAs = sameAs;
})(Port = exports.Port || (exports.Port = {}));
var BoardsPackage;
(function (BoardsPackage) {
    function equals(left, right) {
        return left.id === right.id;
    }
    BoardsPackage.equals = equals;
    function contains(selectedBoard, _a) {
        var id = _a.id, boards = _a.boards;
        if (boards.some(function (board) { return Board.sameAs(board, selectedBoard); })) {
            return true;
        }
        if (selectedBoard.fqbn) {
            var _b = __read(selectedBoard.fqbn.split(':'), 2), platform = _b[0], architecture = _b[1];
            if (platform && architecture) {
                return platform + ":" + architecture === id;
            }
        }
        return false;
    }
    BoardsPackage.contains = contains;
})(BoardsPackage = exports.BoardsPackage || (exports.BoardsPackage = {}));
var BoardWithPackage;
(function (BoardWithPackage) {
    function is(board) {
        return !!board.packageId && !!board.packageName;
    }
    BoardWithPackage.is = is;
})(BoardWithPackage = exports.BoardWithPackage || (exports.BoardWithPackage = {}));
var InstalledBoardWithPackage;
(function (InstalledBoardWithPackage) {
    function is(boardWithPackage) {
        return !!boardWithPackage.fqbn;
    }
    InstalledBoardWithPackage.is = is;
})(InstalledBoardWithPackage = exports.InstalledBoardWithPackage || (exports.InstalledBoardWithPackage = {}));
var ConfigOption;
(function (ConfigOption) {
    function is(arg) {
        return !!arg && 'option' in arg && 'label' in arg && 'values' in arg
            && typeof arg['option'] === 'string' && typeof arg['label'] === 'string' && Array.isArray(arg['values']);
    }
    ConfigOption.is = is;
    /**
     * Appends the configuration options to the `fqbn` argument.
     * Throws an error if the `fqbn` does not have the `segment(':'segment)*` format.
     * The provided output format is always segment(':'segment)*(':'option'='value(','option'='value)*)?
     */
    function decorate(fqbn, configOptions) {
        if (!configOptions.length) {
            return fqbn;
        }
        var toValue = function (values) {
            var selectedValue = values.find(function (_a) {
                var selected = _a.selected;
                return selected;
            });
            if (!selectedValue) {
                console.warn("None of the config values was selected. Values were: " + JSON.stringify(values));
                return undefined;
            }
            return selectedValue.value;
        };
        var options = configOptions
            .map(function (_a) {
            var option = _a.option, values = _a.values;
            return [option, toValue(values)];
        })
            .filter(function (_a) {
            var _b = __read(_a, 2), value = _b[1];
            return !!value;
        })
            .map(function (_a) {
            var _b = __read(_a, 2), option = _b[0], value = _b[1];
            return option + "=" + value;
        })
            .join(',');
        return fqbn + ":" + options;
    }
    ConfigOption.decorate = decorate;
    var ConfigOptionError = /** @class */ (function (_super) {
        __extends(ConfigOptionError, _super);
        function ConfigOptionError(message) {
            var _this = _super.call(this, message) || this;
            Object.setPrototypeOf(_this, ConfigOptionError.prototype);
            return _this;
        }
        return ConfigOptionError;
    }(Error));
    ConfigOption.ConfigOptionError = ConfigOptionError;
    ConfigOption.LABEL_COMPARATOR = function (left, right) { return utils_1.naturalCompare(left.label.toLocaleLowerCase(), right.label.toLocaleLowerCase()); };
})(ConfigOption = exports.ConfigOption || (exports.ConfigOption = {}));
var Programmer;
(function (Programmer) {
    function equals(left, right) {
        if (!left) {
            return !right;
        }
        if (!right) {
            return !left;
        }
        return left.id === right.id && left.name === right.name && left.platform === right.platform;
    }
    Programmer.equals = equals;
})(Programmer = exports.Programmer || (exports.Programmer = {}));
var Board;
(function (Board) {
    function is(board) {
        return !!board && 'name' in board;
    }
    Board.is = is;
    function equals(left, right) {
        return left.name === right.name && left.fqbn === right.fqbn;
    }
    Board.equals = equals;
    function sameAs(left, right) {
        // How to associate a selected board with one of the available cores: https://typefox.slack.com/archives/CJJHJCJSJ/p1571142327059200
        // 1. How to use the FQBN if any and infer the package ID from it: https://typefox.slack.com/archives/CJJHJCJSJ/p1571147549069100
        // 2. How to trim the `/Genuino` from the name: https://arduino.slack.com/archives/CJJHJCJSJ/p1571146951066800?thread_ts=1571142327.059200&cid=CJJHJCJSJ
        var other = typeof right === 'string' ? { name: right } : right;
        if (left.fqbn && other.fqbn) {
            return left.fqbn === other.fqbn;
        }
        return left.name.replace('/Genuino', '') === other.name.replace('/Genuino', '');
    }
    Board.sameAs = sameAs;
    function compare(left, right) {
        var result = utils_1.naturalCompare(left.name, right.name);
        if (result === 0) {
            result = utils_1.naturalCompare(left.fqbn || '', right.fqbn || '');
        }
        return result;
    }
    Board.compare = compare;
    function installed(board) {
        return !!board.fqbn;
    }
    Board.installed = installed;
    function toString(board, options) {
        if (options === void 0) { options = { useFqbn: true }; }
        var fqbn = options && options.useFqbn && board.fqbn ? " [" + board.fqbn + "]" : '';
        return "" + board.name + fqbn;
    }
    Board.toString = toString;
    function decorateBoards(selectedBoard, boards) {
        var e_5, _a;
        // Board names are not unique. We show the corresponding core name as a detail.
        // https://github.com/arduino/arduino-cli/pull/294#issuecomment-513764948
        var distinctBoardNames = new Map();
        try {
            for (var boards_1 = __values(boards), boards_1_1 = boards_1.next(); !boards_1_1.done; boards_1_1 = boards_1.next()) {
                var name_1 = boards_1_1.value.name;
                var counter = distinctBoardNames.get(name_1) || 0;
                distinctBoardNames.set(name_1, counter + 1);
            }
        }
        catch (e_5_1) { e_5 = { error: e_5_1 }; }
        finally {
            try {
                if (boards_1_1 && !boards_1_1.done && (_a = boards_1.return)) _a.call(boards_1);
            }
            finally { if (e_5) throw e_5.error; }
        }
        // Due to the non-unique board names, we have to check the package name as well.
        var selected = function (board) {
            if (!!selectedBoard) {
                if (Board.equals(board, selectedBoard)) {
                    if ('packageName' in selectedBoard) {
                        return board.packageName === selectedBoard.packageName;
                    }
                    if ('packageId' in selectedBoard) {
                        return board.packageId === selectedBoard.packageId;
                    }
                    return true;
                }
            }
            return false;
        };
        return boards.map(function (board) { return (__assign(__assign({}, board), { details: (distinctBoardNames.get(board.name) || 0) > 1 ? " - " + board.packageName : undefined, selected: selected(board), missing: !installed(board) })); });
    }
    Board.decorateBoards = decorateBoards;
})(Board = exports.Board || (exports.Board = {}));
//# sourceMappingURL=boards-service.js.map