var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

/** Config-Values NOTE EDIT HERE **/
var Configuration = {
  cube_width : 50,
  cube_height : 200,
  cube_speed : 7,
  ball_size : 15,
  ball_speed : 15,
  ball_owner : Math.round(Math.random()) + 1,
  ball_maxAngle : 5*Math.PI/12,
  wincount : 3,
  powerupmaxtime : 10
}

/** Parameters **/
var clients = [],  // List of all conected clients
    players = [],  // Store Player 1 and Player 2 in Array {Player1 == players[0]}
    gameState = 0, // Game-Controler Variable
    width, height, // Final Width + Height of Game-Window
    startinterval, // Interval for Counting down (cancelable)
    calcPos,
    score = [0,0],
    id = 0,
    powerups = [];

/** PowerUps **/
var powerTime = 30;

/** Game-Config **/
var cube_width = Configuration.cube_width,
    cube_height = Configuration.cube_height,
    cube_speed = Configuration.cube_speed, // Depending on 'tickrate' [cube_speed*tickrate == acutal Speed]
    wincount = Configuration.wincount;

/** Networking-Config **/
var tickrate = 20;

/** Ball-Config **/
var ball_size = Configuration.ball_size,
    ball_speed = Configuration.ball_speed,
    ball_owner = Configuration.ball_owner,
    ball_maxAngle = Configuration.ball_maxAngle,
    ball_VX = 0,
    ball_VY = 0,
    ballX,
    ballY;

/** ########## **/
/** CONNECTION **/
/** ########## **/
io.on('connection', function(socket){
  /** Set ID for User **/
  id++;
  var clientkey = clients.push(new Client(id, socket)) - 1;
  console.log('Client connected. ID=' + id);

  /** ######### **/
  /** HANDSHAKE **/
  /** ######### **/
  socket.emit('handshake', id, gameState, cube_width, cube_height, cube_speed, score, function(w, h){ // Callback function for Client to set screen size //
    clients[clientkey].width = w;
    clients[clientkey].height = h;
    var pid = 0;
    if(players.length < 2)
      pid = players.push(new Player(id, socket, w, h));
    socket.emit('setpid', pid);
    if(players.length > 1 && gameState == 0) setUp();
  });

/** If game was already started send Window-Detials **/
if(gameState > 0) socket.emit('size', width, height);


  /** #### **/
  /** Ping **/
  /** ### **/
  socket.on('appping', function(fn){
    fn(Date.now());
  });

  /** ######### **/
  /** Ballshoot **/
  /** ######### **/
  socket.on('ballshoot', function(y){
    console.log('### Ball shoot event ###');
    if(ball_owner == 1){
      ball_VX = ball_speed;
      console.log('owner: left  - at:' + players[0].y);
    }
    else if(ball_owner == 2){
      ball_VX = -ball_speed;
      console.log('owner: right  - at:' + players[1].y);
    }
    ball_owner = 0;
  });

  /** ################ **/
  /** Paddel V-Change **/
  /** ############## **/
  socket.on('vchange', function(pos, val, y, timestamp){
    try{
      players[pos-1].vY = val;
    } catch (err) {
      console.log('Error in vChange: ' + err.message);
    }

    // NOTE Interpolation not needed
    // console.log('INTERPOLATING...');
    // var diff = ((players[pos-1].speed/tickrate)*timediff);
    // if(players[pos-1].vY == 0){ // Lag from Sender (accel)
    //   if(val > 0)
    //     players[pos-1].y -= diff;
    //   if(val < 0)
    //     players[pos-1].y += diff;
    // }
    // if(players[pos-1].vY == 1){
    //   if(val == 0)
    //     players[pos-1].y += diff;
    // }
    // if(players[pos-1].vY == -1){
    //   if(val == 0)
    //     players[pos-1].y -= diff;
    // }
    // console.log('Diff:' + diff);
    // console.log('Got: ' + y + ' @ ' + timestamp);
    // console.log('Expected:' + players[pos-1].y + ' @ ' + Date.now());
    // socket.emit('changeV', pos, val, players[pos-1].y);
    // socket.broadcast.emit('changeV', pos, val, players[pos-1].y);
  });

  /** ########## **/
  /** DISCONNECT **/
  /** ########## **/
  socket.on('disconnect', function(){
    var cid, key; // Client ID
    clients.forEach(function(cl, k){
      if(cl.socket == socket){
        cid = cl.id;
        key = k;
        return;
      }
    });
    var pid = 0; // 0 == Spectator --- 1 == Player 1 ---- 2 == Player 2
    players.forEach(function(pl, kkey){
      if(pl.socket == socket){
        pid = kkey+1;
        return;
      }
    });
    console.log('Client disconnected. ID=' + cid);
    clients.splice(key, 1);
    if(clients.length == 0){
      console.log('All Clients disonnected. Reseting...');
      resetServer();
      return;
    }
    if(pid == 0) return; // Don't do anything if spectator leaves
    gameState = 3;
    broadcastGameState();
    if(pid == 1){
      clients[0].socket.emit('win', 2, 'Player 1 left.');
      clients[0].socket.broadcast.emit('win', 2, 'Player 1 left.');
    }
    if(pid == 2){
      clients[0].socket.emit('win', 1, 'Player 2 left.');
      clients[0].socket.broadcast.emit('win', 1, 'Player 2 left.');
    }
    resetServer();
    console.log('Reseting...');
  });
});


http.listen(3000, function(){
  console.log('listening on *:3000');
});


/** Sync all settings to client and start the game **/
function setUp(){
  gameState = 1;
  broadcastGameState();
  console.log('Starting SetUp...');
  /** Select smalest screensize and set for all clients **/
  if(players[0].window_width < players[1].window_width)
    width = players[0].window_width;
  else
    width = players[1].window_width;
  if(players[0].window_height < players[1].window_height)
    height = players[0].window_height;
  else
    height = players[1].window_height;

  if(calcPos == null) // Start tick Server only once
    tickServer();
  clients[0].socket.emit('size', width, height);
  clients[0].socket.broadcast.emit('size', width, height);
  var i = 5;
  startinterval = setInterval(function(){
    if(gameState == 3) clearInterval(startinterval);
    i--;
    clients[0].socket.emit('countdown', i);
    clients[0].socket.broadcast.emit('countdown', i);
    if(i == 0){
      resetPlayers();
      clearInterval(startinterval);
      gameState = 2;
      broadcastGameState();
    }
  }, 1000);
}

/** Broadcast GameState to all clients **/
function broadcastGameState(){
  clients[0].socket.emit('gamestate', gameState);
  clients[0].socket.broadcast.emit('gamestate', gameState);
}

function tickServer(){
  calcPos = setInterval(function(){
    try {
      calculatePlayerPosition();
      calculateBallPosition();
    } catch (err){
      console.log('Error in ServerTick: ' + err.message);
    }

  }, tickrate);
}

function drawPowerUp(){
  var key = powerups.push(new PowerUp(1));
  console.log(powerups[key-1].x);
  console.log(powerups[key-1].y);
  players[0].socket.emit('powerup', powerups[key-1].type, powerups[key-1].width, powerups[key-1].height, powerups[key-1].x, powerups[key-1].y);
  players[0].socket.broadcast.emit('powerup', powerups[key-1].type, powerups[key-1].width, powerups[key-1].height, powerups[key-1].x, powerups[key-1].y);
}

function calculatePlayerPosition(){
  players.forEach(function(player, k){
    // console.log(player.powerup.tick);
    if(player.powerup.type == 1){
      player.speed = Configuration.cube_speed + 10;
      player.powerup.tick++;
      if(player.powerup.tick == Configuration.powerupmaxtime*(1000/tickrate)){
        player.powerup = 0;
      }
    } else {
      player.speed = Configuration.cube_speed;
    }
    player.y -= player.vY*player.speed;
    if(player.y > height-player.height || player.y < 0) player.vY = 0;
    if(player.y < 0) player.y = 0;
    if(player.y > height-player.height) player.y = height-player.height;
    player.socket.emit('paddlepos', k+1, player.vY, player.y);
    player.socket.broadcast.emit('paddlepos', k+1, player.vY, player.y);
  });
}

function calculateBallPosition(){
  /** Move Ball with Paddle if owned by owner (@roundstart) **/
  if(ball_owner == 1){
    ballX = cube_width+ball_size,
    ballY = players[0].y+cube_height/2;
  }
  else if(ball_owner == 2){
    ballX = width-cube_width-ball_size,
    ballY = players[1].y+cube_height/2;
  }

  /** ############ **/
  /** Ball Physics **/
  /** ############ **/

  ballX += ball_VX;
  ballY += ball_VY;

  /** Collide with right Paddle  or get point and respawn **/
  if(ballX > width - cube_width){
    if(ballY < players[1].y || ballY > players[1].y+cube_height){
      console.log('Point for Left');
      changeScore(1, score[0]+1);
      ball_VX = 0;
      ball_VY = 0;
      ball_owner = 1;
      drawPowerUp();
    } else {
      var intersect = ((players[1].y+cube_height/2)-ballY)/(cube_height/2);
      console.log('Intersect with right at:');
      console.log(intersect);
      console.log('###########################');
      ball_VX = -ball_speed*Math.cos(intersect*ball_maxAngle);
      ball_VY = -ball_speed*Math.sin(intersect*ball_maxAngle);
    }
  }

  /** Collide with left Paddle  or get point and respawn **/
  if(ballX < cube_width){
    if(ballY < players[0].y || ballY > players[0].y+cube_height){
      console.log('Point for Right');
      changeScore(2, score[1]+1);
      ball_VX = 0;
      ball_VY = 0;
      ball_owner = 2;
      // Add PowerUp
      drawPowerUp();
    } else {
      var intersect = ((players[0].y+cube_height/2)-ballY)/(cube_height/2);
      console.log('Intersect with left at:');
      console.log(intersect);
      console.log('###########################');
      ball_VX = ball_speed*Math.cos(intersect*ball_maxAngle);
      ball_VY = -ball_speed*Math.sin(intersect*ball_maxAngle);
    }
  }

  /** Collide with walls **/
  if(ballY <= 0 || ballY >= height-ball_size){
    ball_VY = -ball_VY;
    console.log(ballX + ' : ' + ballY);
  }

  /** Collide with PowerUp **/
  powerups.forEach(function(pu, key){
    if(!(ballY > pu.y+pu.height || ballY+ball_size*2 < pu.y)){
      if(!(ballX > pu.x+pu.width || ballX+ball_size*2 < pu.x)){
        if(ball_VX > 0){
          players[0].powerup = pu;
        } else {
          players[1].powerup = pu;
        }
        console.log('#### KEY####');
        console.log(key);
        // console.log(pu);
        powerups.splice(key, 1);
        console.log(powerups.length);
        players[0].socket.emit('powerupremove', key);
        players[0].socket.broadcast.emit('powerupremove', key);
        return;
      }
    }
  });

  var data = {
    x     : ballX,
    y     : ballY,
    vx    : ball_VX,
    vy    : ball_VY,
    speed : ball_speed,
    owner : ball_owner
  };
  players[0].socket.emit('ballpos', data);
  players[0].socket.broadcast.emit('ballpos', data);
}

function changeScore(id, val){
  score[id-1] = val;
  clients[0].socket.emit('score', score);
  clients[0].socket.broadcast.emit('score', score);

  if(val == wincount){
    gameState = 3;
    broadcastGameState();
    clients[0].socket.emit('win', id, 'Player ' + id + ' scored needed points first.');
    clients[0].socket.broadcast.emit('win', id, 'Player ' + id + ' scored needed points first.');
    resetServer();
  }
}

function resetPlayers(){
  ball_owner = Math.round(Math.random()) + 1;
  ball_VX = 0;
  ball_VY = 0;
  players.forEach(function(player, k){
    player.speed = cube_speed;
    player.width = cube_width;
    player.height = cube_height;
    player.y = 0;
    player.vY = 0;
    changeScore(k, 0);
  });
}

function resetServer(){
  gameState = 0;
  clearInterval(calcPos);
  calcPos = null;
  clearInterval(startinterval);
  score = [0,0];

  cube_width = Configuration.cube_width;
  cube_height = Configuration.cube_height;
  u = Configuration.cube_speed;
  ball_size = Configuration.ball_size;
  ball_speed = Configuration.ball_speed;
  ball_owner = Configuration.ball_owner;
  ball_maxAngle = Configuration.ball_maxAngle;
  ball_VX = 0;
  ball_VY = 0;
  ballX = Configuration.cube_width;
  ballY = Configuration.cube_height/2;
  powerups = [];
  players = [];
}

function Client(id, socket){
  this.id = id;
  this.socket = socket;
  this.width = 0;
  this.height = 0;
}

function Player(id, socket, window_width, window_height){
  this.id = id;
  this.socket = socket;
  this.window_width = window_width;
  this.window_height = window_height;
  this.speed = cube_speed;
  this.width = cube_width;
  this.height = cube_height;
  this.y = 0;
  this.vY = 0;
  this.powerup = 0;
}

function PowerUp(type){
  this.type = type;
  this.tick = 0;
  this.width = 50;
  this.height = 50;
  this.x = Math.random()*(width-Configuration.cube_width*4)+Configuration.cube_width*2;
  this.y = Math.random()*(height-50);
}
