contract Smith is ReentrancyGuard, Ownable, ERC721Holder{
    using SafeERC20 for IERC20;
    // OG loot contract
    ILoot loot = ILoot(0xFF9C1b15B16263C61d017ee9F65C50e4AE0113D7);
    // Token used as the trade in for the LootSmith
    IERC20 public lwxp = IERC20(0xf18eC76f918A89a5d790E7150DD468D750a5c4e8);
    // whether the lootSmith is open
    bool smithOpen = true;
    address lootV1WeaponContract = 0x0ac0ECc6D249F1383c5C7c2Ff4941Bd56DEcDd14;
    address lootV2WeaponContract;
    IUpgradeCalculator public upgradeCalculator;
    mapping(address => mapping(uint256 => SmithsLog)) public smithsLogs;
    mapping(address => bool) public lootEcosystemContracts;
    uint256 public wait;
    uint256 public cap;

    struct SmithsLog{
        bool isUpgrading;
        uint256 startTimestamp;
        uint256 duration;
        uint256 endTimestamp;
        uint256 xpAmount;
    }

    event TokenUpdated(
        address oldToken,
        address newToken
    );
    event Upgrade(
        uint256 tokenId,
        uint256 upgradeAmount
    );
    event SmithOpened(
        bool open
    );
    event LootEcosystemContractSet(
        address lootContract
    );
    event SmithsLogsUpdated(
        uint256 tokenId,
        address lootContractAddress,
        uint256 startTimestamp,
        uint256 duration,
        uint256 endTimestamp,
        uint256 amount
    );
    event WaitChanged(
        uint256 wait
    );

    /**
     * @notice allow the owners to set the tokens used as pay-in for the weaponSmith
     * @param  _token address of the new token
     */
    function setToken(address _token) external onlyOwner {
        emit TokenUpdated(address(lwxp), _token);
        lwxp = IERC20(_token);
    }

    /**
     * @notice allow the owners to open the smith
     * @param  _smithOpen bool to open or close the smith
     */
    function setSmithOpen(bool _smithOpen) external onlyOwner {
        emit SmithOpened(_smithOpen);
        smithOpen = _smithOpen;
    }

    function setLootV2WeaponContract(address _lootWeaponContract) external onlyOwner {
        lootV2WeaponContract = _lootWeaponContract;
    }

    function setCap(uint256 _cap) external onlyOwner {
        cap = _cap;
    }

    function setLootEcosystemContract(address _lootContract) external onlyOwner {
        emit LootEcosystemContractSet(_lootContract);
        lootEcosystemContracts[_lootContract] = true;
    }

    /**
     * @notice allow the owners to change the wait time
     * @param  _wait number to change the wait to
     */
    function setWait(uint256 _wait) external onlyOwner {
        emit WaitChanged(_wait);
        wait = _wait;
    }

    function setUpgradeCalculator(address _upgradeCalculator) external onlyOwner {
        upgradeCalculator = IUpgradeCalculator(_upgradeCalculator);
    }

    /**
     * @notice allow the owners to sweep any erc20 tokens sent to the contract
     * @param  _token address of the token to be swept
     * @param  _amount amount to be swept
     */
    function sweep(address _token, uint256 _amount) external onlyOwner {
        IERC20(_token).safeTransfer(msg.sender, _amount);
    }

    function upgrade(
        uint256 tokenId,
        address lootContractAddress,
        uint256 amount
    )
    external
    nonReentrant
    {
        require(smithOpen, "!smithOpen");
        require(lootEcosystemContracts[lootContractAddress], "Not a recognised loot contract");
        require(IERC721(lootContractAddress).ownerOf(tokenId) == msg.sender, "!owner");
        require(amount > 0, "!amount");
        require(amount <= cap, "amount above cap");
        require(!smithsLogs[lootContractAddress][tokenId].isUpgrading, "Loot already upgrading");

        lwxp.safeTransferFrom(msg.sender, address(this), amount);
        if (lootContractAddress == lootV1WeaponContract) {
            IERC721(lootContractAddress).safeTransferFrom(msg.sender, address(this), tokenId);
            if (!ILWXP(address(lwxp)).claimedByTokenId(tokenId)){
                ILWXP(address(lwxp)).claimById(tokenId);
                lwxp.safeTransfer(msg.sender, 100000e18);
            }
            ILootStats(lootV2WeaponContract).mintFromSmith(tokenId, msg.sender);
            lootContractAddress = lootV2WeaponContract;
        }
        smithsLogs[lootContractAddress][tokenId] =
        SmithsLog(
            {
            isUpgrading: true,
            startTimestamp: block.timestamp,
            duration: wait,
            endTimestamp: block.timestamp + wait,
            xpAmount: amount
            }
        );
        emit SmithsLogsUpdated(tokenId, lootContractAddress, block.timestamp, wait, block.timestamp + wait, amount);
    }

    function collect(uint256 tokenId, address lootContractAddress) external nonReentrant {
        require(IERC721(lootContractAddress).ownerOf(tokenId) == msg.sender, "!owner");
        SmithsLog memory smithsLog = smithsLogs[lootContractAddress][tokenId];
        require(smithsLog.isUpgrading, "Loot not upgrading");
        require(smithsLog.endTimestamp < block.timestamp, "Loot not upgraded yet");
        uint256 boostAmount = upgradeCalculator.calculateUpgrade(
                    smithsLog.duration,
                    smithsLog.xpAmount,
                    ILootStats(lootV2WeaponContract).getTotalPower(tokenId)
        );
        smithsLogs[lootContractAddress][tokenId].isUpgrading = false;
        ILootStats(lootContractAddress).smithUpgrade(tokenId, boostAmount);
    }

    constructor(address _upgradeCalculator, address lootWeaponV2, address lootWeaponV1) Ownable() {
        wait = 7200;
        cap = 500000e18;
        upgradeCalculator = IUpgradeCalculator(_upgradeCalculator);
        lootV2WeaponContract = lootWeaponV2;
        lootEcosystemContracts[lootWeaponV2] = true;
        lootEcosystemContracts[lootWeaponV1] = true;
    }

}
/**
 * @title  Smith
 * @notice The Smith is open. Looters, prepare to fight, visit the smith and upgrade your loot with LWXP and Time.
 */
function setWait(uint256 _wait) external onlyOwner {
    emit WaitChanged(_wait);
    wait = _wait;
}

/**
 * @notice allow the owners to change the wait time
 * @param  _wait number to change the wait to
 */