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

    // Info of each user.
    struct UserInfo {
        uint256 amount;     // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
        uint256 rewardDebtAtBlock; // the last block user stake
        //
        // We do some fancy math here. Basically, any point in time, the amount of TEXs
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accTEXPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accTEXPerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken;           // Address of LP token contract.
        uint256 allocPoint;       // How many allocation points assigned to this pool. TEXs to distribute per block.
        uint256 lastRewardBlock;  // Last block number that TEXs distribution occurs.
        uint256 accTEXPerShare; // Accumulated TEXs per share, times 1e12. See below.
    }

    uint256 private _totalLock;
    mapping(address => uint256) private _locks;
    mapping(address => uint256) private _lastUnlockBlock;

    // The TEX TOKEN!
    TexToken public tex;
    // Dev address.
    address public devaddr;
    // TEX tokens created per block.
    uint256 public REWARD_PER_BLOCK; // 1000000000000000000
    // Bonus muliplier for early TEX makers.
    uint256[] public REWARD_MULTIPLIER = [8, 8, 8, 4, 4, 4, 2, 2, 2, 1];
    uint256[] public HALVING_AT_BLOCK; // init in constructor function
    uint256 public FINISH_BONUS_AT_BLOCK;

    // The block number when TEX mining starts.
    uint256 public START_BLOCK;

    uint256 public constant PERCENT_LOCK_BONUS_REWARD = 75; // lock 75% of bounus reward in 1 year
    uint256 public constant PERCENT_FOR_DEV = 5; // 10% reward for dev

    uint256 public burnPercent = 3; // init 0% burn tex

    uint256 public lockFromBlock;
    uint256 public lockToBlock;

    // The migrator contract. It has a lot of power. Can only be set through governance (owner).
    IMigratorToTEXSwap public migrator;

    // Info of each pool.
    PoolInfo[] public poolInfo;
    mapping(address => uint256) public poolId1; // poolId1 count from 1, subtraction 1 before using with poolInfo
    // Info of each user that stakes LP tokens. pid => user address => info
    mapping (uint256 => mapping (address => UserInfo)) public userInfo;
    // Total allocation poitns. Must be the sum of all allocation points in all pools.
    uint256 public totalAllocPoint = 0;

    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);
    event SendTEXReward(address indexed user, uint256 indexed pid, uint256 amount, uint256 lockAmount);
    event Lock(address indexed to, uint256 value);

    constructor(
        TexToken _tex,
        address _devaddr,
        uint256 _rewardPerBlock,
        uint256 _startBlock,
        uint256 _halvingAfterBlock
    ) public {
        tex = _tex;
        devaddr = _devaddr;
        REWARD_PER_BLOCK = _rewardPerBlock;
        START_BLOCK = _startBlock;
        for (uint256 i = 0; i < REWARD_MULTIPLIER.length - 1; i++) {
            uint256 halvingAtBlock = _halvingAfterBlock.mul(i + 1).add(_startBlock);
            HALVING_AT_BLOCK.push(halvingAtBlock);
        }
        FINISH_BONUS_AT_BLOCK = _halvingAfterBlock.mul(REWARD_MULTIPLIER.length - 1).add(_startBlock);
        HALVING_AT_BLOCK.push(uint256(-1));

        lockFromBlock = block.number + 12742826;
        lockToBlock = lockFromBlock + 12742826;
    }

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

    // Add a new lp to the pool. Can only be called by the owner.
    function add(uint256 _allocPoint, IERC20 _lpToken, bool _withUpdate) public onlyOwner {
        require(poolId1[address(_lpToken)] == 0, "TEXMasterFarmer::add: lp is already in pool");
        if (_withUpdate) {
            massUpdatePools();
        }
        uint256 lastRewardBlock = block.number > START_BLOCK ? block.number : START_BLOCK;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolId1[address(_lpToken)] = poolInfo.length + 1;
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardBlock: lastRewardBlock,
            accTEXPerShare: 0
        }));
    }

    // Update the given pool's TEX allocation point. Can only be called by the owner.
    function set(uint256 _pid, uint256 _allocPoint, bool _withUpdate) public onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
        poolInfo[_pid].allocPoint = _allocPoint;
    }

    // Set the migrator contract. Can only be called by the owner.
    function setMigrator(IMigratorToTEXSwap _migrator) public onlyOwner {
        migrator = _migrator;
    }

    // Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
    function migrate(uint256 _pid) public {
        require(address(migrator) != address(0), "migrate: no migrator");
        PoolInfo storage pool = poolInfo[_pid];
        IERC20 lpToken = pool.lpToken;
        uint256 bal = lpToken.balanceOf(address(this));
        lpToken.safeApprove(address(migrator), bal);
        IERC20 newLpToken = migrator.migrate(lpToken);
        require(bal == newLpToken.balanceOf(address(this)), "migrate: bad");
        pool.lpToken = newLpToken;
    }

    // Update reward variables for all pools. Be careful of gas spending!
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        uint256 texForBurn;
        uint256 texForDev;
        uint256 texForFarmer;
        (texForBurn, texForDev, texForFarmer) = getPoolReward(pool.lastRewardBlock, block.number, pool.allocPoint);

        if (texForBurn > 0) {
            tex.mint(address(this), texForBurn);
            tex.burn(texForBurn);
        }

        if (texForDev > 0) {
            // Mint unlocked amount for Dev
            tex.mint(devaddr, texForDev.mul(100 - PERCENT_LOCK_BONUS_REWARD).div(100));
            // Mint locked amount for Dev
            //For more simple, I lock reward for dev if mint reward in bonus time
            tex.mint(address(this), texForDev.mul(PERCENT_LOCK_BONUS_REWARD).div(100));
            farmLock(devaddr, texForDev.mul(PERCENT_LOCK_BONUS_REWARD).div(100));
        }
        tex.mint(address(this), texForFarmer);
        pool.accTEXPerShare = pool.accTEXPerShare.add(texForFarmer.mul(1e12).div(lpSupply));
        pool.lastRewardBlock = block.number;
    }

    // |--------------------------------------|
    // [20, 30, 40, 50, 60, 70, 80, 99999999]
    // Return reward multiplier over the given _from to _to block.
    function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
        uint256 result = 0;
        if (_from < START_BLOCK) return 0;

        for (uint256 i = 0; i < HALVING_AT_BLOCK.length; i++) {
            uint256 endBlock = HALVING_AT_BLOCK[i];

            if (_to <= endBlock) {
                uint256 m = _to.sub(_from).mul(REWARD_MULTIPLIER[i]);
                return result.add(m);
            }

            if (_from < endBlock) {
                uint256 m = endBlock.sub(_from).mul(REWARD_MULTIPLIER[i]);
                _from = endBlock;
                result = result.add(m);
            }
        }

        return result;
    }

    function getPoolReward(uint256 _from, uint256 _to, uint256 _allocPoint) public view returns (uint256 forBurn, uint256 forDev, uint256 forFarmer) {
        uint256 multiplier = getMultiplier(_from, _to);
        uint256 amount = multiplier.mul(REWARD_PER_BLOCK).mul(_allocPoint).div(totalAllocPoint);
        uint256 texCanMint = tex.cap().sub(tex.totalSupply());

        if (texCanMint < amount) {
            forBurn = 0;
            forDev = 0;
            forFarmer = texCanMint;
        }
        else {
            forBurn = amount.mul(burnPercent).div(100);
            forDev = amount.mul(PERCENT_FOR_DEV).div(100);
            forFarmer = amount.mul(100 - burnPercent - PERCENT_FOR_DEV).div(100);
        }
    }

    // View function to see pending TEXs on frontend.
    function pendingReward(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accTEXPerShare = pool.accTEXPerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (block.number > pool.lastRewardBlock && lpSupply > 0) {
            uint256 texForFarmer;
            (, , texForFarmer) = getPoolReward(pool.lastRewardBlock, block.number, pool.allocPoint);
            accTEXPerShare = accTEXPerShare.add(texForFarmer.mul(1e12).div(lpSupply));

        }
        return user.amount.mul(accTEXPerShare).div(1e12).sub(user.rewardDebt);
    }

    function claimReward(uint256 _pid) public {
        updatePool(_pid);
        _harvest(_pid);
    }

    // lock 75% of reward if it come from bounus time
    function _harvest(uint256 _pid) internal {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accTEXPerShare).div(1e12).sub(user.rewardDebt);
            uint256 masterBal = tex.balanceOf(address(this));

            if (pending > masterBal) {
                pending = masterBal;
            }

            if(pending > 0) {
                tex.transfer(msg.sender, pending.mul(100 - PERCENT_LOCK_BONUS_REWARD).div(100));
                uint256 lockAmount = 0;
                if (user.rewardDebtAtBlock <= FINISH_BONUS_AT_BLOCK) {
                    lockAmount = pending.mul(PERCENT_LOCK_BONUS_REWARD).div(100);
                    farmLock(msg.sender, lockAmount);
                }

                user.rewardDebtAtBlock = block.number;

                emit SendTEXReward(msg.sender, _pid, pending, lockAmount);
            }

            user.rewardDebt = user.amount.mul(pool.accTEXPerShare).div(1e12);
        }
    }

    // Deposit LP tokens to TEXMasterFarmer for TEX allocation.
    function deposit(uint256 _pid, uint256 _amount) public {
        require(_amount > 0, "TEXMasterFarmer::deposit: amount must be greater than 0");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        _harvest(_pid);
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        if (user.amount == 0) {
            user.rewardDebtAtBlock = block.number;
        }
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accTEXPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }

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

        updatePool(_pid);
        _harvest(_pid);

        if(_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accTEXPerShare).div(1e12);
        emit Withdraw(msg.sender, _pid, _amount);
    }

    // Withdraw without caring about rewards. 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.amount);
        emit EmergencyWithdraw(msg.sender, _pid, user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
    }

    // Safe tex transfer function, just in case if rounding error causes pool to not have enough TEXs.
    function safeTEXTransfer(address _to, uint256 _amount) internal {
        uint256 texBal = tex.balanceOf(address(this));
        if (_amount > texBal) {
            tex.transfer(_to, texBal);
        } else {
            tex.transfer(_to, _amount);
        }
    }

    // Update dev address by the previous dev.
    function dev(address _devaddr) public {
        require(msg.sender == devaddr, "dev: wut?");
        devaddr = _devaddr;
    }

    function getNewRewardPerBlock(uint256 pid1) public view returns (uint256) {
        uint256 multiplier = getMultiplier(block.number -1, block.number);
        if (pid1 == 0) {
            return multiplier.mul(REWARD_PER_BLOCK);
        }
        else {
            return multiplier
                .mul(REWARD_PER_BLOCK)
                .mul(poolInfo[pid1 - 1].allocPoint)
                .div(totalAllocPoint);
        }
    }

    function setBurnPercent(uint256 _burnPercent) public onlyOwner {
        require(_burnPercent > burnPercent, "error: set burn percent");
        burnPercent = _burnPercent;
    }

    function totalLock() public view returns (uint256) {
        return _totalLock;
    }

    function lockOf(address _holder) public view returns (uint256) {
        return _locks[_holder];
    }

    function lastUnlockBlock(address _holder) public view returns (uint256) {
        return _lastUnlockBlock[_holder];
    }

    function farmLock(address _holder, uint256 _amount) internal {
        require(_holder != address(0), "ERC20: lock to the zero address");
        require(_amount <= tex.balanceOf(address(this)), "ERC20: lock amount over blance");
        _locks[_holder] = _locks[_holder].add(_amount);
        _totalLock = _totalLock.add(_amount);
        if (_lastUnlockBlock[_holder] < lockFromBlock) {
            _lastUnlockBlock[_holder] = lockFromBlock;
        }
        emit Lock(_holder, _amount);
    }

    function canUnlockAmount(address _holder) public view returns (uint256) {
        if (block.number < lockFromBlock) {
            return 0;
        }
        else if (block.number >= lockToBlock) {
            return _locks[_holder];
        }
        else {
            uint256 releaseBlock = block.number.sub(_lastUnlockBlock[_holder]);
            uint256 numberLockBlock = lockToBlock.sub(_lastUnlockBlock[_holder]);
            return _locks[_holder].mul(releaseBlock).div(numberLockBlock);
        }
    }

    function unlock() public {
        require(_locks[msg.sender] > 0, "ERC20: cannot unlock");

        uint256 amount = canUnlockAmount(msg.sender);
        // just for sure
        if (amount > tex.balanceOf(address(this))) {
            amount = tex.balanceOf(address(this));
        }
        tex.transfer(msg.sender, amount);
        _locks[msg.sender] = _locks[msg.sender].sub(amount);
        _lastUnlockBlock[msg.sender] = block.number;
        _totalLock = _totalLock.sub(amount);
    }


    function setTransferBurnRate(uint256 _tranferBurnRate) public onlyOwner {
        tex.setTransferBurnRate(_tranferBurnRate);
    }

    // In some circumstance, we should not burn TEX on transfer, eg: Transfer from owner to distribute bounty, from depositing to swap for liquidity
    function addTransferBurnExceptAddress(address _transferBurnExceptAddress) public onlyOwner {
        tex.addTransferBurnExceptAddress(_transferBurnExceptAddress);
    }

    function removeTransferBurnExceptAddress(address _transferBurnExceptAddress) public onlyOwner {
        tex.removeTransferBurnExceptAddress(_transferBurnExceptAddress);
    }

    function updateEmissionRate(uint256 _rewardPerBlock) public onlyOwner {
        massUpdatePools();
        _rewardPerBlock = _rewardPerBlock;
    }
}
// TEXMasterFarmer is the master of TEX. He can make TEX and he is a fair guy.
//
// Note that it's ownable and the owner wields tremendous power. The ownership
// will be transferred to a governance smart contract once TEX is sufficiently
// distributed and the community can show to govern itself.
//
// Have fun reading it. Hopefully it's bug-free. God bless.
function safeTEXTransfer(address _to, uint256 _amount) internal {
    uint256 texBal = tex.balanceOf(address(this));
    if (_amount > texBal) {
        tex.transfer(_to, texBal);
    } else {
        tex.transfer(_to, _amount);
    }
}

// Safe tex transfer function, just in case if rounding error causes pool to not have enough TEXs.