pragma solidity ^0.4.18;


contract Trigonometry {

    // Table index into the trigonometric table
    uint constant INDEX_WIDTH = 4;
    // Interpolation between successive entries in the tables
    uint constant INTERP_WIDTH = 8;
    uint constant INDEX_OFFSET = 12 - INDEX_WIDTH;
    uint constant INTERP_OFFSET = INDEX_OFFSET - INTERP_WIDTH;
    uint16 constant ANGLES_IN_CYCLE = 16384;
    uint16 constant QUADRANT_HIGH_MASK = 8192;
    uint16 constant QUADRANT_LOW_MASK = 4096;
    uint constant SINE_TABLE_SIZE = 16;

    // constant sine lookup table generated by gen_tables.py
    // We have no other choice but this since constant arrays don't yet exist
    uint8 constant entry_bytes = 2;
    bytes constant sin_table = "\x00\x00\x0c\x8c\x18\xf9\x25\x28\x30\xfb\x3c\x56\x47\x1c\x51\x33\x5a\x82\x62\xf1\x6a\x6d\x70\xe2\x76\x41\x7a\x7c\x7d\x89\x7f\x61\x7f\xff";

    /**
     * Convenience function to apply a mask on an integer to extract a certain
     * number of bits. Using exponents since solidity still does not support
     * shifting.
     *
     * @param _value The integer whose bits we want to get
     * @param _width The width of the bits (in bits) we want to extract
     * @param _offset The offset of the bits (in bits) we want to extract
     * @return An integer containing _width bits of _value starting at the
     *         _offset bit
     */
    function bits(uint _value, uint _width, uint _offset) pure internal returns (uint) {
        return (_value / (2 ** _offset)) & (((2 ** _width)) - 1);
    }

    function sin_table_lookup(uint index) pure internal returns (uint16) {
        bytes memory table = sin_table;
        uint offset = (index + 1) * entry_bytes;
        uint16 trigint_value;
        assembly {
            trigint_value := mload(add(table, offset))
        }

        return trigint_value;
    }

    /**
     * Return the sine of an integer approximated angle as a signed 16-bit
     * integer.
     *
     * @param _angle A 14-bit angle. This divides the circle into 16384
     *               angle units, instead of the standard 360 degrees.
     * @return The sine result as a number in the range -32767 to 32767.
     */
    function sin(uint16 _angle) public pure returns (int) {
        uint interp = bits(_angle, INTERP_WIDTH, INTERP_OFFSET);
        uint index = bits(_angle, INDEX_WIDTH, INDEX_OFFSET);

        bool is_odd_quadrant = (_angle & QUADRANT_LOW_MASK) == 0;
        bool is_negative_quadrant = (_angle & QUADRANT_HIGH_MASK) != 0;

        if (!is_odd_quadrant) {
            index = SINE_TABLE_SIZE - 1 - index;
        }

        uint x1 = sin_table_lookup(index);
        uint x2 = sin_table_lookup(index + 1);
        uint approximation = ((x2 - x1) * interp) / (2 ** INTERP_WIDTH);

        int sine;
        if (is_odd_quadrant) {
            sine = int(x1) + int(approximation);
        } else {
            sine = int(x2) - int(approximation);
        }

        if (is_negative_quadrant) {
            sine *= -1;
        }

        return sine;
    }

    /**
     * Return the cos of an integer approximated angle.
     * It functions just like the sin() method but uses the trigonometric
     * identity sin(x + pi/2) = cos(x) to quickly calculate the cos.
     */
    function cos(uint16 _angle) public pure returns (int) {
        if (_angle > ANGLES_IN_CYCLE - QUADRANT_LOW_MASK) {
            _angle = QUADRANT_LOW_MASK - ANGLES_IN_CYCLE - _angle;
        } else {
            _angle += QUADRANT_LOW_MASK;
        }
        return sin(_angle);
    }

}

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }

}

contract Ownable {

  address public owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  function Ownable() public {
    owner = msg.sender;
  }

  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}


contract VirusGame is Ownable, Trigonometry {

    using SafeMath for uint256;

    /**
    * Structs
    */

    struct Virus {
        bytes32 name;
        bytes32 parent;
        uint256 potential;
        uint256 infected;
        uint256 infectedPayed;
        uint256 infectedTriggle;
        uint256 lastWithdraw;
        uint256 lastInfected;
        uint256 generation;
        address owner;
    }

    /**
    * State
    */

    uint256 nonce;

    mapping (address => bytes32[]) public virusOwner;

    mapping (bytes32 => Virus) public virus;

    bytes32[] public virusHashes;

    uint256 public totalPopulation;

    uint256 public totalInfected;

    bytes32 public genesisVirus;

    uint256 public totalBalance;

    uint256 public totalPayed;

    /**
    * Modifier
    */

    modifier useNonce() {
        _;
        nonce = nonce.add(1);
    }

    modifier healthyPeople() {
        require(totalPopulation > totalInfected);
        _;
    }

    /**
    * Functions
    */

    event LogMutation(
        bytes32 parentHash,
        bytes32 virusHash
    );

    function mutate(bytes32 _virus, bytes32 _name) payable healthyPeople useNonce public {
        // ensure parent virus exists
        require(virus[_virus].owner != address(0));

        uint costs = virus[_virus].generation.mul(0.001 ether).add(0.01 ether);
        require(msg.value >= costs);

        bytes32 newHash = keccak256(_virus, _name, nonce, msg.sender, now);

        // new potential from virus hash to uint -> sinus or cosinus
        int mutationFactor = sin(uint16(newHash));
        uint uintFactor;
        uint newPotential;

        if (mutationFactor >= 0) {
            uintFactor = uint(mutationFactor);
            newPotential = virus[_virus].potential.mul(
                uintFactor.mul(20).div(32767).add(100)
            ).div(100);
        } else {
            uintFactor = uint(-mutationFactor);
            newPotential = virus[_virus].potential.mul(
                uintFactor.mul(20).div(32767).add(80)
            ).div(100);
        }

        virus[newHash].name = _name;
        virus[newHash].parent = _virus;
        virus[newHash].generation = virus[_virus].generation.add(1);
        virus[newHash].potential = newPotential;
        virus[newHash].lastInfected = now;
        virus[newHash].lastWithdraw = now;
        virus[newHash].owner = msg.sender;

        virusHashes.push(newHash);
        virusOwner[msg.sender].push(newHash);

        totalBalance = totalBalance.add(
            costs.mul(9).div(10)
        );

        LogMutation(
            _virus,
            newHash
        );
    }

    event LogInfection(
        uint infected,
        bytes32 virusHash
    );

    event LogEndOfWorld();

    function infect(bytes32 _virus) healthyPeople public {
        require(virus[_virus].owner == msg.sender);

        // infectedTriggle + potential * delay = infected
        uint delay = now.sub(virus[_virus].lastInfected);

        uint infected = virus[_virus].infectedTriggle.add(
            virus[_virus].potential.mul(delay).div(1 days)
        );
        
        // infectedTriggle from parent must be set to x% of infected
        virus[virus[_virus].parent].infectedTriggle = virus[virus[_virus].parent].infectedTriggle.add(
            infected.div(10)
        );

        totalInfected = totalInfected.add(infected);
        virus[_virus].infected = virus[_virus].infected.add(infected);

        virus[_virus].lastInfected = now;
        virus[_virus].infectedTriggle = 0;

        LogInfection(
            infected,
            _virus
        );

        if (totalPopulation < totalInfected) {
            totalInfected = totalPopulation;
            LogEndOfWorld();
        }
    }

    function withdraw(bytes32 _virus) public {
        require(virus[_virus].owner == msg.sender);

        // only withdraw once a day
        require(now > (virus[_virus].lastWithdraw + 1 days));

        // calculate ether
        uint toBePayed = virus[_virus].infected.sub(virus[_virus].infectedPayed);
        uint amount = totalBalance.div(totalInfected.sub(totalPayed)).mul(toBePayed);

        require(amount <= totalBalance);

        // subtract from total balance
        totalBalance = totalBalance.sub(amount);
        totalPayed = totalPayed.add(toBePayed);

        // send ether
        msg.sender.transfer(amount);

        virus[_virus].infectedPayed = virus[_virus].infected;
        virus[_virus].lastWithdraw = now;
    }

    function withdrawExcess(address _withdraw) onlyOwner public {
        _withdraw.transfer(this.balance.sub(totalBalance));
    }

    /**
    * Getters
    */

    function getVirusLength() public view returns(uint) {
        return virusHashes.length;
    }

    function getOwnerVirusLength(address _owner) public view returns(uint) {
        return virusOwner[_owner].length;
    }

    /**
    * Constructor
    */

    function VirusGame() public {
        totalPopulation = 7000000000;

        genesisVirus = keccak256("Genesis");

        virus[genesisVirus].name = "Genesis";
        virus[genesisVirus].potential = 100;
        virus[genesisVirus].owner = msg.sender;
        virus[genesisVirus].lastInfected = now;

        virusOwner[msg.sender].push(genesisVirus);
        virusHashes.push(genesisVirus);
    }

}