pragma solidity ^0.5.0;

// https://pastebin.com/pbWBpvCJ
contract RandomGuess {
    struct Game{
        address player_a;
        address player_b;
        uint start;
        bytes32 hash_a;
        bytes32 hash_b;
        uint random;
        uint num_a;
        uint num_b;
    }

    mapping (uint256 => Game) private games;
    mapping (address => uint256) private user_game;
    uint256 filling_game = 0;
    uint256 counter = 0;

    function random() private view returns (uint8) {
       return uint8(uint256(keccak256(abi.encode(block.timestamp, block.difficulty)))%100) + 1;
   }

   function abs(int n) private pure returns (int) {
       if(n >= 0) {
            return n;
       }
       return -n;
   }   
   
   function min(int a, int b) private pure returns (int) {
       if(a<=b) {
           return a;
       }
       return b;
   }

    function getWinner(uint256 game_id) public view returns (address){
        require(isPayoutReady(game_id));
        int diff_a_right = abs(int256(games[game_id].random) - int256(games[game_id].num_a));
        int diff_a_left = abs(int256(games[game_id].random) - int256(games[game_id].num_a) + 100);
        int diff_a = min(diff_a_right, diff_a_left);
        int diff_b_right = abs(int256(games[game_id].random) - int256(games[game_id].num_b));
        int diff_b_left = abs(int256(games[game_id].random) - int256(games[game_id].num_b) + 100);
        int diff_b = min(diff_b_right, diff_b_left);
        if(diff_a == diff_b){
            // the contract win if both have guessed the same
            return address(0);
        }
        if(diff_a > diff_b) {
            return games[game_id].player_b;
        }
        return games[game_id].player_a;
    }

    function isWinner(uint256 game_id) public view returns (bool){
        require(isPayoutReady(game_id));
        return getWinner(game_id) == msg.sender;
    }

    event PayoutReady(
        uint256 game_id
    );

    function isPayoutReady(uint256 game_id) public view returns (bool){
        // todo should also be possible after n blocks
        // todo should only be executed once per game

        if(games[game_id].num_a > 0 && games[game_id].num_b > 0){
            return true;
        }
        return false;
    }

    event RevalReady(
       uint256 game_id
    );

    function isRevalReady(uint256 game_id) public view returns (bool){
        if(games[game_id].hash_a != "" && games[game_id].hash_b != ""){
            return true;
        }
        return false;
    }

    function computeSecret(uint8 number) public view returns (bytes32) {
        require(number > 0 && number <= 100);
        return keccak256(abi.encode(number, msg.sender));
    }

    constructor() public {

    }

    function nextCounter() private returns (uint256) {
        return ++counter;
    }

    function register(bytes32 hash) payable external returns (uint256) {
        require(msg.value == 2 wei);

        uint256 game_id = 0;
        if(filling_game==0){
            uint256 id = nextCounter();
            games[id] = Game(msg.sender, address(0), now, hash, "", 0, 0, 0);
            filling_game = id;
            game_id = id;
        }else{
            games[filling_game].player_b = msg.sender;
            games[filling_game].hash_b = hash;
            games[filling_game].random = random();
            game_id = filling_game;
            filling_game = 0;
        }

        if(isRevalReady(game_id)){
            emit RevalReady(game_id);
        }

        user_game[msg.sender] = game_id;

        return game_id;
    }

    function getMyGame() public view returns (uint256) {
        return user_game[msg.sender];
    }

    function submitReval(uint256 game_id, uint num) public returns (bool) {
        require(isRevalReady(game_id) && num > 0 && num <= 100);
        // todo check if num is possible, according to submitted hash

        if(games[game_id].player_a == msg.sender){
            games[game_id].num_a = num;
        } else if(games[game_id].player_b == msg.sender){
            games[game_id].num_b = num;
        }else{
            return false;
        }

        // todo why does this not work???
        // if(games[game_id].num_a > 0 && games[game_id].num_b > 0){
        //     emit PayoutReady(game_id);
        // }

        return true;
    }

    function sendMoney(uint256 game_id) public payable {
        // todo should only be executed once per game
        require(isPayoutReady(game_id));
        address winner = getWinner(game_id);

        if(winner != address(0)){
            address payable addr = address(uint160(winner));
            if (address(addr).send(4 ether)) {
                // done
            }
        }
    }

    function getGameInfo(uint256 game_id) public view returns (address,uint256,address,uint256,uint256) {
        return (games[game_id].player_a,
        games[game_id].num_a,
        games[game_id].player_b,
        games[game_id].num_b,
        games[game_id].random);
    }

}
