text: 'use strict';
  var CryptoJS = require("crypto-js");
  var express = require("express");
  var bodyParser = require('body-parser');
  var WebSocket = require("ws");
  const Wallet = require('./class/wallet.js');
  const Block = require('./class/block.js');
  const ProofOfWork = require('./class/ProofOfWork.js');
  var proofOfWork = new ProofOfWork();
  var wallet = new Wallet();
  var http_port = process.env.HTTP_PORT || 3001;
  var p2p_port = process.env.P2P_PORT || 6001;
  var initialPeers = process.env.PEERS ? process.env.PEERS.split(',') : [];
  
  var peers = [];
  var keys = [];
  var sockets = [];
  var MessageType = {
      QUERY_LATEST: 0,
      QUERY_ALL: 1,
      RESPONSE_BLOCKCHAIN: 2,
      RESPONSE_PEERS: 3
  };
  
  var getGenesisBlock = () => {
      return new Block(0, "0", 1465154705, "my genesis block!!", "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7");
  };
  
  var blockchain = [getGenesisBlock()];
  
  var initHttpServer = () => {
      var app = express();
      app.use(bodyParser.json());
  
      app.get('/blocks', (req, res) => res.send(JSON.stringify(blockchain)));
      app.post('/mineBlock', (req, res) => {
          var newBlock = generateNextBlock(wallet.encrypt(req.body.data));
          addBlock(newBlock);
          broadcast(responseLatestMsg());
          console.log('block added: ' + JSON.stringify(newBlock));
          res.send();
      });
      app.get('/peers', (req, res) => {
          res.send(sockets.map(s => s._socket.remoteAddress + ':' + s._socket.remotePort));
      });
      app.post('/addPeer', (req, res) => {
          connectToPeers([req.body.peer]);
          res.send();
      });
      app.listen(http_port, () => console.log('Listening http on port: ' + http_port));
  };
  
  
  var initP2PServer = () => {
      var server = new WebSocket.Server({port: p2p_port});
      server.on('connection', ws => initConnection(ws));
      console.log('listening websocket p2p port on: ' + p2p_port);
  
  };
  
  var initConnection = (ws) => {
      sockets.push(ws);
      initMessageHandler(ws);
      initErrorHandler(ws);
      write(ws, queryChainLengthMsg());
  };
  
  var initMessageHandler = (ws) => {
      ws.on('message', (data) => {
          var message = JSON.parse(data);
          console.log('Received message' + JSON.stringify(message));
          switch (message.type) {
              case MessageType.QUERY_LATEST:
                  write(ws, responseLatestMsg());
                  break;
              case MessageType.QUERY_ALL:
                  write(ws, responseChainMsg());
                  break;
              case MessageType.RESPONSE_BLOCKCHAIN:
                  handleBlockchainResponse(message);
                  break;
              case MessageType.RESPONSE_PEERS:
                  responseMyPeers();
                  break;
          }
      });
  };
  
  var initErrorHandler = (ws) => {
      var closeConnection = (ws) => {
          console.log('connection failed to peer: ' + ws.url);
          sockets.splice(sockets.indexOf(ws), 1);
      };
      ws.on('close', () => closeConnection(ws));
      ws.on('error', () => closeConnection(ws));
  };
  
  
  var generateNextBlock = (blockData) => {
      var previousBlock = getLatestBlock();
      var nextIndex = previousBlock.index + 1;
      var nextTimestamp = new Date().getTime() / 1000;
      var nextHash = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);
      var signature = wallet.sign(nextHash.toString());
      var proof = proofOfWork.hash(nextHash);
      return new Block(nextIndex, previousBlock.hash, nextTimestamp, blockData, nextHash, signature, wallet.getKey(), wallet.getAddress(), proof);
  };
  
  
  var calculateHashForBlock = (block) => {
      return calculateHash(block.index, block.previousHash, block.timestamp, block.data);
  };
  
  var calculateHash = (index, previousHash, timestamp, data) => {
      return CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
  };
  
  var addBlock = (newBlock) => {
      if (isValidNewBlock(newBlock, getLatestBlock())) {
          blockchain.push(newBlock);
      }
  };
  var checkSignature = (key, signature, hash) => {
      var NodeRSA = require('node-rsa');
      var keyobject = new NodeRSA();
      keyobject.importKey(key, "pkcs8-public-pem");
      return keyobject.verify(hash, signature);
  };
  var checkAddress = (key, address) => {
      var CryptoJS = require("crypto-js");
      return CryptoJS.MD5(key).toString() == address;
  };
  var isValidNewBlock = (newBlock, previousBlock) => {
      if (newBlock.index === 0) return true;
      if (previousBlock.index + 1 !== newBlock.index) {
          console.log('invalid index');
          return false;
      } else if (previousBlock.hash !== newBlock.previousHash) {
          console.log('invalid previoushash');
          return false;
      } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
          console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));
          console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);
          return false;
      } else if (!checkSignature(newBlock.publickey, newBlock.signature, newBlock.hash)) {
          console.log("signature mismatch");
          return false;
      } else if (!checkAddress(newBlock.publickey, newBlock.address)) {
          console.log("invalid address/key pair");
          return false;
      } else if (!proofOfWork.verify(newBlock.hash, newBlock.proof)) {
          console.log("invalid Proof");
          return false;
      }
      return true;
  };
  
  var connectToPeers = (newPeers) => {
      newPeers.forEach((peer) => {
          if (peers.indexOf(peer) < 0)
              peers.push(peer);
          var ws = new WebSocket(peer);
          ws.on('open', () => initConnection(ws));
          ws.on('error', () => {
              console.log('connection failed')
          });
      });
  };
  
  var handleBlockchainResponse = (message) => {
      var receivedBlocks = JSON.parse(message.data).sort((b1, b2) => (b1.index - b2.index));
      var latestBlockReceived = receivedBlocks[receivedBlocks.length - 1];
      var latestBlockHeld = getLatestBlock();
      if (latestBlockReceived.index > latestBlockHeld.index) {
          console.log('blockchain possibly behind. We got: ' + latestBlockHeld.index + ' Peer got: ' + latestBlockReceived.index);
          if (latestBlockHeld.hash === latestBlockReceived.previousHash) {
              console.log("We can append the received block to our chain");
              blockchain.push(latestBlockReceived);
              broadcast(responseLatestMsg());
          } else if (receivedBlocks.length === 1) {
              console.log("We have to query the chain from our peer");
              broadcast(queryAllMsg());
          } else {
              console.log("Received blockchain is longer than current blockchain");
              replaceChain(receivedBlocks);
          }
      } else {
          console.log('received blockchain is not longer than received blockchain. Do nothing');
      }
  };
  
  var replaceChain = (newBlocks) => {
      if (isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
          console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');
          blockchain = newBlocks;
          broadcast(responseLatestMsg());
      } else {
          console.log('Received blockchain invalid');
      }
  };
  
  var isValidChain = (blockchainToValidate) => {
      if (JSON.stringify(blockchainToValidate[0]) !== JSON.stringify(getGenesisBlock())) {
          return false;
      }
      var tempBlocks = [blockchainToValidate[0]];
      for (var i = 1; i < blockchainToValidate.length; i++) {
          if (isValidNewBlock(blockchainToValidate[i], tempBlocks[i - 1])) {
              tempBlocks.push(blockchainToValidate[i]);
          } else {
              return false;
          }
      }
      return true;
  };
  
  var getLatestBlock = () => blockchain[blockchain.length - 1];
  var queryChainLengthMsg = () => ({'type': MessageType.QUERY_LATEST});
  var queryAllMsg = () => ({'type': MessageType.QUERY_ALL});
  var responseChainMsg = () => ({
      'type': MessageType.RESPONSE_BLOCKCHAIN, 'data': JSON.stringify(blockchain)
  });
  var responseLatestMsg = () => ({
      'type': MessageType.RESPONSE_BLOCKCHAIN,
      'data': JSON.stringify([getLatestBlock()])
  });
  var responseMyPeers = () => ({
      'type': MessageType.RESPONSE_PEERS,
      'data': JSON.stringify(peers)
  });
  var write = (ws, message) => ws.send(JSON.stringify(message));
  var broadcast = (message) => sockets.forEach(socket => write(socket, message));
  
  connectToPeers(initialPeers);
  initHttpServer();
  initP2PServer();