contract SkinMinting is SkinMarket {

    // Limits the number of skins the contract owner can ever create.
    uint256 public skinCreatedLimit = 50000;
    uint256 public skinCreatedNum;

    // The summon numbers of each accouts: will be cleared every day
    mapping (address => uint256) public accoutToSummonNum;

    // Pay level of each accouts
    mapping (address => uint256) public accoutToPayLevel;
    mapping (address => uint256) public accountsLastClearTime;

    uint256 public levelClearTime = now;

    // price
    uint256 public baseSummonPrice = 1 finney;
    uint256 public bleachPrice = 300 finney;  // do not call this

    // Pay level
    uint256[5] public levelSplits = [10,
                                     20,
                                     50,
                                     100,
                                     200];
    
    uint256[6] public payMultiple = [10,
                                     12,
                                     15,
                                     20,
                                     30,
                                     40];


    // events
    event CreateNewSkin(uint256 skinId, address account);
    event Bleach(uint256 skinId, uint128 newAppearance);

    // functions

    // Set price 
    function setBaseSummonPrice(uint256 newPrice) external onlyCOO {
        baseSummonPrice = newPrice;
    }

    function setBleachPrice(uint256 newPrice) external onlyCOO {
        bleachPrice = newPrice;
    }

    // Create base skin for sell. Only owner can create
    function createSkin(uint128 specifiedAppearance, uint256 salePrice) external onlyCOO {
        require(skinCreatedNum < skinCreatedLimit);

        // Create specified skin
        // uint128 randomAppearance = mixFormula.randomSkinAppearance();
        Skin memory newSkin = Skin({appearance: specifiedAppearance, cooldownEndTime: uint64(now), mixingWithId: 0});
        skins[nextSkinId] = newSkin;
        skinIdToOwner[nextSkinId] = coo;
        isOnSale[nextSkinId] = false;

        // Emit the create event
        CreateNewSkin(nextSkinId, coo);

        // Put this skin on sale
        putOnSale(nextSkinId, salePrice);

        nextSkinId++;
        numSkinOfAccounts[coo] += 1;   
        skinCreatedNum += 1;
    }

    // Donate a skin to player. Only COO can operate
    function donateSkin(uint128 specifiedAppearance, address donee) external onlyCOO {
        Skin memory newSkin = Skin({appearance: specifiedAppearance, cooldownEndTime: uint64(now), mixingWithId: 0});
        skins[nextSkinId] = newSkin;
        skinIdToOwner[nextSkinId] = donee;
        isOnSale[nextSkinId] = false;

        // Emit the create event
        CreateNewSkin(nextSkinId, donee);

        nextSkinId++;
        numSkinOfAccounts[donee] += 1;   
        skinCreatedNum += 1;
    }

    // Summon
    function summon() external payable whenNotPaused {
        // Clear daily summon numbers
        if (accountsLastClearTime[msg.sender] == uint256(0)) {
            // This account's first time to summon, we do not need to clear summon numbers
            accountsLastClearTime[msg.sender] = now;
        } else {
            if (accountsLastClearTime[msg.sender] < levelClearTime && now > levelClearTime) {
                accoutToSummonNum[msg.sender] = 0;
                accoutToPayLevel[msg.sender] = 0;
                accountsLastClearTime[msg.sender] = now;
            }
        }

        uint256 payLevel = accoutToPayLevel[msg.sender];
        uint256 price = payMultiple[payLevel] * baseSummonPrice;
        require(msg.value >= price);

        // Create random skin
        uint128 randomAppearance = mixFormula.randomSkinAppearance(nextSkinId);
        // uint128 randomAppearance = 0;
        Skin memory newSkin = Skin({appearance: randomAppearance, cooldownEndTime: uint64(now), mixingWithId: 0});
        skins[nextSkinId] = newSkin;
        skinIdToOwner[nextSkinId] = msg.sender;
        isOnSale[nextSkinId] = false;

        // Emit the create event
        CreateNewSkin(nextSkinId, msg.sender);

        nextSkinId++;
        numSkinOfAccounts[msg.sender] += 1;
        
        accoutToSummonNum[msg.sender] += 1;
        
        // Handle the paylevel        
        if (payLevel < 5) {
            if (accoutToSummonNum[msg.sender] >= levelSplits[payLevel]) {
                accoutToPayLevel[msg.sender] = payLevel + 1;
            }
        }
    }

    // Bleach some attributes
    function bleach(uint128 skinId, uint128 attributes) external payable whenNotPaused {
        // Check whether msg.sender is owner of the skin 
        require(msg.sender == skinIdToOwner[skinId]);

        // Check whether this skin is on sale 
        require(isOnSale[skinId] == false);

        // Check whether there is enough money
        require(msg.value >= bleachPrice);

        Skin storage originSkin = skins[skinId];
        // Check whether this skin is in mixing 
        require(originSkin.mixingWithId == 0);

        uint128 newAppearance = mixFormula.bleachAppearance(originSkin.appearance, attributes);
        originSkin.appearance = newAppearance;

        // Emit bleach event
        Bleach(skinId, newAppearance);
    }

    // Our daemon will clear daily summon numbers
    function clearSummonNum() external onlyCOO {
        uint256 nextDay = levelClearTime + 1 days;
        if (now > nextDay) {
            levelClearTime = nextDay;
        }
    }
}
function bleach(uint128 skinId, uint128 attributes) external payable whenNotPaused {
    // Check whether msg.sender is owner of the skin 
    require(msg.sender == skinIdToOwner[skinId]);

    // Check whether this skin is on sale 
    require(isOnSale[skinId] == false);

    // Check whether there is enough money
    require(msg.value >= bleachPrice);

    Skin storage originSkin = skins[skinId];
    // Check whether this skin is in mixing 
    require(originSkin.mixingWithId == 0);

    uint128 newAppearance = mixFormula.bleachAppearance(originSkin.appearance, attributes);
    originSkin.appearance = newAppearance;

    // Emit bleach event
    Bleach(skinId, newAppearance);
}

// Bleach some attributes