import {Injectable} from '@angular/core';
import Peer from 'peerjs';
import {DataConnection} from 'peerjs';
import {EthService} from './eth.service';
import {Contract} from 'web3-eth-contract';
import {by} from 'protractor';
import {InfoMessagesService} from './info-messages.service';

const abi = require('../assets/contract/Signaling.json');

export interface PeerMessage {
  source: string;
  data: string;
}

@Injectable({
  providedIn: 'root'
})
export class PeerService {

  peer: Peer = null;

  messages: PeerMessage[] = [];
  users: string[] = [];
  private contract: Contract = null;

  constructor(private ethService: EthService,
              private infoService: InfoMessagesService) {
    ethService.validProviderAndAccount$.subscribe(() => this.loadContract());
    infoService.infos$.subscribe(m => this.sendMessage(m));
    this.peer = new Peer(undefined, {host: 'localhost', port: 9000, path: '/block-game', secure: false});
    this.peer.on('open', id => this.connected(id));
    this.peer.on('close', () => console.log('peer:close', this.peer.id));
    this.peer.on('error', error => console.log('peer:error', error));
    this.peer.on('connection', (connection) => this.onConnection(connection));
  }

  private onConnection(connection) {
    console.log('peer:connection', connection);
    this.manageConnection(connection);
  }

  private manageConnection(connection) {
    connection.once('open', () => () => this.onConnectionOpen(connection));
    connection.once('close', () => () => this.onConnectionClose(connection));
    connection.on('data', evt => this.onData(evt));
    connection.on('error', error => console.log('connection:error', error));
    console.log('connections', this.peer.connections);
    this.users.push(connection.peer);
  }


  private onData(event) {
    console.log('connection:data', event);

    if (event.type === 'peers') {
      event.data.forEach(peerId => {
        if (!(peerId in this.peer.connections)) {
          this.openConnection(peerId);
        }
      });
    } else if (event.type === 'message') {
      this.messages.push({source: event.source, data: event.data});
    }
  }


  private onConnectionOpen(connection) {
    console.log('connection:open', connection);
    this.sendPeers(connection);
  }


  private onConnectionClose(connection) {
    console.log('connection:close', connection);
    const index = this.users.indexOf(connection.peer);
    if (index > -1) {
      this.users.splice(index, 1);
    } else {
      console.warn('peer was not in list', connection.peer);
    }
  }


  private sendPeers(connection) {
    const connections: DataConnection[][] = Object.values(this.peer.connections);
    const peerIds = connections.filter(peerConnections => peerConnections.length > 0)
      .map(peerConnections => peerConnections[0])
      .filter(peerConnections => peerConnections.peer !== connection.peer)
      .map(peerConnections => peerConnections.peer);
    const event = {
      type: 'peers',
      source: this.peer.id,
      data: peerIds,
    };
    connection.send(event);
  }


  sendMessage(text) {
    const event = {
      type: 'message',
      source: this.peer.id,
      data: text
    };
    Object.values(this.peer.connections).forEach(connections => {
      connections[0].send(event);
    });
    this.messages.push(event);
  }

  private openConnection(peerId) {
    console.log('connecting with peer', peerId);
    if (!(peerId in this.peer.connections)) {
      const connection = this.peer.connect(peerId);
      this.manageConnection(connection);
    } else {
      console.warn('allready connected to peer', peerId);
    }

  }

  private connected(id: string) {
    console.log('peer:open', id);
    if (!this.contract) {
      setTimeout(() => this.connected(id), 10);
      return;
    }
    const byte16id = this.ethService.utils.fromAscii(id);
    this.contract.methods.triggerMe(byte16id).send();
  }

  private async loadContract() {
    const address = this.ethService.contractAddresses.Signaling;
    this.contract = new this.ethService.eth.Contract(abi, address, {
      from: await this.ethService.getAccount(),
      gas: 1500000,
      gasPrice: '100000000000',
    });
    this.listenForConnections();
  }

  private listenForConnections() {
    this.contract.events.NewPartner({
      fromBlock: 0,
    }).on('data', data => {
      console.log('got event with peer connection', data);
      const hexPeerId = data.returnValues.id;
      const peerId = this.ethService.utils.hexToAscii(hexPeerId);
      this.openConnection(peerId);
    });
  }
}
