contract SkinMinting is SkinMarket {

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

    // The summon and bleach numbers of each accounts: will be cleared every day
    mapping (address => uint256) public accountToSummonNum;
    mapping (address => uint256) public accountToBleachNum;

    // Pay level of each accounts
    mapping (address => uint256) public accountToPayLevel;
    mapping (address => uint256) public accountLastClearTime;
    mapping (address => uint256) public bleachLastClearTime;

    // Free bleach number donated
    mapping (address => uint256) public freeBleachNum;
    bool isBleachAllowed = false;
    bool isRecycleAllowed = false;

    uint256 public levelClearTime = now;

    // price and limit
    uint256 public bleachDailyLimit = 3;
    uint256 public baseSummonPrice = 1 finney;
    uint256 public bleachPrice = 100 ether;  // 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);
    event Recycle(uint256 skinId0, uint256 skinId1, uint256 skinId2, uint256 skinId3, uint256 skinId4, uint256 newSkinId);

    // functions

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

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

    function setBleachDailyLimit(uint256 limit) external onlyCOO {
        bleachDailyLimit = limit;
    }

    function switchBleachAllowed(bool newBleachAllowed) external onlyCOO {
        isBleachAllowed = newBleachAllowed;
    }

    function switchRecycleAllowed(bool newRecycleAllowed) external onlyCOO {
        isRecycleAllowed = newRecycleAllowed;
    }

    // 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
        emit 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 whenNotPaused 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
        emit CreateNewSkin(nextSkinId, donee);

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

    //
    function moveData(uint128[] legacyAppearance, address[] legacyOwner, bool[] legacyIsOnSale, uint256[] legacyDesiredPrice) external onlyCOO {
        Skin memory newSkin = Skin({appearance: 0, cooldownEndTime: 0, mixingWithId: 0});
        for (uint256 i = 0; i < legacyOwner.length; i++) {
            newSkin.appearance = legacyAppearance[i];
            newSkin.cooldownEndTime = uint64(now);
            newSkin.mixingWithId = 0;

            skins[nextSkinId] = newSkin;
            skinIdToOwner[nextSkinId] = legacyOwner[i];
            isOnSale[nextSkinId] = legacyIsOnSale[i];
            desiredPrice[nextSkinId] = legacyDesiredPrice[i];

            // Emit the create event
            emit CreateNewSkin(nextSkinId, legacyOwner[i]);

            nextSkinId++;
            numSkinOfAccounts[legacyOwner[i]] += 1;
            if (numSkinOfAccounts[legacyOwner[i]] > freeBleachNum[legacyOwner[i]]*10 || freeBleachNum[legacyOwner[i]] == 0) {
                freeBleachNum[legacyOwner[i]] += 1;
            }
            skinCreatedNum += 1;
        }
    }

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

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

        // Create random skin
        uint128 randomAppearance = mixFormula.randomSkinAppearance(nextSkinId, getActiveSkin(msg.sender));
        // 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
        emit CreateNewSkin(nextSkinId, msg.sender);

        nextSkinId++;
        numSkinOfAccounts[msg.sender] += 1;

        accountToSummonNum[msg.sender] += 1;

        // Handle the paylevel
        if (payLevel < 5) {
            if (accountToSummonNum[msg.sender] >= levelSplits[payLevel]) {
                accountToPayLevel[msg.sender] = payLevel + 1;
            }
        }
    }

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

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

        Skin memory newSkin;
        uint128 randomAppearance;
        // Create random skin
        for (uint256 i = 0; i < 10; i++) {
            randomAppearance = mixFormula.randomSkinAppearance(nextSkinId, getActiveSkin(msg.sender));
            newSkin = Skin({appearance: randomAppearance, cooldownEndTime: uint64(now), mixingWithId: 0});
            skins[nextSkinId] = newSkin;
            skinIdToOwner[nextSkinId] = msg.sender;
            isOnSale[nextSkinId] = false;
            // Emit the create event
            emit CreateNewSkin(nextSkinId, msg.sender);
            nextSkinId++;
        }

        // Give additional skin
        randomAppearance = mixFormula.summon10SkinAppearance(nextSkinId, getActiveSkin(msg.sender));
        newSkin = Skin({appearance: randomAppearance, cooldownEndTime: uint64(now), mixingWithId: 0});
        skins[nextSkinId] = newSkin;
        skinIdToOwner[nextSkinId] = msg.sender;
        isOnSale[nextSkinId] = false;
        // Emit the create event
        emit CreateNewSkin(nextSkinId, msg.sender);
        nextSkinId++;

        numSkinOfAccounts[msg.sender] += 11;
        accountToSummonNum[msg.sender] += 10;

        // Handle the paylevel
        if (payLevel < 5) {
            if (accountToSummonNum[msg.sender] >= levelSplits[payLevel]) {
                accountToPayLevel[msg.sender] = payLevel + 1;
            }
        }
    }

    // Recycle bin
    function recycleSkin(uint256[5] wasteSkins, uint256 preferIndex) external whenNotPaused {
        require(isRecycleAllowed == true);
        for (uint256 i = 0; i < 5; i++) {
            require(skinIdToOwner[wasteSkins[i]] == msg.sender);
            skinIdToOwner[wasteSkins[i]] = address(0);
        }

        uint128[5] memory apps;
        for (i = 0; i < 5; i++) {
            apps[i] = skins[wasteSkins[i]].appearance;
        }
        // Create random skin
        uint128 recycleApp = mixFormula.recycleAppearance(apps, preferIndex, getActiveSkin(msg.sender));
        Skin memory newSkin = Skin({appearance: recycleApp, cooldownEndTime: uint64(now), mixingWithId: 0});
        skins[nextSkinId] = newSkin;
        skinIdToOwner[nextSkinId] = msg.sender;
        isOnSale[nextSkinId] = false;

        // Emit event
        emit Recycle(wasteSkins[0], wasteSkins[1], wasteSkins[2], wasteSkins[3], wasteSkins[4], nextSkinId);

        nextSkinId++;
        numSkinOfAccounts[msg.sender] -= 4;
    }

    // Bleach some attributes
    function bleach(uint128 skinId, uint128 attributes) external payable whenNotPaused {
        require(isBleachAllowed);

        // Clear daily summon numbers
        if (bleachLastClearTime[msg.sender] == uint256(0)) {
            // This account's first time to summon, we do not need to clear bleach numbers
            bleachLastClearTime[msg.sender] = now;
        } else {
            if (bleachLastClearTime[msg.sender] < levelClearTime && now > levelClearTime) {
                accountToBleachNum[msg.sender] = 0;
                bleachLastClearTime[msg.sender] = now;
            }
        }

        require(accountToBleachNum[msg.sender] < bleachDailyLimit);
        accountToBleachNum[msg.sender] += 1;

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

        uint256 bleachNum = 0;
        for (uint256 i = 0; i < 8; i++) {
            if ((attributes & (uint128(1) << i)) > 0) {
                if (freeBleachNum[msg.sender] > 0) {
                    freeBleachNum[msg.sender]--;
                } else {
                    bleachNum++;
                }
            }
        }
        // Check whether there is enough money
        require(msg.value >= bleachNum * 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
        emit 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 setBaseSummonPrice(uint256 newPrice) external onlyCOO {
    baseSummonPrice = newPrice;
}

// functions
// Set price