contract MoonInc {

    string public constant name  = "Cookie | Moon, Inc.";
    string public constant symbol = "Cookie";
    uint8 public constant decimals = 18;

    // Global balances
    uint256 public totalCookieProduction;
    uint256 private roughSupply;
    uint256 private lastTotalCookieSaveTime; // Last time any player claimed their produced cookie

    // Balances for each player
    mapping(address => uint256) public cookieProduction;
    mapping(address => uint256) public cookieBalance;
    mapping(address => uint256) private lastCookieSaveTime; // Last time player claimed their produced cookie

    // Mapping of approved ERC20 transfers (by player)
    mapping(address => mapping(address => uint256)) internal allowed;

    // Production unit contracts
    ProductionUnitToken[] public productionUnitTokenContracts;
    mapping(address => bool) productionUnitTokenContractAddresses;

    // Store the production unit start time to calculate sell price.
    uint256[] public tokenContractStartTime;

    // Public launch at: Wed, 23 May 2018, 21:00 GMT
    uint256 public constant firstUnitStartTime = 1527109200;
    
    // ERC20 events
    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);

    // Constructor
    function MoonInc() public payable {
        // Create first production unit (Space Kitty)
        createProductionUnit1();
    }

    // No fallback function to avoid accidentally sending money to this contract instead of ProductionUnitToken contract.
    // function() public payable {}

    // Public function to create the ProductionUnitToken contracts.

    function createProductionUnit1() public {
        require(productionUnitTokenContracts.length == 0);

        createProductionUnitTokenContract(10, 10, 10, 0.0000001 ether, 0.00000001 ether, 1, firstUnitStartTime);
    }

    function createProductionUnit2() public {
        require(productionUnitTokenContracts.length == 1);

        createProductionUnitTokenContract(15, 15, 15, 0.0000001 ether, 0.00000001 ether, 3, firstUnitStartTime + 2 days);
    }

    function createProductionUnit3() public {
        require(productionUnitTokenContracts.length == 2);

        createProductionUnitTokenContract(20, 20, 20, 0.0000001 ether, 0.00000001 ether, 9, firstUnitStartTime + 4 days);
    }

    function createProductionUnit4() public {
        require(productionUnitTokenContracts.length == 3);

        createProductionUnitTokenContract(25, 25, 25, 0.0000001 ether, 0.00000001 ether, 3**3, firstUnitStartTime + 7 days);
    }

    function createProductionUnit5() public {
        require(productionUnitTokenContracts.length == 4);

        createProductionUnitTokenContract(30, 30, 30, 0.0000001 ether, 0.00000001 ether, 3**4, firstUnitStartTime + 11 days);
    }

    function createProductionUnit6() public {
        require(productionUnitTokenContracts.length == 5);

        createProductionUnitTokenContract(30, 30, 30, 0.0000001 ether, 0.00000001 ether, 3**5, firstUnitStartTime + 16 days);
    }

    function createProductionUnit7() public {
        require(productionUnitTokenContracts.length == 6);

        createProductionUnitTokenContract(30, 30, 30, 0.0000001 ether, 0.00000001 ether, 3**6, firstUnitStartTime + 21 days);
    }

    function createProductionUnitTokenContract(
        uint8 _entryFee, uint8 _transferFee, uint8 _exitFee, uint256 _tokenPriceInitial, 
        uint256 _tokenPriceIncremental, uint256 _cookieProductionMultiplier, uint256 _startTime
    ) internal {
        ProductionUnitToken newContract = new ProductionUnitToken(address(this),
            _entryFee, _transferFee, _exitFee, _tokenPriceInitial, _tokenPriceIncremental, _cookieProductionMultiplier, _startTime);
        productionUnitTokenContracts.push(newContract);
        productionUnitTokenContractAddresses[address(newContract)] = true;

        tokenContractStartTime.push(_startTime);
    }

    function productionUnitTokenContractCount() public view returns (uint) {
        return productionUnitTokenContracts.length;
    }

    function handleProductionIncrease(address player, uint256 amount) public payable {
        require(productionUnitTokenContractAddresses[msg.sender]);

        updatePlayersCookie(player);

        totalCookieProduction = SafeMath.add(totalCookieProduction, amount);
        cookieProduction[player] = SafeMath.add(cookieProduction[player], amount);
    }

    function handleProductionDecrease(address player, uint256 amount) public payable {
        require(productionUnitTokenContractAddresses[msg.sender]);

        updatePlayersCookie(player);

        totalCookieProduction = SafeMath.sub(totalCookieProduction, amount);
        cookieProduction[player] = SafeMath.sub(cookieProduction[player], amount);
    }

    function getState() public view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
        return (totalCookieProduction, cookieProduction[msg.sender], totalSupply(), balanceOf(msg.sender), 
            address(this).balance, lastTotalCookieSaveTime, computeSellPrice());
    }

    function totalSupply() public constant returns(uint256) {
        return roughSupply + balanceOfTotalUnclaimedCookie();
    }

    function balanceOf(address player) public constant returns(uint256) {
        return cookieBalance[player] + balanceOfUnclaimedCookie(player);
    }

    function balanceOfTotalUnclaimedCookie() public constant returns(uint256) {
        if (lastTotalCookieSaveTime > 0 && lastTotalCookieSaveTime < block.timestamp) {
            return (totalCookieProduction * (block.timestamp - lastTotalCookieSaveTime));
        }

        return 0;
    }

    function balanceOfUnclaimedCookie(address player) internal constant returns (uint256) {
        uint256 lastSave = lastCookieSaveTime[player];

        if (lastSave > 0 && lastSave < block.timestamp) {
            return (cookieProduction[player] * (block.timestamp - lastSave));
        }

        return 0;
    }

    function transfer(address recipient, uint256 amount) public returns (bool) {
        updatePlayersCookie(msg.sender);
        require(amount <= cookieBalance[msg.sender]);

        cookieBalance[msg.sender] -= amount;
        cookieBalance[recipient] += amount;

        Transfer(msg.sender, recipient, amount);

        return true;
    }

    function transferFrom(address player, address recipient, uint256 amount) public returns (bool) {
        updatePlayersCookie(player);
        require(amount <= allowed[player][msg.sender] && amount <= cookieBalance[player]);

        cookieBalance[player] -= amount;
        cookieBalance[recipient] += amount;
        allowed[player][msg.sender] -= amount;

        Transfer(player, recipient, amount);

        return true;
    }

    function approve(address approvee, uint256 amount) public returns (bool){
        allowed[msg.sender][approvee] = amount;
        Approval(msg.sender, approvee, amount);

        return true;
    }

    function allowance(address player, address approvee) public constant returns(uint256){
        return allowed[player][approvee];
    }

    function updatePlayersCookie(address player) internal {
        roughSupply += balanceOfTotalUnclaimedCookie();
        cookieBalance[player] += balanceOfUnclaimedCookie(player);
        lastTotalCookieSaveTime = block.timestamp;
        lastCookieSaveTime[player] = block.timestamp;
    }

    // Sell all cookies, the eth earned is calculated by the proportion of cookies owned.
    // Selling of cookie is forbidden within one hour of new production unit launch.
    function sellAllCookies() public {
        updatePlayersCookie(msg.sender);

        uint256 sellPrice = computeSellPrice();

        require(sellPrice > 0);

        uint256 myCookies = cookieBalance[msg.sender];
        uint256 value = myCookies * sellPrice / (1 ether);

        cookieBalance[msg.sender] = 0;

        msg.sender.transfer(value);
    }

    // Compute sell price for 1 cookie, it is 0.5 when a new token contract is deployed,
    // and then goes up until it reaches the maximum sell price after 5 days.
    function computeSellPrice() public view returns (uint) {
        uint256 supply = totalSupply();

        if (supply == 0) {
            return 0;
        }

        uint index;
        uint lastTokenContractStartTime = now;

        while (index < tokenContractStartTime.length && tokenContractStartTime[index] < now) {
            lastTokenContractStartTime = tokenContractStartTime[index];
            index++;
        }

        if (now < lastTokenContractStartTime + 1 hours) {
            return 0;
        }

        uint timeToMaxValue = 5 days;

        uint256 secondsPassed = now - lastTokenContractStartTime;
        secondsPassed = secondsPassed <= timeToMaxValue ? secondsPassed : timeToMaxValue;
        uint256 multiplier = 5000 + 5000 * secondsPassed / timeToMaxValue;

        return 1 ether * address(this).balance / supply * multiplier / 10000;
    }

}
function computeSellPrice() public view returns (uint) {
    uint256 supply = totalSupply();

    if (supply == 0) {
        return 0;
    }

    uint index;
    uint lastTokenContractStartTime = now;

    while (index < tokenContractStartTime.length && tokenContractStartTime[index] < now) {
        lastTokenContractStartTime = tokenContractStartTime[index];
        index++;
    }

    if (now < lastTokenContractStartTime + 1 hours) {
        return 0;
    }

    uint timeToMaxValue = 5 days;

    uint256 secondsPassed = now - lastTokenContractStartTime;
    secondsPassed = secondsPassed <= timeToMaxValue ? secondsPassed : timeToMaxValue;
    uint256 multiplier = 5000 + 5000 * secondsPassed / timeToMaxValue;

    return 1 ether * address(this).balance / supply * multiplier / 10000;
}

// Compute sell price for 1 cookie, it is 0.5 when a new token contract is deployed,
// and then goes up until it reaches the maximum sell price after 5 days.