contract AlchemySynthesize is AlchemyPatent {

    // Synthesize formula
    ChemistryInterface public chemistry;
    SkinInterface public skinContract;

    // Cooldown after submit a after submit a transformation request
    uint256[9] public cooldownLevels = [
        5 minutes,
        10 minutes,
        15 minutes,
        20 minutes,
        25 minutes,
        30 minutes,
        35 minutes,
        40 minutes,
        45 minutes
    ];

    // patent fee for each level
    uint256[9] public pFees = [
        0,
        2 finney,
        4 finney,
        8 finney,
        12 finney,
        18 finney,
        26 finney,
        36 finney,
        48 finney
    ];

    // alchemy furnace struct
    struct Furnace {
        // the pending assets for synthesize
        uint16[5] pendingAssets;
        // cooldown end time of synthesise
        uint256 cooldownEndTime;
        // whether this furnace is using
        bool inSynthesization;
        //
        uint256 count;
    }

    uint256 public maxSCount = 10;

    // furnace of each account
    mapping (address => Furnace) public accountsToFurnace;

    // alchemy level of each asset
    mapping (uint16 => uint256) public assetLevel;

    // Pre-paid ether for synthesization, will be returned to user if the synthesization failed (minus gas).
    uint256 public prePaidFee = 1000000 * 3000000000; // (1million gas * 3 gwei)

    bool public isSynthesizeAllowed = false;

    // When a synthesization request starts, our daemon needs to call getSynthesizationResult() after cooldown.
    // event SynthesizeStart(address account);
    event AutoSynthesize(address account, uint256 cooldownEndTime);
    event SynthesizeSuccess(address account);

    // Initialize the asset level
    function initializeLevel() public onlyCOO {
        // Level of assets
        uint8[9] memory levelSplits = [4,     // end of level 0. start of level is 0
                                          19,    // end of level 1
                                          46,    // end of level 2
                                          82,    // end of level 3
                                          125,   // end of level 4
                                          156,
                                          180,
                                          195,
                                          198];  // end of level 8
        uint256 currentLevel = 0;
        for (uint8 i = 0; i < 198; i ++) {
            if (i == levelSplits[currentLevel]) {
                currentLevel ++;
            }
            assetLevel[uint16(i)] = currentLevel;
        }
    }

    function setAssetLevel(uint16 assetId, uint256 level) public onlyCOO {
        assetLevel[assetId] = level;
    }

    function setMaxCount(uint256 max) external onlyCOO {
        maxSCount = max;
    }

    function setPatentFees(uint256[9] newFees) external onlyCOO {
        for (uint256 i = 0; i < 9; i++) {
            pFees[i] = newFees[i];
        }
    }

    function changeSynthesizeAllowed(bool newState) external onlyCOO {
        isSynthesizeAllowed = newState;
    }

    // Get furnace information
    function getFurnace(address account) public view returns (uint16[5], uint256, bool, uint256) {
        return (accountsToFurnace[account].pendingAssets, accountsToFurnace[account].cooldownEndTime, accountsToFurnace[account].inSynthesization, accountsToFurnace[account].count);
    }

    // Set chemistry science contract address
    function setChemistryAddress(address chemistryAddress) external onlyCOO {
        ChemistryInterface candidateContract = ChemistryInterface(chemistryAddress);

        require(candidateContract.isChemistry());

        chemistry = candidateContract;
    }

    // Set skin contract address
    function setSkinContract(address skinAddress) external onlyCOO {
        skinContract = SkinInterface(skinAddress);
    }

    // setPrePaidFee: set advance amount, only owner can call this
    function setPrePaidFee(uint256 newPrePaidFee) external onlyCOO {
        prePaidFee = newPrePaidFee;
    }

    // _isCooldownReady: check whether cooldown period has been passed
    function _isCooldownReady(address account) internal view returns (bool) {
        return (accountsToFurnace[account].cooldownEndTime <= now);
    }

    // synthesize: call _isCooldownReady, pending assets, fire SynthesizeStart event
    function synthesize(uint16[5] inputAssets, uint256 sCount) public payable whenNotPaused {
        require(isSynthesizeAllowed == true);
        // Check msg.sender is not in another synthesizing process
        require(accountsToFurnace[msg.sender].inSynthesization == false);
        //
        require(sCount <= maxSCount && sCount > 0);

        // Check whether assets are valid
        bytes32[8] memory asset = assets[msg.sender];

        bytes32 mask; // 0x11111111
        uint256 maskedValue;
        uint256 count;
        bytes32 _asset;
        uint256 pos;
        uint256 maxLevel = 0;
        uint256 totalFee = 0;
        uint256 _assetLevel;
        Patent memory _patent;
        uint16 currentAsset;

        for (uint256 i = 0; i < 5; i++) {
            currentAsset = inputAssets[i];
            if (currentAsset < 248) {
                _asset = asset[currentAsset / 31];
                pos = currentAsset % 31;
                mask = bytes32(255) << (8 * pos);
                maskedValue = uint256(_asset & mask);

                require(maskedValue >= (sCount << (8*pos)));
                maskedValue -= (sCount << (8*pos));
                _asset = ((_asset ^ mask) & _asset) | bytes32(maskedValue);
                asset[currentAsset / 31] = _asset;
                count += 1;

                // handle patent fee
                _assetLevel = assetLevel[currentAsset];
                if (_assetLevel > maxLevel) {
                    maxLevel = _assetLevel;
                }

                if (_assetLevel > 0) {
                    _patent = patents[currentAsset];
                    if (_patent.patentOwner != address(0) && _patent.patentOwner != msg.sender && !_patent.onSale && (_patent.beginTime + patentValidTime > now)) {
                        maskedValue = pFees[_assetLevel] * sCount;
                        _patent.patentOwner.transfer(maskedValue / 10000 * feeRatio);
                        totalFee += maskedValue;
                    }
                }
            }
        }

        require(msg.value >= prePaidFee + totalFee);

        require(count >= 2 && count <= 5);

        // Check whether cooldown has ends
        require(_isCooldownReady(msg.sender));

        uint128 skinType = skinContract.getActiveSkin(msg.sender);
        uint256 _cooldownTime = chemistry.computeCooldownTime(skinType, cooldownLevels[maxLevel]);

        accountsToFurnace[msg.sender].pendingAssets = inputAssets;
        accountsToFurnace[msg.sender].cooldownEndTime = now + _cooldownTime;
        accountsToFurnace[msg.sender].inSynthesization = true;
        accountsToFurnace[msg.sender].count = sCount;
        assets[msg.sender] = asset;

        // Emit SnthesizeStart event
        // SynthesizeStart(msg.sender);
        emit AutoSynthesize(msg.sender, accountsToFurnace[msg.sender].cooldownEndTime);
    }

    function getPatentFee(address account, uint16[5] inputAssets, uint256 sCount) external view returns (uint256) {

        uint256 totalFee = 0;
        uint256 _assetLevel;
        Patent memory _patent;
        uint16 currentAsset;

        for (uint256 i = 0; i < 5; i++) {
            currentAsset = inputAssets[i];
            if (currentAsset < 248) {

                // handle patent fee
                _assetLevel = assetLevel[currentAsset];
                if (_assetLevel > 0) {
                    _patent = patents[currentAsset];
                    if (_patent.patentOwner != address(0) && _patent.patentOwner != account && !_patent.onSale && (_patent.beginTime + patentValidTime > now)) {
                        totalFee += pFees[_assetLevel] * sCount;
                    }
                }
            }
        }
        return totalFee;
    }

    // getSynthesizationResult: auto synthesize daemin call this. if cooldown time has passed, give final result
    // Anyone can call this function, if they are willing to pay the gas
    function getSynthesizationResult(address account) external whenNotPaused {

        // Make sure this account is in synthesization
        require(accountsToFurnace[account].inSynthesization);

        // Make sure the cooldown has ends
        require(_isCooldownReady(account));

        // Get result using pending assets
        uint16[5] memory _pendingAssets = accountsToFurnace[account].pendingAssets;
        uint128 skinType = skinContract.getActiveSkin(account);
        uint16[5] memory resultAssets; //= chemistry.turnOnFurnace(_pendingAssets, skinType);

        // Write result
        bytes32[8] memory asset = assets[account];

        bytes32 mask; // 0x11111111
        uint256 maskedValue;
        uint256 j;
        uint256 pos;

        for (uint256 k = 0; k < accountsToFurnace[account].count; k++) {
            resultAssets = chemistry.turnOnFurnace(_pendingAssets, skinType);
            for (uint256 i = 0; i < 5; i++) {
                if (resultAssets[i] < 248) {
                    j = resultAssets[i] / 31;
                    pos = resultAssets[i] % 31;
                    mask = bytes32(255) << (8 * pos);
                    maskedValue = uint256(asset[j] & mask);

                    require(maskedValue < (uint256(255) << (8*pos)));
                    maskedValue += (uint256(1) << (8*pos));
                    asset[j] = ((asset[j] ^ mask) & asset[j]) | bytes32(maskedValue);

                    // handle patent
                    if (resultAssets[i] > 3 && patents[resultAssets[i]].patentOwner == address(0)) {
                        patents[resultAssets[i]] = Patent({patentOwner: account,
                                                        beginTime: now,
                                                        onSale: false,
                                                        price: 0,
                                                        lastPrice: 100 finney,
                                                        sellTime: 0});
                        // Emit the event
                        emit RegisterCreator(account, resultAssets[i]);
                    }
                }
            }
        }


        // Mark this synthesization as finished
        accountsToFurnace[account].inSynthesization = false;
        accountsToFurnace[account].count = 0;
        assets[account] = asset;

        msg.sender.transfer(prePaidFee);

        emit SynthesizeSuccess(account);
    }
}
function synthesize(uint16[5] inputAssets, uint256 sCount) public payable whenNotPaused {
    require(isSynthesizeAllowed == true);
    // Check msg.sender is not in another synthesizing process
    require(accountsToFurnace[msg.sender].inSynthesization == false);
    //
    require(sCount <= maxSCount && sCount > 0);

    // Check whether assets are valid
    bytes32[8] memory asset = assets[msg.sender];

    bytes32 mask; // 0x11111111
    uint256 maskedValue;
    uint256 count;
    bytes32 _asset;
    uint256 pos;
    uint256 maxLevel = 0;
    uint256 totalFee = 0;
    uint256 _assetLevel;
    Patent memory _patent;
    uint16 currentAsset;

    for (uint256 i = 0; i < 5; i++) {
        currentAsset = inputAssets[i];
        if (currentAsset < 248) {
            _asset = asset[currentAsset / 31];
            pos = currentAsset % 31;
            mask = bytes32(255) << (8 * pos);
            maskedValue = uint256(_asset & mask);

            require(maskedValue >= (sCount << (8*pos)));
            maskedValue -= (sCount << (8*pos));
            _asset = ((_asset ^ mask) & _asset) | bytes32(maskedValue);
            asset[currentAsset / 31] = _asset;
            count += 1;

            // handle patent fee
            _assetLevel = assetLevel[currentAsset];
            if (_assetLevel > maxLevel) {
                maxLevel = _assetLevel;
            }

            if (_assetLevel > 0) {
                _patent = patents[currentAsset];
                if (_patent.patentOwner != address(0) && _patent.patentOwner != msg.sender && !_patent.onSale && (_patent.beginTime + patentValidTime > now)) {
                    maskedValue = pFees[_assetLevel] * sCount;
                    _patent.patentOwner.transfer(maskedValue / 10000 * feeRatio);
                    totalFee += maskedValue;
                }
            }
        }
    }

    require(msg.value >= prePaidFee + totalFee);

    require(count >= 2 && count <= 5);

    // Check whether cooldown has ends
    require(_isCooldownReady(msg.sender));

    uint128 skinType = skinContract.getActiveSkin(msg.sender);
    uint256 _cooldownTime = chemistry.computeCooldownTime(skinType, cooldownLevels[maxLevel]);

    accountsToFurnace[msg.sender].pendingAssets = inputAssets;
    accountsToFurnace[msg.sender].cooldownEndTime = now + _cooldownTime;
    accountsToFurnace[msg.sender].inSynthesization = true;
    accountsToFurnace[msg.sender].count = sCount;
    assets[msg.sender] = asset;

    // Emit SnthesizeStart event
    // SynthesizeStart(msg.sender);
    emit AutoSynthesize(msg.sender, accountsToFurnace[msg.sender].cooldownEndTime);
}

// synthesize: call _isCooldownReady, pending assets, fire SynthesizeStart event