text:
(function() {
  var Activateable, CircularArray, ColorBar, ComputerPlayer, DATA, Fight, ImageAnimation, InputState, KeyChanger, Lobby, MainTimer, Menu, NumberChanger, PropertiesDisplay, RES, Selectable, Ship, ShipEditor, Shoot, TextChooser, Timer, c, choose, getRandomInt, include, loadImages, removeFromArray, t,
    __slice = [].slice,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  DATA = {
    energyMax: 1000,
    damageMax: 100,
    rofMax: 100,
    speedMax: 20,
    presets: [
      {
        presetName: 'normal',
        shipName: 'hunter',
        properties: {
          energy: 50,
          damage: 5,
          rof: 15,
          speed: 8
        }
      }, {
        presetName: 'light',
        shipName: 'scout',
        properties: {
          energy: 35,
          damage: 2,
          rof: 5,
          speed: 10
        }
      }, {
        presetName: 'heavy',
        shipName: 'destroyer',
        properties: {
          energy: 75,
          damage: 20,
          rof: 60,
          speed: 5
        }
      }
    ],
    action_keyCode_maps: [
      {
        up: 87,
        down: 83,
        right: 68,
        left: 65
      }, {
        up: 38,
        down: 40,
        right: 39,
        left: 37
      }
    ],
    areaHeight: 500,
    areaWidth: 1000,
    shipAreaGap: 5,
    mode: 'offline',
    CSS: {
      'flip-horizontal': {
        '-moz-transform': 'scaleX(-1)',
        '-webkit-transform': 'scaleX(-1)',
        'transform': 'scaleX(-1)',
        'filter': 'fliph',
        '-o-transform': 'scaleX(-1)'
      }
    }
  };

  loadImages = function(folder, number, width, height) {
    var i, images, img, _i;
    images = new Array;
    images.width = width;
    images.height = height;
    for (i = _i = 1; 1 <= number ? _i <= number : _i >= number; i = 1 <= number ? ++_i : --_i) {
      img = new Image;
      img.src = 'images/' + folder + '/img (' + i + ').png';
      images.push(img);
    }
    return images;
  };

  MainTimer = (function() {
    function MainTimer(interval) {
      if (interval == null) {
        interval = 10;
      }
      this.interval = interval;
      this.running = false;
      this.timers = [];
      this.intervalId = void 0;
    }

    MainTimer.prototype.start = function() {
      var _this = this;
      this.stop();
      this.intervalId = window.setInterval((function() {
        return _this.tick();
      }), this.interval);
      return this.running = true;
    };

    MainTimer.prototype.stop = function() {
      window.clearInterval(this.intervalId);
      return this.running = false;
    };

    MainTimer.prototype.tick = function() {
      var timer, _i, _len, _ref, _results;
      _ref = this.timers;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        timer = _ref[_i];
        if ((timer != null) && !timer.stop) {
          timer.count += 1;
          if (timer.count >= timer.interval) {
            timer.fn();
            if (timer.repeat) {
              _results.push(timer.count = 0);
            } else {
              _results.push(removeFromArray(timer, this.timers));
            }
          } else {
            _results.push(void 0);
          }
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };

    MainTimer.prototype.add = function(timer) {
      return this.timers.push(timer);
    };

    MainTimer.prototype.remove = function(timer) {
      return removeFromArray(timer, this.timers);
    };

    return MainTimer;

  })();

  Timer = (function() {
    function Timer(fn, interval, repeat, count) {
      this.fn = fn;
      this.interval = interval;
      this.repeat = repeat != null ? repeat : true;
      this.count = count != null ? count : 0;
      this.stop = false;
    }

    return Timer;

  })();

  getRandomInt = function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  };

  choose = function(elements) {
    return elements[getRandomInt(0, elements.length - 1)];
  };

  removeFromArray = function(element, array) {
    var index;
    index = $.inArray(element, array);
    if (index > -1) {
      return array.splice(index, 1);
    }
  };

  c = function() {
    var child, children, el, tag, _i, _len;
    tag = arguments[0], children = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
    if (tag.nodeName) {
      el = tag;
    } else {
      el = document.createElement(tag);
    }
    for (_i = 0, _len = children.length; _i < _len; _i++) {
      child = children[_i];
      if (child.dom) {
        child = child.dom;
      }
      el.appendChild(child);
    }
    return el;
  };

  t = function(text) {
    return document.createTextNode(text);
  };

  ({
    jV: function(data) {
      var el, i, key, object, value, _i, _len;
      object = {};
      for (i = _i = 0, _len = data.length; _i < _len; i = _i += 2) {
        el = data[i];
        key = el;
        value = data[i + 1];
        object[key] = value;
      }
      return object;
    }
  });

  include = function(target, obj) {
    var key, value, _results;
    _results = [];
    for (key in obj) {
      value = obj[key];
      _results.push(target.prototype[key] = value);
    }
    return _results;
  };

  CircularArray = (function() {
    function CircularArray(array) {
      this.array = array != null ? array : [];
      this.i = 0;
    }

    CircularArray.prototype.next = function() {
      this.i = (this.i + 1) % this.array.length;
      return this;
    };

    CircularArray.prototype.previous = function() {
      if ((this.i -= 1) < 0) {
        this.i = this.array.length - 1;
      }
      return this;
    };

    CircularArray.prototype.get = function(i) {
      return this.array[i != null ? i : this.i];
    };

    CircularArray.prototype.index = function(i) {
      if (i != null) {
        this.i = i;
      }
      return this.i;
    };

    CircularArray.prototype.add = function(el) {
      this.array.push(el);
      return el;
    };

    CircularArray.prototype.remove = function(el) {
      removeFromArray(el, this.array);
      return el;
    };

    CircularArray.prototype.addAfter = function(i, el) {
      var end, start;
      start = this.array.slice(0, i + 1);
      end = this.array.slice(i + 1);
      start.push(el);
      return this.array = start.concat(end);
    };

    CircularArray.prototype.addBefore = function(i, el) {
      var a;
      a = this.array.slice(0, i);
      a.push(el);
      return this.array = a.concat(this.array.slice(i));
    };

    return CircularArray;

  })();

  RES = {
    images: {
      explosion: loadImages('explosion', 8, 32, 32),
      countdown: loadImages('countdown', 24, 320, 180)
    },
    mainTimer: new MainTimer(40),
    socket: void 0
  };

  Selectable = {
    select: function() {
      return $(this.dom).css({
        background: 'black',
        color: 'white'
      });
    },
    deselect: function() {
      return $(this.dom).css({
        background: '',
        color: 'black'
      });
    }
  };

  TextChooser = (function() {
    include(TextChooser, Selectable);

    function TextChooser() {
      this.dom = c('span', this.txt = t(''));
    }

    TextChooser.prototype.next = function() {
      return this.activate(this.texts.next().index());
    };

    TextChooser.prototype.previous = function() {
      return this.activate(this.texts.previous().index());
    };

    TextChooser.prototype.activate = function(index) {
      var _ref;
      this.txt.data = this.texts.get(index);
      return (_ref = this.actions) != null ? typeof _ref[index] === "function" ? _ref[index]() : void 0 : void 0;
    };

    TextChooser.prototype.setData = function(data) {
      var i;
      i = $.inArray(data, this.texts.array);
      if (i > -1) {
        this.texts.index(i);
        return this.txt.data = data;
      }
    };

    TextChooser.prototype.setTexts = function(texts) {
      return this.texts = new CircularArray(texts);
    };

    TextChooser.prototype.setActions = function(actions) {
      this.actions = actions;
    };

    return TextChooser;

  })();

  NumberChanger = (function() {
    include(NumberChanger, Selectable);

    function NumberChanger(max, min, step) {
      this.max = max;
      this.min = min != null ? min : 0;
      this.step = step != null ? step : 1;
      this.dom = c('span', this.txt = t(''));
    }

    NumberChanger.prototype.next = function() {
      return this.txt.data = this.number = Math.min(this.number + this.step, this.max);
    };

    NumberChanger.prototype.previous = function() {
      return this.txt.data = this.number = Math.max(this.number - this.step, this.min);
    };

    NumberChanger.prototype.setData = function(data) {
      return this.txt.data = this.number = data;
    };

    NumberChanger.prototype.getData = function() {
      return this.number;
    };

    return NumberChanger;

  })();

  Activateable = (function() {
    include(Activateable, Selectable);

    function Activateable(text, action) {
      this.text = text;
      this.action = action;
      this.dom = c('span', this.txt = t(this.text));
    }

    Activateable.prototype.next = function() {
      return typeof this.action === "function" ? this.action() : void 0;
    };

    return Activateable;

  })();

  ColorBar = (function() {
    function ColorBar(width, i) {
      this.width = width;
      this.i = i;
      this.dom = c('div', this.bar = c('div'));
      $(this.dom).css({
        width: this.width + 'px'
      });
      $(this.bar).css({
        height: '20px',
        position: 'absolute'
      });
      if (this.i === 1) {
        this.bar.style.right = '0px';
      }
    }

    ColorBar.prototype.update = function(n, maxn) {
      var color, f, p;
      if (n < 0) {
        n = 0;
      }
      p = n / maxn;
      f = parseInt(510 * p);
      if (p === 0) {
        color = 'rgb(255,0,0)';
      } else if (f <= 255) {
        color = 'rgb(255,' + f + ',0)';
      } else if (p === 1) {
        color = 'rgb(0,255,0)';
      } else if (f > 255) {
        f = f - 255;
        f = 255 - f;
        color = 'rgb(' + f + ',255,0)';
      }
      this.bar.style.background = color;
      return this.bar.style.width = parseInt(this.width * p) + 'px';
    };

    return ColorBar;

  })();

  PropertiesDisplay = (function() {
    function PropertiesDisplay() {
      this.dom = c('div');
    }

    PropertiesDisplay.prototype.add = function(property) {
      var img, max, step;
      this[property] = {};
      max = DATA[property + 'Max'];
      if (max >= 100) {
        if (max > 500) {
          step = 10;
        } else {
          step = 2;
        }
      } else {
        step = 1;
      }
      this.dom.appendChild(c('div', this[property].image = img = c('img'), this[property].txt = new NumberChanger(max, 0, step)));
      img.src = 'images/' + property + '.png';
      $(img).css({
        verticalAlign: 'middle',
        margin: '4px',
        marginRight: '8px'
      });
      return this[property];
    };

    return PropertiesDisplay;

  })();

  ShipEditor = (function() {
    function ShipEditor(i, action_keyCode_map) {
      var OKCont, cpuHandler, s, shipNameDiv,
        _this = this;
      this.i = i;
      this.action_keyCode_map = action_keyCode_map;
      this.end = __bind(this.end, this);
      this._break = __bind(this._break, this);
      cpuHandler = function() {
        _this.cpu = true;
        return _this.end();
      };
      this.keyHandler = function(e) {
        var _base, _base1;
        switch (e.which) {
          case _this.action_keyCode_map.down:
            s.get().deselect();
            s.next();
            return s.get().select();
          case _this.action_keyCode_map.up:
            s.get().deselect();
            s.previous();
            return s.get().select();
          case _this.action_keyCode_map.right:
            return typeof (_base = s.get()).next === "function" ? _base.next() : void 0;
          case _this.action_keyCode_map.left:
            return typeof (_base1 = s.get()).previous === "function" ? _base1.previous() : void 0;
        }
      };
      this.dom = c('div', c('div', this.presetName = new TextChooser), this.imageContainer = c('div', this.image = c('img'), shipNameDiv = c('div', this.shipName = new TextChooser)), this.properties = new PropertiesDisplay, OKCont = c('div', this.CPU = new Activateable('CPU', cpuHandler), c('br'), t(' '), this.OK = new Activateable('OK', this.end)));
      if (this.i === 1) {
        $(this.image).css(DATA.CSS['flip-horizontal']);
      }
      $(this.dom).css({
        background: 'url(images/grey.png)',
        position: 'absolute',
        padding: '5px',
        width: '250px'
      });
      $(this.imageContainer).css({
        border: '1px dashed black',
        background: 'url(images/grey.png)',
        overflow: 'hidden',
        minHeight: '80px',
        position: 'relative',
        margin: '20px',
        padding: '10px',
        position: 'relative'
      });
      $(shipNameDiv).css({
        left: '5px',
        bottom: '2px',
        position: 'absolute',
        background: 'url(images/grey.png)'
      });
      $(OKCont).css({
        textAlign: 'right'
      });
      $(this.image).css({
        position: 'absolute'
      });
      $(this.dom).hide();
      this.selectables = s = new CircularArray([this.presetName, this.shipName, this.CPU, this.OK]);
      this.selectables.get().select();
    }

    ShipEditor.prototype._continue = function() {
      if (this.started) {
        $(this.dom).show();
        return $(document).on('keydown', this.keyHandler);
      }
    };

    ShipEditor.prototype._break = function() {
      if (this.started) {
        $(this.dom).hide();
        return $(document).off('keydown', this.keyHandler);
      }
    };

    ShipEditor.prototype.start = function(callback) {
      this.callback = callback;
      this.started = true;
      this.cpu = false;
      $(this.dom).show();
      return $(document).on('keydown', this.keyHandler);
    };

    ShipEditor.prototype.end = function() {
      this.started = false;
      $(this.dom).hide();
      $(document).off('keydown', this.keyHandler);
      return this.callback(this.getData());
    };

    ShipEditor.prototype.setData = function(data) {
      var actions, i, img, name, preset, presetNames, property, shipNames, _i, _j, _len, _len1,
        _this = this;
      this.data = data;
      this.images = [];
      presetNames = (function() {
        var _i, _len, _results;
        _results = [];
        for (_i = 0, _len = data.length; _i < _len; _i++) {
          preset = data[_i];
          _results.push(preset.presetName);
        }
        return _results;
      })();
      this.presetName.setTexts(presetNames);
      actions = [];
      for (i = _i = 0, _len = presetNames.length; _i < _len; i = ++_i) {
        name = presetNames[i];
        actions.push((function(v) {
          return function() {
            return _this.setPreset(v);
          };
        })(i));
      }
      this.presetName.setActions(actions);
      shipNames = (function() {
        var _j, _len1, _results;
        _results = [];
        for (_j = 0, _len1 = data.length; _j < _len1; _j++) {
          preset = data[_j];
          _results.push(preset.shipName);
        }
        return _results;
      })();
      this.shipName.setTexts(shipNames);
      actions = [];
      for (i = _j = 0, _len1 = shipNames.length; _j < _len1; i = ++_j) {
        name = shipNames[i];
        img = new Image;
        img.src = 'images/' + name + '.png';
        this.images.push(img);
        actions.push((function(index) {
          return function() {
            _this.image.src = _this.images[index].src;
            return _this.image.onload = function() {
              return _this.centerImage();
            };
          };
        })(i));
      }
      this.shipName.setActions(actions);
      for (property in data[0].properties) {
        this.properties.add(property);
        this.selectables.addBefore(this.selectables.array.length - 2, this.properties[property].txt);
      }
      return this.setPreset(0);
    };

    ShipEditor.prototype.setPreset = function(i) {
      var preset, property, value, _ref, _results,
        _this = this;
      preset = this.data[i];
      this.presetName.setData(preset.presetName);
      this.image.src = this.images[i].src;
      this.image.onload = function() {
        return _this.centerImage();
      };
      this.shipName.setData(preset.shipName);
      _ref = preset.properties;
      _results = [];
      for (property in _ref) {
        value = _ref[property];
        _results.push(this.properties[property].txt.setData(value));
      }
      return _results;
    };

    ShipEditor.prototype.getData = function() {
      var data, property;
      data = {};
      data.presetName = this.presetName.texts.get();
      data.shipName = this.shipName.texts.get();
      for (property in this.data[0].properties) {
        data[property] = this.properties[property].txt.getData();
      }
      data.cpu = this.cpu;
      return data;
    };

    ShipEditor.prototype.centerImage = function() {
      this.image.style.left = (($(this.imageContainer).innerWidth() / 2) - (this.image.width / 2)) + 'px';
      return this.image.style.top = (($(this.imageContainer).innerHeight() / 2) - (this.image.height / 2)) + 'px';
    };

    return ShipEditor;

  })();

  KeyChanger = (function() {
    function KeyChanger() {
      this.dom = c('div');
      $(this.dom).css({
        position: 'absolute',
        textAlign: 'center',
        left: '50%',
        top: '50%',
        width: '500px',
        marginLeft: '-250px',
        border: '1px dashed black',
        background: 'url(images/grey.png)'
      });
      $(this.dom).hide();
    }

    KeyChanger.prototype.setKey = function() {
      var _this = this;
      $(this.dom).text('Push a key for \"' + this.names[this.i] + '\"');
      return $(document).one('keydown', (function(e) {
        _this.nameKeyPairs[_this.names[_this.i]] = e.which;
        _this.i++;
        if (_this.i === _this.names.length) {
          return _this.end();
        } else {
          return _this.setKey();
        }
      }));
    };

    KeyChanger.prototype.start = function(nameKeyPairs, callback) {
      var name;
      this.nameKeyPairs = nameKeyPairs;
      this.callback = callback;
      this.names = (function() {
        var _results;
        _results = [];
        for (name in nameKeyPairs) {
          _results.push(name);
        }
        return _results;
      })();
      this.i = 0;
      this.setKey();
      return $(this.dom).show();
    };

    KeyChanger.prototype.end = function() {
      $(this.dom).hide();
      return this.callback();
    };

    return KeyChanger;

  })();

  Menu = (function() {
    function Menu() {
      var c1, c2,
        _this = this;
      this.dom = c('div');
      $(this.dom).css({
        width: '500px',
        position: 'absolute',
        left: '50%',
        top: '50px',
        marginLeft: '-250px',
        textAlign: 'center',
        background: 'url(images/grey.png)'
      });
      $(this.dom).hide();
      this.keyHandlerMenu = function(e) {
        if (e.which === 80) {
          return _this.end();
        }
      };
      c1 = DATA.action_keyCode_maps[0];
      c2 = DATA.action_keyCode_maps[1];
      this.keyHandler = function(e) {
        var _base, _base1;
        switch (e.which) {
          case c1.down:
          case c2.down:
            _this.selectables.get().deselect();
            _this.selectables.next();
            return _this.selectables.get().select();
          case c1.up:
          case c2.up:
            _this.selectables.get().deselect();
            _this.selectables.previous();
            return _this.selectables.get().select();
          case c1.right:
          case c2.right:
            return typeof (_base = _this.selectables.get()).next === "function" ? _base.next() : void 0;
          case c1.left:
          case c2.left:
            return typeof (_base1 = _this.selectables.get()).previous === "function" ? _base1.previous() : void 0;
        }
      };
      this._continue = function() {
        $(_this.dom).show();
        $(document).on('keydown', _this.keyHandler);
        return $(document).on('keydown', _this.keyHandlerMenu);
      };
      this.start = function(callback) {
        _this.callback = callback;
        return _this._continue();
      };
      this._break = function() {
        $(_this.dom).hide();
        $(document).off('keydown', _this.keyHandler);
        return $(document).off('keydown', _this.keyHandlerMenu);
      };
      this.end = function() {
        _this._break();
        return _this.callback();
      };
    }

    Menu.prototype.setData = function(texts, actions) {
      var i, _i, _ref, _ref1;
      this.texts = texts;
      this.actions = actions;
      $(this.dom).empty();
      this.activateables = [];
      for (i = _i = 0, _ref = this.texts.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
        this.activateables.push(new Activateable(this.texts[i], (_ref1 = this.actions) != null ? _ref1[i] : void 0));
        $(this.dom).append(this.activateables[i].dom);
        $(this.dom).append(c('br'));
        $(this.dom).append(c('br'));
      }
      this.selectables = new CircularArray(this.activateables);
      return this.selectables.get().select();
    };

    return Menu;

  })();

  ComputerPlayer = (function() {
    function ComputerPlayer(ship) {
      this.ship = ship;
      this.down = __bind(this.down, this);
      this.up = __bind(this.up, this);
      this.stop = __bind(this.stop, this);
      this.tick = __bind(this.tick, this);
      this.enemy = this.ship.enemy;
      this.i = 0;
      this.mode = '';
      this.backToCenterModeLength = 25;
      this.timer = new Timer(this.tick, 1);
    }

    ComputerPlayer.prototype.tick = function() {
      this.ship.inputState.right = true;
      if (this.i > 0) {
        this.i -= 1;
        return this[this.mode]();
      } else {
        if (this.isInDanger()) {
          return this.avoid();
        } else {
          return this.hunt();
        }
      }
    };

    ComputerPlayer.prototype.isInDanger = function() {
      var shoot, _i, _len, _ref;
      _ref = this.enemy.shoots;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        shoot = _ref[_i];
        if (shoot.y + Shoot.size > this.ship.y && shoot.y < this.ship.y + this.ship.height) {
          return true;
        }
      }
      return false;
    };

    ComputerPlayer.prototype.hunt = function() {
      var enemyMiddle, shipMiddle;
      enemyMiddle = this.enemy.y + (this.enemy.height / 2);
      shipMiddle = this.ship.y + (this.ship.height / 2);
      if (shipMiddle > enemyMiddle + 20) {
        return this.up();
      } else if (shipMiddle < enemyMiddle - 20) {
        return this.down();
      } else {
        return this.stop();
      }
    };

    ComputerPlayer.prototype.avoid = function() {
      var shipMiddle, shoot, shootMiddle;
      shoot = this.enemy.shoots[0];
      shootMiddle = shoot.y + (Shoot.size / 2);
      shipMiddle = this.ship.y + (this.ship.height / 2);
      if (shootMiddle < shipMiddle) {
        if (this.isPossibleMove('down')) {
          this.i = 15;
          return this.mode = 'down';
        } else {
          return this.backToCenterMode('up');
        }
      } else if (shootMiddle > shipMiddle) {
        if (this.isPossibleMove('up')) {
          this.i = 15;
          return this.mode = 'up';
        } else {
          return this.backToCenterMode('down');
        }
      } else {
        return choose([this.up, this.down])();
      }
    };

    ComputerPlayer.prototype.isPossibleMove = function(move) {
      switch (move) {
        case 'up':
          return this.ship.y - this.ship.data.speed > 0;
        case 'down':
          return this.ship.y + this.ship.height + this.ship.data.speed < DATA.areaHeight;
      }
    };

    ComputerPlayer.prototype.backToCenterMode = function(mode) {
      this.i = this.backToCenterModeLength;
      return this.mode = mode;
    };

    ComputerPlayer.prototype.stop = function() {
      this.ship.inputState.up = false;
      this.ship.inputState.down = false;
      return this.ship.inputState.last = '';
    };

    ComputerPlayer.prototype.up = function() {
      this.ship.inputState.up = true;
      this.ship.inputState.down = false;
      return this.ship.inputState.last = 'up';
    };

    ComputerPlayer.prototype.down = function() {
      this.ship.inputState.up = false;
      this.ship.inputState.down = true;
      return this.ship.inputState.last = 'down';
    };

    return ComputerPlayer;

  })();

  InputState = (function() {
    function InputState(action_keyCode_map) {
      var action, keyCode, _ref;
      this.action_keyCode_map = action_keyCode_map;
      this.eventHandlerUp = __bind(this.eventHandlerUp, this);
      this.eventHandlerDown = __bind(this.eventHandlerDown, this);
      this.keyEvent = __bind(this.keyEvent, this);
      this.keyCode_action_map = {};
      _ref = this.action_keyCode_map;
      for (action in _ref) {
        keyCode = _ref[action];
        this[action] = false;
        this.keyCode_action_map[keyCode] = action;
      }
    }

    InputState.prototype.keyEvent = function(e, isDown) {
      var action, keyCode;
      keyCode = e.which;
      action = this.keyCode_action_map[keyCode];
      if (action != null) {
        this[action] = isDown;
        return this.last = action;
      }
    };

    InputState.prototype.eventHandlerDown = function(e) {
      return this.keyEvent(e, true);
    };

    InputState.prototype.eventHandlerUp = function(e) {
      return this.keyEvent(e, false);
    };

    InputState.prototype.addHandler = function() {
      $(document).on('keydown', this.eventHandlerDown);
      return $(document).on('keyup', this.eventHandlerUp);
    };

    InputState.prototype.removeHandler = function() {
      $(document).off('keydown', this.eventHandlerDown);
      $(document).off('keyup', this.eventHandlerUp);
      return this.allFalse();
    };

    InputState.prototype.allFalse = function() {
      var action;
      for (action in this.action_keyCode_map) {
        this[action] = false;
      }
      return this.last = '';
    };

    return InputState;

  })();

  Fight = (function() {
    function Fight(ships, menu, ani) {
      var ship, _i, _len, _ref;
      this.ships = ships;
      this.menu = menu;
      this.ani = ani;
      this.end = __bind(this.end, this);
      this.socketTickListener = __bind(this.socketTickListener, this);
      this.waitForShoots = __bind(this.waitForShoots, this);
      this.keyHandlerMenu = __bind(this.keyHandlerMenu, this);
      this.waitingForShoots = false;
      this.computerPlayers = [];
      _ref = this.ships;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        ship = _ref[_i];
        this.computerPlayers.push(new ComputerPlayer(ship));
      }
    }

    Fight.prototype.keyHandlerMenu = function(e) {
      var _this = this;
      if (e.which === 80) {
        this._break();
        return this.menu.start(function() {
          return _this.ani.play(function() {
            return _this._continue();
          });
        });
      }
    };

    Fight.prototype.waitForShoots = function(e) {
      var destroyedShip, ship, _i, _len, _ref,
        _this = this;
      if (!this.waitingForShoots) {
        this.waitingForShoots = true;
        _ref = this.ships;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          ship = _ref[_i];
          ship.removeHandler();
        }
        destroyedShip = e.target;
        if (destroyedShip.shoots.length > 0) {
          return $(destroyedShip).one('noShoots', function() {
            return _this.end();
          });
        } else {
          return this.end();
        }
      }
    };

    Fight.prototype.socketTickListener = function(inputState0, inputState1) {
      this.inputState0 = inputState0;
      this.inputState1 = inputState1;
      return RES.mainTimer.tick();
    };

    Fight.prototype.start = function(callback) {
      var i, ship, _i, _j, _len, _len1, _ref, _ref1;
      this.callback = callback;
      _ref = this.ships;
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        ship = _ref[i];
        if (ship.data.cpu) {
          RES.mainTimer.add(this.computerPlayers[i].timer);
        }
      }
      RES.mainTimer.start();
      _ref1 = this.ships;
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        ship = _ref1[_j];
        $(ship).one('destroyed', this.waitForShoots);
      }
      if (DATA.mode === 'online') {
        RES.socket.on('tick', this.socketTickListener);
      }
      return $(document).on('keydown', this.keyHandlerMenu);
    };

    Fight.prototype.end = function() {
      var computerPlayer, ship, shoot, shoots, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4,
        _this = this;
      _ref = this.computerPlayers;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        computerPlayer = _ref[_i];
        RES.mainTimer.remove(computerPlayer.timer);
      }
      _ref1 = this.ships;
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        ship = _ref1[_j];
        ship.removeHandler();
      }
      _ref2 = this.ships;
      for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
        ship = _ref2[_k];
        shoots = ship.shoots.slice();
        for (_l = 0, _len3 = shoots.length; _l < _len3; _l++) {
          shoot = shoots[_l];
          if (shoot != null) {
            shoot.remove();
          }
        }
      }
      this.waitingForShoots = false;
      RES.mainTimer.stop();
      if ((_ref3 = RES.socket) != null) {
        _ref3.removeListener('tick', this.socketTickListener);
      }
      $(document).off('keydown', this.keyHandlerMenu);
      _ref4 = this.ships;
      for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) {
        ship = _ref4[_m];
        $(ship).off('destroyed', this.waitForShoots);
      }
      this.ships[0].destroyed = false;
      this.ships[1].destroyed = false;
      return setTimeout((function() {
        return _this.callback();
      }), 500);
    };

    Fight.prototype._break = function() {
      var _ref;
      $(document).off('keydown', this.keyHandlerMenu);
      if (DATA.mode === 'offline') {
        return RES.mainTimer.stop();
      } else {
        return (_ref = RES.socket) != null ? _ref.removeListener('tick', this.socketTickListener) : void 0;
      }
    };

    Fight.prototype._continue = function() {
      $(document).on('keydown', this.keyHandlerMenu);
      if (DATA.mode === 'offline') {
        return RES.mainTimer.start();
      } else if (DATA.mode === 'online') {
        return RES.socket.on('tick', this.socketTickListener);
      }
    };

    return Fight;

  })();

  Lobby = (function() {
    function Lobby(menu, ships, editors) {
      this.menu = menu;
      this.ships = ships;
      this.editors = editors;
      this._continue = __bind(this._continue, this);
      this._break = __bind(this._break, this);
      this.end = __bind(this.end, this);
      this.keyHandlerMenu = __bind(this.keyHandlerMenu, this);
    }

    Lobby.prototype.keyHandlerMenu = function(e) {
      var _this = this;
      if (e.which === 80) {
        this._break();
        return this.menu.start(function() {
          return _this._continue();
        });
      }
    };

    Lobby.prototype.start = function(callback) {
      var i, ready, ship, _i, _len, _ref,
        _this = this;
      this.callback = callback;
      ready = 0;
      _ref = this.ships;
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        ship = _ref[i];
        $(ship.dom).hide();
        $(ship.bar.dom).hide();
        this.editors[i].start((function(index) {
          return function(data) {
            var editedShip;
            editedShip = _this.ships[index];
            editedShip.setData(data);
            $(editedShip.dom).show();
            $(editedShip.bar.dom).show();
            ready += 1;
            if (ready === _this.ships.length) {
              return _this.end();
            }
          };
        })(i));
      }
      return $(document).on('keydown', this.keyHandlerMenu);
    };

    Lobby.prototype.end = function() {
      $(document).off('keydown', this.keyHandlerMenu);
      return this.callback();
    };

    Lobby.prototype._break = function() {
      var editor, _i, _len, _ref, _results;
      $(document).off('keydown', this.keyHandlerMenu);
      _ref = this.editors;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        editor = _ref[_i];
        _results.push(editor._break());
      }
      return _results;
    };

    Lobby.prototype._continue = function() {
      var editor, _i, _len, _ref, _results;
      $(document).on('keydown', this.keyHandlerMenu);
      _ref = this.editors;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        editor = _ref[_i];
        _results.push(editor._continue());
      }
      return _results;
    };

    return Lobby;

  })();

  Ship = (function() {
    function Ship(i) {
      var _this = this;
      this.i = i;
      this.dom = c('img');
      this.bar = new ColorBar(300, this.i);
      if (this.i === 1) {
        $(this.dom).css(DATA.CSS['flip-horizontal']);
      }
      this.shoots = [];
      this.laserLoading = false;
      this.inputState = new InputState(DATA.action_keyCode_maps[this.i]);
      this.handler = function() {
        if (_this.inputState.up && !_this.inputState.down) {
          _this.up();
        } else if (!_this.inputState.up && _this.inputState.down) {
          _this.down();
        } else if (_this.inputState.up && _this.inputState.down) {
          if (_this.inputState.last === 'up') {
            _this.up();
          } else if (_this.inputState.last === 'down') {
            _this.down();
          }
        }
        if (_this.inputState.right) {
          return _this.shoot();
        }
      };
      this.timer = new Timer(this.handler, 1);
    }

    Ship.prototype.addHandler = function() {
      this.inputState.addHandler();
      return RES.mainTimer.add(this.timer);
    };

    Ship.prototype.removeHandler = function() {
      this.inputState.removeHandler();
      return RES.mainTimer.remove(this.timer);
    };

    Ship.prototype.setY = function(y) {
      this.y = y;
      return this.dom.style.top = this.y + 'px';
    };

    Ship.prototype.up = function() {
      return this.setY(Math.max(this.y - this.data.speed, 0));
    };

    Ship.prototype.down = function() {
      return this.setY(Math.min(this.y + this.data.speed, DATA.areaHeight - this.height));
    };

    Ship.prototype.shoot = function() {
      var shoot,
        _this = this;
      if (!this.laserLoading) {
        shoot = new Shoot(this, this.i);
        this.shoots.push(shoot);
        $(shoot).one('destroyed', function() {
          removeFromArray(shoot, _this.shoots);
          if (_this.shoots.length === 0) {
            return $(_this).trigger('noShoots');
          }
        });
        this.laserLoading = true;
        return RES.mainTimer.add(new Timer((function() {
          return _this.laserLoading = false;
        }), this.data.rof, false));
      }
    };

    Ship.prototype.takeDamage = function(damage) {
      this.energy -= damage;
      this.bar.update(this.energy, this.data.energy);
      if (this.energy <= 0) {
        if (!this.destroyed) {
          return this.explode();
        }
      }
    };

    Ship.prototype.explode = function() {
      var ani,
        _this = this;
      this.inputState.removeHandler();
      this.inputState.allFalse();
      this.destroyed = true;
      $(this).trigger('destroyed');
      $(this.dom).hide();
      ani = new ImageAnimation(RES.images.explosion, 4, 8, true);
      $(ani.dom).css({
        position: 'absolute',
        top: (this.y + (this.height / 2) - (ani.dom.height / 2)) + 'px',
        left: (this.x + (this.width / 2) - (ani.dom.width / 2)) + 'px'
      });
      $(this.dom.parentNode).prepend(ani.dom);
      return ani.play(function() {
        return _this.dom.parentNode.removeChild(ani.dom);
      });
    };

    Ship.prototype.setData = function(data) {
      var _this = this;
      this.data = data;
      this.dom.src = 'images/' + this.data.shipName + '.png';
      this.dom.onload = function() {
        _this.width = _this.dom.width;
        _this.height = _this.dom.height;
        $(_this.dom).css({
          top: (_this.y = (DATA.areaHeight / 2) - (_this.height / 2)) + 'px',
          position: 'absolute'
        });
        if (_this.i === 0) {
          return $(_this.dom).css({
            left: (_this.x = DATA.shipAreaGap) + 'px'
          });
        } else {
          return $(_this.dom).css({
            left: (_this.x = DATA.areaWidth - DATA.shipAreaGap - _this.width) + 'px'
          });
        }
      };
      this.energy = this.data.energy;
      return this.bar.update(this.energy, this.data.energy);
    };

    return Ship;

  })();

  Shoot = (function() {
    Shoot.speed = 40;

    Shoot.size = 5;

    function Shoot(ship, i) {
      this.ship = ship;
      this.i = i;
      this.moveLeft = __bind(this.moveLeft, this);
      this.moveRight = __bind(this.moveRight, this);
      this.dom = c('div');
      $(this.dom).css({
        width: Shoot.size + 'px',
        height: Shoot.size + 'px',
        position: 'absolute',
        background: 'black'
      });
      if (this.i === 0) {
        this.direction = 1;
        this.setX(this.ship.width + DATA.shipAreaGap);
        this.timer = new Timer(this.moveRight, 1);
      } else {
        this.direction = -1;
        this.setX(DATA.areaWidth - DATA.shipAreaGap - this.ship.width - Shoot.size);
        this.timer = new Timer(this.moveLeft, 1);
      }
      this.y = this.ship.y + (this.ship.height / 2) - (Shoot.size / 2);
      this.dom.style.top = this.y + 'px';
      this.ship.dom.parentNode.appendChild(this.dom);
      RES.mainTimer.add(this.timer);
    }

    Shoot.prototype.setX = function(x) {
      this.x = x;
      return this.dom.style.left = x + 'px';
    };

    Shoot.prototype.moveRight = function() {
      this.setX(this.x += Shoot.speed);
      if (this.x > DATA.areaWidth) {
        return this.remove();
      } else {
        if (this.isCollisionRight()) {
          this.ship.enemy.takeDamage(this.ship.data.damage);
          return this.remove();
        }
      }
    };

    Shoot.prototype.moveLeft = function() {
      this.setX(this.x -= Shoot.speed);
      if (this.x < -Shoot.size) {
        return this.remove();
      } else {
        if (this.isCollisionLeft()) {
          this.ship.enemy.takeDamage(this.ship.data.damage);
          return this.remove();
        }
      }
    };

    Shoot.prototype.isCollisionRight = function() {
      if ((this.x + Shoot.size) > (DATA.areaWidth - this.ship.enemy.width - Shoot.size)) {
        if ((this.y + Shoot.size) > this.ship.enemy.y && this.y < (this.ship.enemy.y + this.ship.enemy.height)) {
          return true;
        }
      }
      return false;
    };

    Shoot.prototype.isCollisionLeft = function() {
      if (this.x < (this.ship.enemy.width + Shoot.size)) {
        if ((this.y + Shoot.size) > this.ship.enemy.y && this.y < (this.ship.enemy.y + this.ship.enemy.height)) {
          return true;
        }
      }
      return false;
    };

    Shoot.prototype.remove = function() {
      if ($.contains(this.ship.dom.parentNode, this.dom)) {
        this.ship.dom.parentNode.removeChild(this.dom);
      }
      RES.mainTimer.remove(this.timer);
      $(this).trigger('destroyed');
      return delete this;
    };

    return Shoot;

  })();

  ImageAnimation = (function() {
    function ImageAnimation(images, speed, fnAt, ownTimer, repeat) {
      var _this = this;
      this.images = images;
      this.speed = speed;
      this.ownTimer = ownTimer;
      this.repeat = repeat != null ? repeat : false;
      this.fnAt = fnAt != null ? fnAt : this.images.length;
      this.dom = c('img');
      this.dom.src = this.images[0].src;
      this.i = 1;
      this.timer = new Timer((function() {
        if (_this.i != null) {
          _this.i += 1;
          _this.dom.src = _this.images[_this.i - 1].src;
          if (_this.i === _this.fnAt) {
            _this.callback();
          }
          if (_this.i === _this.images.length) {
            if (_this.repeat) {
              return _this.i = 0;
            } else {
              return _this.stop();
            }
          }
        }
      }), this.speed);
      if (this.ownTimer) {
        this.mainTimer = new MainTimer;
        this.mainTimer.add(this.timer);
      }
      $(this.dom).hide();
    }

    ImageAnimation.prototype.play = function(callback) {
      this.callback = callback;
      $(this.dom).show();
      if (this.ownTimer) {
        return this.mainTimer.start();
      } else {
        return RES.mainTimer.add(this.timer);
      }
    };

    ImageAnimation.prototype.stop = function() {
      this.i = 0;
      if (this.ownTimer) {
        return this.mainTimer.stop();
      } else {
        return RES.mainTimer.remove(this.timer);
      }
    };

    ImageAnimation.prototype.pause = function() {
      if (this.ownTimer) {
        return this.mainTimer.stop();
      } else {
        return RES.mainTimer.remove(this.timer);
      }
    };

    return ImageAnimation;

  })();

  $(document).ready(function() {
    var $bar, $editor, action_keyCode_maps, ani, area, cycle, directions, editor, editors, fight, getHandler, i, keyChanger, lobby, map, menu, player, players, ship, ships, _i;
    area = c('div');
    $(area).css({
      top: '50%',
      marginTop: -(DATA.areaHeight / 2) + 'px',
      left: '50%',
      marginLeft: -(DATA.areaWidth / 2) + 'px',
      background: 'url(images/grey.png)',
      height: DATA.areaHeight,
      width: DATA.areaWidth,
      position: 'fixed',
      overflow: 'hidden',
      fontFamily: '"Lucida Console", Monaco, monospace'
    });
    keyChanger = new KeyChanger;
    action_keyCode_maps = DATA.action_keyCode_maps;
    menu = new Menu;
    getHandler = function(action_keyCode_map) {
      return function() {
        menu._break();
        return keyChanger.start(action_keyCode_map, function() {
          return menu._continue();
        });
      };
    };
    menu.setData((function() {
      var _i, _len, _results;
      _results = [];
      for (i = _i = 0, _len = action_keyCode_maps.length; _i < _len; i = ++_i) {
        map = action_keyCode_maps[i];
        _results.push('change keys ' + i);
      }
      return _results;
    })(), (function() {
      var _i, _len, _results;
      _results = [];
      for (_i = 0, _len = action_keyCode_maps.length; _i < _len; _i++) {
        map = action_keyCode_maps[_i];
        _results.push(getHandler(map));
      }
      return _results;
    })());
    players = 2;
    ships = [];
    editors = [];
    directions = ['left', 'right'];
    for (player = _i = 0; 0 <= players ? _i < players : _i > players; player = 0 <= players ? ++_i : --_i) {
      editors.push(editor = new ShipEditor(player, action_keyCode_maps[player]));
      editor.setData(DATA.presets);
      $editor = $(editor.dom);
      $editor.css(directions[player], '20px');
      $editor.css('top', '20px');
      ships.push(ship = new Ship(player));
      $bar = $(ship.bar.dom);
      $bar.css({
        position: 'absolute',
        top: '5px'
      });
      $bar.css(directions[player], '5px');
    }
    ships[0].enemy = ships[1];
    ships[1].enemy = ships[0];
    lobby = new Lobby(menu, ships, editors);
    ani = new ImageAnimation(RES.images.countdown, 4, 19, true);
    $(ani.dom).css({
      position: 'absolute',
      left: '50%',
      top: '50%',
      marginLeft: -(ani.images.width / 2) + 'px',
      marginTop: -(ani.images.height / 2) + 'px'
    });
    fight = new Fight(ships, menu, ani);
    c(area, keyChanger, menu, ships[0], ships[1], ships[0].bar, ships[1].bar, editors[0], editors[1], ani);
    cycle = function() {
      return lobby.start(function() {
        var _j, _len;
        for (_j = 0, _len = ships.length; _j < _len; _j++) {
          ship = ships[_j];
          ship.addHandler();
        }
        return ani.play(function() {
          if (DATA.mode === 'online') {
            RES.socket.emit('fight');
          }
          return fight.start(function() {
            return cycle();
          });
        });
      });
    };
    cycle();
    $('body').append(area);
    return $('body').css({
      background: 'url(images/grey.png)'
    });
  });

}).call(this);


text:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Utilizador
 */
public class SequenceTest {

    public static void main(String[] args) {
        Sequence sequence = new Sequence();
        sequence.displaySequence();
    }

}

redpajama_set_name: RedPajamaGithub


text:
using Hylasoft.Opc.Common;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Hylasoft.Opc.Ua
{
  /// <summary>
  /// Client Implementation for UA
  /// </summary>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling",
    Justification = "Doesn't make sense to split this class")]
  public class UaClient : IClient<UaNode>
  {
    private readonly UaClientOptions _options = new UaClientOptions();
    private readonly Uri _serverUrl;
    private Session _session;

    private readonly IDictionary<string, UaNode> _nodesCache = new Dictionary<string, UaNode>();
    private readonly IDictionary<string, IList<UaNode>> _folderCache = new Dictionary<string, IList<UaNode>>();

    /// <summary>
    /// Creates a server object
    /// </summary>
    /// <param name="serverUrl">the url of the server to connect to</param>
    public UaClient(Uri serverUrl)
    {
      _serverUrl = serverUrl;
      Status = OpcStatus.NotConnected;
    }

    /// <summary>
    /// Options to configure the UA client session
    /// </summary>
    public UaClientOptions Options
    {
      get { return _options; }
    }

    private void PostInitializeSession()
    {
      var node = _session.NodeCache.Find(ObjectIds.ObjectsFolder);
      RootNode = new UaNode(string.Empty, node.NodeId.ToString());
      AddNodeToCache(RootNode);
      Status = OpcStatus.Connected;
    }

    /// <summary>
    /// Connect the client to the OPC Server
    /// </summary>
    public void Connect()
    {
      if (Status == OpcStatus.Connected)
        return;
      _session = InitializeSession(_serverUrl);
      _session.KeepAlive += SessionKeepAlive;
      _session.SessionClosing += SessionClosing;
      PostInitializeSession();
    }

    private void SessionKeepAlive(Session session, KeepAliveEventArgs e)
    {
      if (e.CurrentState != ServerState.Running)
      {
        if (Status == OpcStatus.Connected)
        {
          Status = OpcStatus.NotConnected;
          NotifyServerConnectionLost();
        }
      }
      else if (e.CurrentState == ServerState.Running)
      {
        if (Status == OpcStatus.NotConnected)
        {
          Status = OpcStatus.Connected;
          NotifyServerConnectionRestored();
        }
      }
    }

    private void SessionClosing(object sender, EventArgs e)
    {
      Status = OpcStatus.NotConnected;
      NotifyServerConnectionLost();
    }

    /// <summary>
    /// Reconnect the OPC session
    /// </summary>
    public void ReConnect()
    {
      if (Status != OpcStatus.Connected)
        return;
      Status = OpcStatus.NotConnected;
      _session.Reconnect();
      Status = OpcStatus.Connected;
    }

    /// <summary>
    /// Create a new OPC session, based on the current session parameters.
    /// </summary>
    public void RecreateSession()
    {
      if (Status != OpcStatus.Connected)
        return;

      Status = OpcStatus.NotConnected;
      _session = Session.Recreate(_session);
      PostInitializeSession();
    }

    /// <summary>
    /// Gets the current status of the OPC Client
    /// </summary>
    public OpcStatus Status { get; private set; }

    private ReadValueIdCollection BuildReadValueIdCollection(string tag, uint attributeId)
    {
      var n = FindNode(tag, RootNode);
      var readValue = new ReadValueId
      {
        NodeId = n.NodeId,
        AttributeId = attributeId
      };
      return new ReadValueIdCollection { readValue };
    }

    /// <summary>
    /// Read a tag
    /// </summary>
    /// <typeparam name="T">The type of tag to read</typeparam>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
    /// <returns>The value retrieved from the OPC</returns>
    public T Read<T>(string tag)
    {
      var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);
      DataValueCollection results;
      DiagnosticInfoCollection diag;
      _session.Read(
          requestHeader: null,
          maxAge: 0,
          timestampsToReturn: TimestampsToReturn.Neither,
          nodesToRead: nodesToRead,
          results: out results,
          diagnosticInfos: out diag);
      var val = results[0];

      CheckReturnValue(val.StatusCode);
      return (T)val.Value;
    }

    /// <summary>
    /// Read a tag asynchronously
    /// </summary>
    /// <typeparam name="T">The type of tag to read</typeparam>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
    /// <returns>The value retrieved from the OPC</returns>
    public Task<T> ReadAsync<T>(string tag)
    {
      var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);

      // Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
      var taskCompletionSource = new TaskCompletionSource<T>();
      _session.BeginRead(
          requestHeader: null,
          maxAge: 0,
          timestampsToReturn: TimestampsToReturn.Neither,
          nodesToRead: nodesToRead,
          callback: ar =>
          {
            DataValueCollection results;
            DiagnosticInfoCollection diag;
            var response = _session.EndRead(
                result: ar,
                results: out results,
                diagnosticInfos: out diag);

            try
            {
              CheckReturnValue(response.ServiceResult);
              CheckReturnValue(results[0].StatusCode);
              var val = results[0];
              taskCompletionSource.TrySetResult((T)val.Value);
            }
            catch (Exception ex)
            {
              taskCompletionSource.TrySetException(ex);
            }
          },
          asyncState: null);

      return taskCompletionSource.Task;
    }

    private WriteValueCollection BuildWriteValueCollection(string tag, uint attributeId, object dataValue)
    {
      var n = FindNode(tag, RootNode);
      var writeValue = new WriteValue
      {
        NodeId = n.NodeId,
        AttributeId = attributeId,
        Value = { Value = dataValue }
      };
      return new WriteValueCollection { writeValue };
    }

    /// <summary>
    /// Write a value on the specified opc tag
    /// </summary>
    /// <typeparam name="T">The type of tag to write on</typeparam>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
    /// <param name="item">The value for the item to write</param>
    public void Write<T>(string tag, T item)
    {
      var nodesToWrite = BuildWriteValueCollection(tag, Attributes.Value, item);

      StatusCodeCollection results;
      DiagnosticInfoCollection diag;
      _session.Write(
          requestHeader: null,
          nodesToWrite: nodesToWrite,
          results: out results,
          diagnosticInfos: out diag);

      CheckReturnValue(results[0]);
    }

    /// <summary>
    /// Write a value on the specified opc tag asynchronously
    /// </summary>
    /// <typeparam name="T">The type of tag to write on</typeparam>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
    /// <param name="item">The value for the item to write</param>
    public Task WriteAsync<T>(string tag, T item)
    {
      var nodesToWrite = BuildWriteValueCollection(tag, Attributes.Value, item);

      // Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
      var taskCompletionSource = new TaskCompletionSource<T>();
      _session.BeginWrite(
          requestHeader: null,
          nodesToWrite: nodesToWrite,
          callback: ar =>
          {
            StatusCodeCollection results;
            DiagnosticInfoCollection diag;
            var response = _session.EndWrite(
                result: ar,
                results: out results,
                diagnosticInfos: out diag);
            try
            {
              CheckReturnValue(response.ServiceResult);
              CheckReturnValue(results[0]);
            }
            catch (Exception ex)
            {
              taskCompletionSource.TrySetException(ex);
            }
          },
          asyncState: null);
      return taskCompletionSource.Task;
    }

    /// <summary>
    /// Monitor the specified tag for changes
    /// </summary>
    /// <typeparam name="T">the type of tag to monitor</typeparam>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` monitors the tag `bar` on the folder `foo`</param>
    /// <param name="callback">the callback to execute when the value is changed.
    /// The first parameter is the new value of the node, the second is an `unsubscribe` function to unsubscribe the callback</param>
    public void Monitor<T>(string tag, Action<T, Action> callback)
    {
      var node = FindNode(tag);

      var sub = new Subscription
      {
        PublishingInterval = _options.DefaultMonitorInterval,
        PublishingEnabled = true,
        DisplayName = tag,
        Priority = byte.MaxValue
      };

      var item = new MonitoredItem
      {
        StartNodeId = node.NodeId,
        AttributeId = Attributes.Value,
        DisplayName = tag,
        SamplingInterval = _options.DefaultMonitorInterval,
      };
      sub.AddItem(item);
      _session.AddSubscription(sub);
      sub.Create();
      sub.ApplyChanges();

      item.Notification += (monitoredItem, args) =>
      {
        var p = (MonitoredItemNotification)args.NotificationValue;
        var t = p.Value.WrappedValue.Value;
        Action unsubscribe = () =>
        {
          sub.RemoveItems(sub.MonitoredItems);
          sub.Delete(true);
          _session.RemoveSubscription(sub);
          sub.Dispose();
        };
        callback((T)t, unsubscribe);
      };
    }

    /// <summary>
    /// Explore a folder on the Opc Server
    /// </summary>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` finds the sub nodes of `bar` on the folder `foo`</param>
    /// <returns>The list of sub-nodes</returns>
    public IEnumerable<UaNode> ExploreFolder(string tag)
    {
      IList<UaNode> nodes;
      _folderCache.TryGetValue(tag, out nodes);
      if (nodes != null)
        return nodes;

      var folder = FindNode(tag);
      nodes = ClientUtils.Browse(_session, folder.NodeId)
        .GroupBy(n => n.NodeId) //this is to select distinct
        .Select(n => n.First())
        .Where(n => n.NodeClass == NodeClass.Variable || n.NodeClass == NodeClass.Object)
        .Select(n => n.ToHylaNode(folder))
        .ToList();

      //add nodes to cache
      _folderCache.Add(tag, nodes);
      foreach (var node in nodes)
        AddNodeToCache(node);

      return nodes;
    }

    /// <summary>
    /// Explores a folder asynchronously
    /// </summary>
    public async Task<IEnumerable<Common.Node>> ExploreFolderAsync(string tag)
    {
      return await Task.Run(() => ExploreFolder(tag));
    }

    /// <summary>
    /// Finds a node on the Opc Server
    /// </summary>
    /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
    /// E.g: the tag `foo.bar` finds the tag `bar` on the folder `foo`</param>
    /// <returns>If there is a tag, it returns it, otherwise it throws an </returns>
    public UaNode FindNode(string tag)
    {
      // if the tag already exists in cache, return it
      if (_nodesCache.ContainsKey(tag))
        return _nodesCache[tag];

      // try to find the tag otherwise
      var found = FindNode(tag, RootNode);
      if (found != null)
      {
        AddNodeToCache(found);
        return found;
      }

      // throws an exception if not found
      throw new OpcException(string.Format("The tag \"{0}\" doesn't exist on the Server", tag));
    }

    /// <summary>
    /// Find node asynchronously
    /// </summary>
    public async Task<Common.Node> FindNodeAsync(string tag)
    {
      return await Task.Run(() => FindNode(tag));
    }

    /// <summary>
    /// Gets the root node of the server
    /// </summary>
    public UaNode RootNode { get; private set; }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
      if (_session != null)
      {
        _session.RemoveSubscriptions(_session.Subscriptions);
        _session.Close();
        _session.Dispose();
      }
      GC.SuppressFinalize(this);
    }

    private void CheckReturnValue(StatusCode status)
    {
      if (!StatusCode.IsGood(status))
        throw new OpcException(string.Format("Invalid response from the server. (Response Status: {0})", status), status);
    }

    /// <summary>
    /// Adds a node to the cache using the tag as its key
    /// </summary>
    /// <param name="node">the node to add</param>
    private void AddNodeToCache(UaNode node)
    {
      if (!_nodesCache.ContainsKey(node.Tag))
        _nodesCache.Add(node.Tag, node);
    }

    /// <summary>
    /// Crappy method to initialize the session. I don't know what many of these things do, sincerely.
    /// </summary>
    private Session InitializeSession(Uri url)
    {
      var certificateValidator = new CertificateValidator();
      certificateValidator.CertificateValidation += (sender, eventArgs) =>
      {
        if (ServiceResult.IsGood(eventArgs.Error))
          eventArgs.Accept = true;
        else if ((eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted) && _options.AutoAcceptUntrustedCertificates)
          eventArgs.Accept = true;
        else
          throw new OpcException(string.Format("Failed to validate certificate with error code {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo), eventArgs.Error.StatusCode);
      };

      // Build the application configuration
      var appInstance = new ApplicationInstance
      {
        ApplicationType = ApplicationType.Client,
        ConfigSectionName = _options.ConfigSectionName,
        ApplicationConfiguration = new ApplicationConfiguration
        {
          ApplicationUri = url.ToString(),
          ApplicationName = _options.ApplicationName,
          ApplicationType = ApplicationType.Client,
          CertificateValidator = certificateValidator,
          SecurityConfiguration = new SecurityConfiguration
          {
            AutoAcceptUntrustedCertificates = _options.AutoAcceptUntrustedCertificates
          },
          TransportQuotas = new TransportQuotas
          {
            OperationTimeout = 600000,
            MaxStringLength = 1048576,
            MaxByteStringLength = 1048576,
            MaxArrayLength = 65535,
            MaxMessageSize = 4194304,
            MaxBufferSize = 65535,
            ChannelLifetime = 300000,
            SecurityTokenLifetime = 3600000
          },
          ClientConfiguration = new ClientConfiguration
          {
            DefaultSessionTimeout = 60000,
            MinSubscriptionLifetime = 10000
          },
          DisableHiResClock = true
        }
      };

      // Assign a application certificate (when specified)
      if (_options.ApplicationCertificate != null)
        appInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(_options.ApplicationCertificate);

      // Find the endpoint to be used
      var endpoints = ClientUtils.SelectEndpoint(url, _options.UseMessageSecurity);

      // Create the OPC session:
      var session = Session.Create(
          configuration: appInstance.ApplicationConfiguration,
          endpoint: new ConfiguredEndpoint(
              collection: null,
              description: endpoints,
              configuration: EndpointConfiguration.Create(applicationConfiguration: appInstance.ApplicationConfiguration)),
          updateBeforeConnect: false,
          checkDomain: false,
          sessionName: _options.SessionName,
          sessionTimeout: _options.SessionTimeout,
          identity: null,
          preferredLocales: new string[] { });

      return session;
    }

    /// <summary>
    /// Finds a node starting from the specified node as the root folder
    /// </summary>
    /// <param name="tag">the tag to find</param>
    /// <param name="node">the root node</param>
    /// <returns></returns>
    private UaNode FindNode(string tag, UaNode node)
    {
      var folders = tag.Split('.');
      var head = folders.FirstOrDefault();
      UaNode found;
      try
      {
        var subNodes = ExploreFolder(node.Tag);
        found = subNodes.Single(n => n.Name == head);
      }
      catch (Exception ex)
      {
        throw new OpcException(string.Format("The tag \"{0}\" doesn't exist on folder \"{1}\"", head, node.Tag), ex);
      }

      return folders.Length == 1
        ? found // last node, return it
        : FindNode(string.Join(".", folders.Except(new[] { head })), found); // find sub nodes
    }

    private void NotifyServerConnectionLost()
    {
      if (ServerConnectionLost != null)
        ServerConnectionLost(this, EventArgs.Empty);
    }

    private void NotifyServerConnectionRestored()
    {
      if (ServerConnectionRestored != null)
        ServerConnectionRestored(this, EventArgs.Empty);
    }

    /// <summary>
    /// This event is raised when the connection to the OPC server is lost.
    /// </summary>
    public event EventHandler ServerConnectionLost;

    /// <summary>
    /// This event is raised when the connection to the OPC server is restored.
    /// </summary>
    public event EventHandler ServerConnectionRestored;

  }

}