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);
    }

}
function getVirusLength() public view returns(uint) {
    return virusHashes.length;
}

/**
 * Getters
 */