contract ZyxTokenManager is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    struct LastDeposits {
        uint256 amount;
        uint256 rewardIndex;
    }

    // Info of each user.
    struct UserInfo {
        uint256 totalAmount;             // How many LP tokens the user has provided.
        uint256 amount;
        uint256 reward;                  // Reward to withdraw
        uint256 lastRewardBlock;
        LastDeposits[] lastDeposits;
    }

    uint256 public totalLpTokens = 0;


    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken;
        uint256 totalSupplyLp;
    }

    struct Reward {
        uint256 block;
        uint256 amount;
        uint256 totalLp;
    }

    ZyxToken public zyx;
    // Info of each pool.
    PoolInfo[] public poolInfo;
    // Rewards info
    Reward[] public rewards;


    // Info of each user that stakes LP tokens.
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;

    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);


    constructor(
        ZyxToken _zyx
    ) public {
        zyx = _zyx;
        rewards.push(
            Reward({
        block : block.number,
        amount : 0,
        totalLp : 0
        }));
    }

    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    function rewardsLength() external view returns (uint256) {
        return rewards.length;
    }

    function lastDeposits(uint256 _pid) external view returns (uint256,uint256) {
        UserInfo storage user = userInfo[_pid][msg.sender];
        return (user.lastDeposits[0].amount,user.lastDeposits[0].rewardIndex);
    }

    // Add a new lp to the pool. Can only be called by the owner.
    // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
    function add(IERC20 _lpToken) public onlyOwner {
        poolInfo.push(PoolInfo({
        lpToken : _lpToken,
        totalSupplyLp : 0
        }));
    }

    function calcReward(uint _pid, address sender) private returns (uint256 TotalReward, uint256 amount){
        UserInfo storage user = userInfo[_pid][sender];

        uint256 totalReward = 0;
        uint256 firstBlockIndex = 0;
        for (uint i = rewards.length - 1; i > 0; i--) {
            if (rewards[i].block <= user.lastRewardBlock) {
                firstBlockIndex = (i + 1);
                break;
            }
        }

        for (uint i = firstBlockIndex; i < rewards.length; i++) {
            Reward memory rewardBlock = rewards[i];
            if (rewardBlock.amount > 0 && rewardBlock.totalLp > 0) {
                uint256 UserReward = user.amount.mul(rewardBlock.amount).div(rewardBlock.totalLp);
                totalReward = totalReward.add(UserReward);
            }
            if (i == user.lastDeposits[0].rewardIndex) {
                user.amount = user.amount.add(user.lastDeposits[0].amount);
            }
        }

        if(user.amount > user.totalAmount){
            user.amount = user.totalAmount;
        }

        return (user.reward.add(totalReward), user.amount);
    }


    // Deposit LP tokens to ZyxToken for ZYX allocation.
    function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        if (user.totalAmount > 0) {
            (uint totalReward, uint amount) = calcReward(_pid, msg.sender);
            user.reward = totalReward;
            user.amount = amount;


            if (user.lastDeposits[0].rewardIndex == rewards.length) {
                user.lastDeposits[0].amount = user.lastDeposits[0].amount.add(_amount);
            } else {
                user.lastDeposits[0].amount = _amount;
                user.lastDeposits[0].rewardIndex = rewards.length;
            }
        } else {
            user.lastDeposits.push(LastDeposits({
            amount : _amount,
            rewardIndex : rewards.length
            }));
        }

        if(user.amount > user.totalAmount){
            user.amount = user.totalAmount;
        }

        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.lastRewardBlock = block.number;
        totalLpTokens = totalLpTokens.add(_amount);
        pool.totalSupplyLp = pool.totalSupplyLp.add(_amount);
        user.totalAmount = user.totalAmount.add(_amount);
        emit Deposit(msg.sender, _pid, _amount);
    }


    // Withdraw LP tokens from ZyxManager.
    function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.totalAmount >= _amount, "withdraw: not good");
        require(user.totalAmount > 0, "withdraw: no balance");


        (uint totalReward, uint amount) = calcReward(_pid, msg.sender);
        user.reward = totalReward;
        user.amount = amount;

        uint256 Amount = 0;
        if(_amount > user.lastDeposits[0].amount){
            Amount = _amount.sub(user.lastDeposits[0].amount);
            user.lastDeposits[0].amount = 0;
            user.amount = user.amount.sub(Amount);
        } else {
            user.lastDeposits[0].amount = user.lastDeposits[0].amount.sub(_amount);
        }


        user.totalAmount = user.totalAmount.sub(_amount);
        if(user.totalAmount == 0){
            user.amount = 0;
            user.lastDeposits.pop();
        }

        if(user.amount > user.totalAmount){
            user.amount = user.totalAmount;
        }

        safeZyxTransfer(msg.sender, user.reward);
        user.reward = 0;

        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        pool.totalSupplyLp = pool.totalSupplyLp.sub(_amount);
        user.lastRewardBlock = block.number;
        totalLpTokens = totalLpTokens.sub(_amount);
        emit Withdraw(msg.sender, _pid, _amount);
    }

    // EMERGENCY ONLY
    function emergencyWithdraw(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        pool.lpToken.safeTransfer(address(msg.sender), user.totalAmount);
        user.totalAmount = 0;
        user.reward = 0;
        user.lastDeposits.pop();
        user.amount = 0;
        user.lastRewardBlock = block.number;
        emit EmergencyWithdraw(msg.sender, _pid, user.totalAmount);
    }

    function distributeReward(uint256 amount) public onlyOwner {
        rewards.push(
            Reward({
        block : block.number,
        amount : amount,
        totalLp : totalLpTokens
        }));
    }

    // View function to see pending ZYX's on frontend.
    function pendingZyx(uint256 _pid, address _user) external view returns (uint256) {
        UserInfo memory user = userInfo[_pid][_user];

        uint256 totalReward = 0;
        uint256 firstBlockIndex = 0;
        for (uint i = rewards.length - 1; i > 0; i--) {
            if (rewards[i].block <= user.lastRewardBlock) {
                firstBlockIndex = (i + 1);
                break;
            }
        }

        for (uint i = firstBlockIndex; i < rewards.length; i++) {
            Reward memory rewardBlock = rewards[i];
            if (rewardBlock.amount > 0 && rewardBlock.totalLp > 0) {
                uint256 UserReward = user.amount.mul(rewardBlock.amount).div(rewardBlock.totalLp);
                totalReward = totalReward.add(UserReward);
            }
            if (i == user.lastDeposits[0].rewardIndex) {
                user.amount = user.amount.add(user.lastDeposits[0].amount);
            }
        }
        return user.reward.add(totalReward);
    }

    function safeZyxTransfer(address _to, uint256 _amount) internal {
        uint256 zyxBal = zyx.balanceOf(address(this));
        if (_amount > zyxBal) {
            zyx.transfer(_to, zyxBal);
        } else {
            zyx.transfer(_to, _amount);
        }
    }


}
function add(IERC20 _lpToken) public onlyOwner {
    poolInfo.push(PoolInfo({
    lpToken : _lpToken,
    totalSupplyLp : 0
    }));
}

// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.