pragma solidity ^0.4.21;

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 UnicornInit {
    function init() external;
}

contract UnicornManagement {
    using SafeMath for uint;

    address public ownerAddress;
    address public managerAddress;
    address public communityAddress;
    address public walletAddress;
    address public candyToken;
    address public candyPowerToken;
    address public dividendManagerAddress; //onlyCommunity
    address public blackBoxAddress; //onlyOwner
    address public unicornBreedingAddress; //onlyOwner
    address public geneLabAddress; //onlyOwner
    address public unicornTokenAddress; //onlyOwner

    uint public createDividendPercent = 375; //OnlyManager 4 digits. 10.5% = 1050
    uint public sellDividendPercent = 375; //OnlyManager 4 digits. 10.5% = 1050
    uint public subFreezingPrice = 1000000000000000000; //
    uint64 public subFreezingTime = 1 hours;
    uint public subTourFreezingPrice = 1000000000000000000; //
    uint64 public subTourFreezingTime = 1 hours;
    uint public createUnicornPrice = 50000000000000000;
    uint public createUnicornPriceInCandy = 25000000000000000000; //25 tokens
    uint public oraclizeFee = 3000000000000000; //0.003 ETH

    bool public paused = true;
    bool public locked = false;

    mapping(address => bool) tournaments;//address

    event GamePaused();
    event GameResumed();
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event NewManagerAddress(address managerAddress);
    event NewCommunityAddress(address communityAddress);
    event NewDividendManagerAddress(address dividendManagerAddress);
    event NewWalletAddress(address walletAddress);
    event NewCreateUnicornPrice(uint price, uint priceCandy);
    event NewOraclizeFee(uint fee);
    event NewSubFreezingPrice(uint price);
    event NewSubFreezingTime(uint time);
    event NewSubTourFreezingPrice(uint price);
    event NewSubTourFreezingTime(uint time);
    event NewCreateUnicornPrice(uint price);
    event NewCreateDividendPercent(uint percent);
    event NewSellDividendPercent(uint percent);
    event AddTournament(address tournamentAddress);
    event DelTournament(address tournamentAddress);
    event NewBlackBoxAddress(address blackBoxAddress);
    event NewBreedingAddress(address breedingAddress);


    modifier onlyOwner() {
        require(msg.sender == ownerAddress);
        _;
    }

    modifier onlyManager() {
        require(msg.sender == managerAddress);
        _;
    }

    modifier onlyCommunity() {
        require(msg.sender == communityAddress);
        _;
    }

    modifier whenNotPaused() {
        require(!paused);
        _;
    }

    modifier whenPaused {
        require(paused);
        _;
    }

    modifier whenUnlocked() {
        require(!locked);
        _;
    }

    function lock() external onlyOwner whenPaused whenUnlocked {
        locked = true;
    }

    function UnicornManagement(address _candyToken) public {
        ownerAddress = msg.sender;
        managerAddress = msg.sender;
        communityAddress = msg.sender;
        walletAddress = msg.sender;
        candyToken = _candyToken;
        candyPowerToken = _candyToken;
    }


    struct InitItem {
        uint listIndex;
        bool exists;
    }

    mapping (address => InitItem) private initItems;
    address[] private initList;

    function registerInit(address _contract) external whenPaused {
        require(msg.sender == ownerAddress || tx.origin == ownerAddress);

        if (!initItems[_contract].exists) {
            initItems[_contract] = InitItem({
                listIndex: initList.length,
                exists: true
                });
            initList.push(_contract);
        }
    }

    function unregisterInit(address _contract) external onlyOwner whenPaused {
        require(initItems[_contract].exists && initList.length > 0);
        uint lastIdx = initList.length - 1;
        initItems[initList[lastIdx]].listIndex = initItems[_contract].listIndex;
        initList[initItems[_contract].listIndex] = initList[lastIdx];
        initList.length--;
        delete initItems[_contract];

    }

    function runInit() external onlyOwner whenPaused {
        for(uint i = 0; i < initList.length; i++) {
            UnicornInit(initList[i]).init();
        }
    }

    function setCandyPowerToken(address _candyPowerToken) external onlyOwner whenPaused {
        require(_candyPowerToken != address(0));
        candyPowerToken = _candyPowerToken;
    }

    function setUnicornToken(address _unicornTokenAddress) external onlyOwner whenPaused whenUnlocked {
        require(_unicornTokenAddress != address(0));
        //        require(unicornTokenAddress == address(0));
        unicornTokenAddress = _unicornTokenAddress;
    }

    function setBlackBox(address _blackBoxAddress) external onlyOwner whenPaused {
        require(_blackBoxAddress != address(0));
        blackBoxAddress = _blackBoxAddress;
    }

    function setGeneLab(address _geneLabAddress) external onlyOwner whenPaused {
        require(_geneLabAddress != address(0));
        geneLabAddress = _geneLabAddress;
    }

    function setUnicornBreeding(address _unicornBreedingAddress) external onlyOwner whenPaused whenUnlocked {
        require(_unicornBreedingAddress != address(0));
        //        require(unicornBreedingAddress == address(0));
        unicornBreedingAddress = _unicornBreedingAddress;
    }

    function setManagerAddress(address _managerAddress) external onlyOwner {
        require(_managerAddress != address(0));
        managerAddress = _managerAddress;
        emit NewManagerAddress(_managerAddress);
    }

    function setDividendManager(address _dividendManagerAddress) external onlyOwner {
        require(_dividendManagerAddress != address(0));
        dividendManagerAddress = _dividendManagerAddress;
        emit NewDividendManagerAddress(_dividendManagerAddress);
    }

    function setWallet(address _walletAddress) external onlyOwner {
        require(_walletAddress != address(0));
        walletAddress = _walletAddress;
        emit NewWalletAddress(_walletAddress);
    }

    function setTournament(address _tournamentAddress) external onlyOwner {
        require(_tournamentAddress != address(0));
        tournaments[_tournamentAddress] = true;
        emit AddTournament(_tournamentAddress);
    }

    function delTournament(address _tournamentAddress) external onlyOwner {
        require(tournaments[_tournamentAddress]);
        tournaments[_tournamentAddress] = false;
        emit DelTournament(_tournamentAddress);
    }

    function transferOwnership(address _ownerAddress) external onlyOwner {
        require(_ownerAddress != address(0));
        ownerAddress = _ownerAddress;
        emit OwnershipTransferred(ownerAddress, _ownerAddress);
    }


    function setCreateDividendPercent(uint _percent) public onlyManager {
        require(_percent < 2500);
        //no more then 25%
        createDividendPercent = _percent;
        emit NewCreateDividendPercent(_percent);
    }

    function setSellDividendPercent(uint _percent) public onlyManager {
        require(_percent < 2500);
        //no more then 25%
        sellDividendPercent = _percent;
        emit NewSellDividendPercent(_percent);
    }

    //time in minutes
    function setSubFreezingTime(uint64 _time) external onlyManager {
        subFreezingTime = _time * 1 minutes;
        emit NewSubFreezingTime(_time);
    }

    //price in CandyCoins
    function setSubFreezingPrice(uint _price) external onlyManager {
        subFreezingPrice = _price;
        emit NewSubFreezingPrice(_price);
    }


    //time in minutes
    function setSubTourFreezingTime(uint64 _time) external onlyManager {
        subTourFreezingTime = _time * 1 minutes;
        emit NewSubTourFreezingTime(_time);
    }

    //price in CandyCoins
    function setSubTourFreezingPrice(uint _price) external onlyManager {
        subTourFreezingPrice = _price;
        emit NewSubTourFreezingPrice(_price);
    }

    //in weis
    function setOraclizeFee(uint _fee) external onlyManager {
        oraclizeFee = _fee;
        emit NewOraclizeFee(_fee);
    }

    //price in weis
    function setCreateUnicornPrice(uint _price, uint _candyPrice) external onlyManager {
        createUnicornPrice = _price;
        createUnicornPriceInCandy = _candyPrice;
        emit NewCreateUnicornPrice(_price, _candyPrice);
    }

    function setCommunity(address _communityAddress) external onlyCommunity {
        require(_communityAddress != address(0));
        communityAddress = _communityAddress;
        emit NewCommunityAddress(_communityAddress);
    }


    function pause() external onlyOwner whenNotPaused {
        paused = true;
        emit GamePaused();
    }

    function unpause() external onlyOwner whenPaused {
        paused = false;
        emit GameResumed();
    }



    function isTournament(address _tournamentAddress) external view returns (bool) {
        return tournaments[_tournamentAddress];
    }

    function getCreateUnicornFullPrice() external view returns (uint) {
        return createUnicornPrice.add(oraclizeFee);
    }

    function getCreateUnicornFullPriceInCandy() external view returns (uint) {
        return createUnicornPriceInCandy;
    }

    function getHybridizationFullPrice(uint _price) external view returns (uint) {
        return _price.add(valueFromPercent(_price, createDividendPercent));//.add(oraclizeFee);
    }

    function getSellUnicornFullPrice(uint _price) external view returns (uint) {
        return _price.add(valueFromPercent(_price, sellDividendPercent));//.add(oraclizeFee);
    }

    //1% - 100, 10% - 1000 50% - 5000
    function valueFromPercent(uint _value, uint _percent) internal pure returns (uint amount)    {
        uint _amount = _value.mul(_percent).div(10000);
        return (_amount);
    }
}