'use strict';

var _ = require('lodash');
var when = require('when');
var nodefn = require('when/node');
var Serialport = require('browser-serialport');

function getFallbackType(){
  //TODO eventually will be used to select a board type when one isn't detected.
  return 'bs2';
}

function generateUniqueName(device){
  return device.type + '-' + device.path;
}

function addBoard(type, Board){
  this._boards[type] = Board;
}

function removeBoard(type){
  delete this._boards[type];
}

function listPorts(options){
  var reject = _.get(options, 'reject') || [];
  var predicate = reject;
  if(Array.isArray(reject)){
    predicate = function(port){
      return _.some(reject, function(entry){
        if(typeof entry === 'string'){
          return port.comName === entry;
        }
        return entry.test(port.comName);
      });
    };
  }
  return nodefn.call(Serialport.list)
    .then(function(portList){
      return _.reject(portList, predicate);
    });
}

function getBoard(options){
  var uniqueName = generateUniqueName(options);
  var device = this._devices[uniqueName];
  if(!device){
    var Board = this._boards[options.type];
    device = new Board(options.board);
    this._devices[uniqueName] = device;
  }
  return device;
}

function generateEmptyPortlist(portList){
  var type = getFallbackType();
  return _.map(portList, function(port){
    return {
      name: null,
      path: port,
      type: type,
      board: {
        path: port
      }
    };
  });
}

function scanBoards(options){
  var self = this;
  var opts = options || {};
  var targetBoards, targetTypes;
  if(opts.type){
    targetBoards = [this._boards[opts.type]];
    targetTypes = [opts.type];
  }else{
    targetBoards = _.values(this._boards);
    targetTypes = _.keys(this._boards);
  }

  return this.releaseAllBoards()
    .then(function(){
      self._devices = {};
      return self.listPorts(opts);
    })
    .then(function(ports){
      var portList = _.pluck(ports, 'comName');
      return when.reduce(targetBoards, function(boardList, board, ix){
        return board.search(portList, opts)
          .then(function(newBoards){
            _.forEach(newBoards, function(boardInfo){
              boardInfo.type = targetTypes[ix];
            });
            return boardList.concat(newBoards);
          });
      }, [])
      .then(function(boards){
        var allPorts = generateEmptyPortlist(portList);
        var emptyPorts = _.reject(allPorts, function(ep){
          return _.find(boards, { path: ep.path });
        });
        return boards.concat(emptyPorts);
      });
  });
}

function getOpenBoards(){
  return _.filter(this._devices, function(device){
    return device.isOpen();
  });
}

function releaseAllBoards(){
  var closingDevices = _.map(this.getOpenBoards(), function(device){
    return device.close();
  });

  return when.settle(closingDevices);
}

module.exports = {
  addBoard: addBoard,
  releaseAllBoards: releaseAllBoards,
  getBoard: getBoard,
  getOpenBoards: getOpenBoards,
  listPorts: listPorts,
  removeBoard: removeBoard,
  scanBoards: scanBoards
};

@@ -1427,16 +1427,54 @@
 : type,%0A
+      match: false,%0A      data: null,%0A
       bo

Add empty fields to fallback board object