contract LinearStaking is AbstractStaking {
    using SafeMath for uint256;

    mapping(address=>Staking) public stakingInfo;

    struct Staking {
        uint256 amount;
        uint256 totalAmountTime; // staking amount*time
        uint256 lastUpdatedTimestamp;
    }

    /**
     * @param stakingBPT The BPT token users deposit as stake.
     * @param bellaToken The bonus token is bella.
     * @param admin The admin address
     * @param _startTime Timestamp that user can stake
     */
    constructor(
        IERC20 stakingBPT,
        IERC20 bellaToken,
        address admin,
        uint256 _startTime
        ) AbstractStaking(
            stakingBPT,
            bellaToken,
            admin,
            _startTime
        ) public {}

    /**
     * @return The user's total staking BPT amount
     */
    function totalStakedFor(address user) public view override returns (uint256) {
        return stakingInfo[user].amount;
    }

    /**
     * @dev Update a specific user's state due to more time cumulated and/or new BPT staking token
     * @param amount New BPT staking deposited (can be 0)
     * @param user The account to be updated
     */
    function _updateUserStaking(uint256 amount, address user) internal override {
        Staking memory userInfo = stakingInfo[user];
        uint256 additionalAmountTime = userInfo.amount.mul(now.sub(userInfo.lastUpdatedTimestamp));
        userInfo.totalAmountTime = userInfo.totalAmountTime.add(additionalAmountTime);
        userInfo.amount = userInfo.amount.add(amount);
        userInfo.lastUpdatedTimestamp = now;
        stakingInfo[user] = userInfo;
    }

    /**
     * @dev Calculate user's total cumulated reward and burn his/her all staking amount*time
     * @return User cumulated reward bella during the staking process
     */
    function _calculateRewardAndBurnAll(address user) internal override returns (uint256) {
        Staking memory userInfo = stakingInfo[user];
        uint256 reward = unlockedPool.balance().mul(userInfo.totalAmountTime).div(totalStakingAmountTime);

        totalStakingAmountTime = totalStakingAmountTime.sub(userInfo.totalAmountTime);

        stakingInfo[user].totalAmountTime = 0;

        return reward;
    }

    /**
     * @dev Calculate user's total cumulated reward and staking,
     * and remove him/her from the staking process
     * @return [1] User cumulated staking BPT
     * @return [2] User cumulated reward bella during the staking process
     */
    function _calculateRewardAndCleanUser(address user) internal override returns (uint256, uint256) {
        Staking memory userInfo = stakingInfo[user];
        uint256 reward = unlockedPool.balance().mul(userInfo.totalAmountTime).div(totalStakingAmountTime);
        uint256 staking = userInfo.amount;

        totalStakingAmountTime = totalStakingAmountTime.sub(userInfo.totalAmountTime);
        totalStakingAmount = totalStakingAmount.sub(userInfo.amount);

        delete stakingInfo[user];

        return (staking, reward);
    }

    /**
     * @dev Internal function to calculate user's total rewards
     * @return A specific user's total rewards when clamining
     */
    function _totalRewardsFor(address user) internal view override returns (uint256) {

        // calculate new total staking amount*time
        uint256 additionalAmountTime = totalStakingAmount.mul(now.sub(lastUpdatedTimestamp));
        uint256 newTotalStakingAmountTime = totalStakingAmountTime.add(additionalAmountTime);

        // calculate new user staking
        Staking memory userInfo = stakingInfo[user];
        uint256 additionalUserAmountTime = userInfo.amount.mul(now.sub(userInfo.lastUpdatedTimestamp));
        uint256 newUserTotalAmountTime = userInfo.totalAmountTime.add(additionalUserAmountTime);

        // calculate total unlocked pool
        uint256 unlockedAmount = unlockedPool.balance();
        if (currentUnlockCycle != 0) {
            uint256 timeDelta = now.sub(lastUnlockTime);
            if (currentUnlockCycle < timeDelta) {
                unlockedAmount = unlockedAmount.add(lockedPool.balance());
            } else {
                uint256 additionalAmount = lockedPool.balance().mul(timeDelta).div(currentUnlockCycle);
                unlockedAmount = unlockedAmount.add(additionalAmount);
            }
        }

        uint256 reward = unlockedAmount.mul(newUserTotalAmountTime).div(newTotalStakingAmountTime); 
        return reward;       
    }
}
/**
 * @title Linear Staking
 * @dev A staking pool for user to stake Balancer BPT token and get bella as reward.
 * The reward is always proportional to the amount*time of the staking.
 * The reward is added by the admin at the 0th, 30th and 60th day, respectively.
 */
function _updateUserStaking(uint256 amount, address user) internal override {
    Staking memory userInfo = stakingInfo[user];
    uint256 additionalAmountTime = userInfo.amount.mul(now.sub(userInfo.lastUpdatedTimestamp));
    userInfo.totalAmountTime = userInfo.totalAmountTime.add(additionalAmountTime);
    userInfo.amount = userInfo.amount.add(amount);
    userInfo.lastUpdatedTimestamp = now;
    stakingInfo[user] = userInfo;
}

/**
 * @dev Update a specific user's state due to more time cumulated and/or new BPT staking token
 * @param amount New BPT staking deposited (can be 0)
 * @param user The account to be updated
 */