class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach((listener) => listener(...args));
    }
  }

  removeAllListeners() {
    this.events = {};
  }
}

// Convert Node.js module system to ES6 module system
export class NotConnectedError extends Error {
  constructor(client) {
    super("Client not connected");
    this.client = client;
  }
}

export const Polarity = {
  Negative: 0,
  Positive: 1,
};

export const Region = {
  Unknown: 0,
  Europe: 1,
  Oceania: 2,
  NorthAmerica: 3,
  Asia: 4,
  SouthAmerica: 5,
};

export class Client extends EventEmitter {
  constructor(socketFactory) {
    super();
    this.socketFactory = socketFactory;
  }

  getSocket() {
    return this.socket;
  }

  connect(url = this.generateRandomConnectionUrl()) {
    const socket = (this.socket = this.socketFactory.make(url));
    socket.onmessage = (event) => {
      const rawData = this.decode(event.data);

      this.emit("data", this.buildStrikeData(JSON.parse(rawData)));
    };
    socket.onopen = () => {
      this.sendJSON({ a: 111 });
      this.emit("connect", socket);
    };
    socket.onerror = (err) => this.emit("error", err);
  }

  close() {
    if (!this.socket) {
      throw new NotConnectedError(this);
    }
    this.socket.close();
    this.removeAllListeners();
  }

  processRawLocation(location) {
    return {
      latitude: location.lat,
      longitude: location.lon,
      altitude: location.alt,
    };
  }

  buildStrikeData(strike) {
    return {
      location: this.processRawLocation(strike),
      deviation: strike.mds,
      delay: strike.delay,
      time: new Date(Math.floor(strike.time / 1e6)),
      detectors: strike.sig.map((rawDec) => ({
        id: rawDec.sta,
        location: this.processRawLocation(rawDec),
        status: rawDec.status,
        delay: rawDec.time,
      })),
      polarity: strike.pol > 0 ? Polarity.Positive : Polarity.Negative,
      maxDeviation: strike.mds,
      maxCircularGap: strike.mcg,
      region: strike.region,
    };
  }

  decode(b) {
    let a,
      e = {},
      d = b.split(""),
      c = d[0],
      f = c,
      g = [c],
      h = 256,
      o = h;

    for (let i = 1; i < d.length; i++) {
      let charCode = d[i].charCodeAt(0);
      a = h > charCode ? d[i] : e[charCode] ? e[charCode] : f + c;
      g.push(a);
      c = a.charAt(0);
      e[o] = f + c;
      o++;
      f = a;
    }

    return g.join("");
  }

  sendJSON(data) {
    this.socket.send(JSON.stringify(data));
  }

  generateRandomConnectionUrl() {
    const knownServerIds = [1, 7, 8];
    return `wss://ws${
      knownServerIds[Math.floor(Math.random() * knownServerIds.length)]
    }.blitzortung.org:443/`;
  }
}

export class BrowserSocketFactory {
  make(url) {
    return new WebSocket(url);
  }
}
