contract PoolStore is IPoolStore, IPoolStoreGov, Operator {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /* ================= DATA STRUCTURE ================= */

    struct Pool {
        string name;
        IERC20 token;
        uint256 weight;
        uint256 totalSupply;
    }

    /* ================= STATES ================= */

    uint256 public override totalWeight = 0;

    Pool[] public pools;
    mapping(uint256 => mapping(address => uint256)) balances;
    mapping(address => uint256[]) public indexByToken;

    bool public emergency = false;
    address public weightFeeder;

    constructor() Operator() {
        weightFeeder = _msgSender();
    }

    /* ================= GOV - OWNER ONLY ================= */

    /**
     * @dev CAUTION: DO NOT USE IN NORMAL SITUATION
     * @notice Enable emergency withdraw
     */
    function reportEmergency() public override onlyOwner {
        emergency = true;
        emit EmergencyReported(_msgSender());
    }

    /**
     * @dev CAUTION: DO NOT USE IN NORMAL SITUATION
     * @notice Disable emergency withdraw
     */
    function resolveEmergency() public override onlyOwner {
        emergency = false;
        emit EmergencyResolved(_msgSender());
    }

    /*
     * @param _newFeeder weight feeder address to change
     */
    function setWeightFeeder(address _newFeeder) public override onlyOwner {
        address oldFeeder = weightFeeder;
        weightFeeder = _newFeeder;
        emit WeightFeederChanged(_msgSender(), oldFeeder, _newFeeder);
    }

    /**
     * @param _token pool token
     * @param _weight pool weight
     */
    function addPool(
        string memory _name,
        IERC20 _token,
        uint256 _weight
    ) public override onlyOwner {
        totalWeight = totalWeight.add(_weight);

        uint256 index = pools.length;
        indexByToken[address(_token)].push(index);

        pools.push(
            Pool({name: _name, token: _token, weight: _weight, totalSupply: 0})
        );
        emit PoolAdded(_msgSender(), index, _name, address(_token), _weight);
    }

    /**
     * @param _pid pool id
     * @param _weight target pool weight
     */
    function setPool(uint256 _pid, uint256 _weight)
        public
        override
        onlyWeightFeeder
        checkPoolId(_pid)
    {
        Pool memory pool = pools[_pid];

        uint256 oldWeight = pool.weight;
        totalWeight = totalWeight.add(_weight).sub(pool.weight);
        pool.weight = _weight;

        pools[_pid] = pool;

        emit PoolWeightChanged(_msgSender(), _pid, oldWeight, _weight);
    }

    /**
     * @param _pid pool id
     * @param _name name of pool
     */
    function setPool(uint256 _pid, string memory _name)
        public
        override
        checkPoolId(_pid)
        onlyOwner
    {
        string memory oldName = pools[_pid].name;
        pools[_pid].name = _name;

        emit PoolNameChanged(_msgSender(), _pid, oldName, _name);
    }

    /* ================= MODIFIER ================= */

    modifier onlyWeightFeeder {
        require(_msgSender() == weightFeeder, 'PoolStore: unauthorized');

        _;
    }

    modifier checkPoolId(uint256 _pid) {
        require(_pid <= pools.length, 'PoolStore: invalid pid');

        _;
    }

    /* ================= CALLS - ANYONE ================= */
    /**
     * @return total pool length
     */
    function poolLength() public view override returns (uint256) {
        return pools.length;
    }

    /**
     * @param _token pool token address
     * @return pool id
     */
    function poolIdsOf(address _token)
        public
        view
        override
        returns (uint256[] memory)
    {
        return indexByToken[_token];
    }

    /**
     * @param _pid pool id
     * @return name of pool
     */
    function nameOf(uint256 _pid)
        public
        view
        override
        checkPoolId(_pid)
        returns (string memory)
    {
        return pools[_pid].name;
    }

    /**
     * @param _pid pool id
     * @return pool token
     */
    function tokenOf(uint256 _pid)
        public
        view
        override
        checkPoolId(_pid)
        returns (address)
    {
        return address(pools[_pid].token);
    }

    /**
     * @param _pid pool id
     * @return pool weight
     */
    function weightOf(uint256 _pid)
        public
        view
        override
        checkPoolId(_pid)
        returns (uint256)
    {
        return pools[_pid].weight;
    }

    /**
     * @param _pid pool id
     * @return total staked token amount
     */
    function totalSupply(uint256 _pid)
        public
        view
        override
        checkPoolId(_pid)
        returns (uint256)
    {
        return pools[_pid].totalSupply;
    }

    /**
     * @param _pid pool id
     * @param _sender staker address
     * @return staked amount of user
     */
    function balanceOf(uint256 _pid, address _sender)
        public
        view
        override
        checkPoolId(_pid)
        returns (uint256)
    {
        return balances[_pid][_sender];
    }

    /* ================= TXNS - OPERATOR ONLY ================= */

    /**
     * @param _pid pool id
     * @param _owner stake address
     * @param _amount stake amount
     */
    function deposit(
        uint256 _pid,
        address _owner,
        uint256 _amount
    ) public override checkPoolId(_pid) onlyOperator {
        pools[_pid].totalSupply = pools[_pid].totalSupply.add(_amount);
        balances[_pid][_owner] = balances[_pid][_owner].add(_amount);
        IERC20(tokenOf(_pid)).safeTransferFrom(
            _msgSender(),
            address(this),
            _amount
        );

        emit Deposit(_msgSender(), _owner, _pid, _amount);
    }

    function _withdraw(
        uint256 _pid,
        address _owner,
        uint256 _amount
    ) internal {
        pools[_pid].totalSupply = pools[_pid].totalSupply.sub(_amount);
        balances[_pid][_owner] = balances[_pid][_owner].sub(_amount);
        IERC20(tokenOf(_pid)).safeTransfer(_msgSender(), _amount);

        emit Withdraw(_msgSender(), _owner, _pid, _amount);
    }

    /**
     * @param _pid pool id
     * @param _owner stake address
     * @param _amount stake amount
     */
    function withdraw(
        uint256 _pid,
        address _owner,
        uint256 _amount
    ) public override checkPoolId(_pid) onlyOperator {
        _withdraw(_pid, _owner, _amount);
    }

    /**
     * @notice Anyone can withdraw its balance even if is not the operator
     * @param _pid pool id
     */
    function emergencyWithdraw(uint256 _pid) public override checkPoolId(_pid) {
        require(emergency, 'PoolStore: not in emergency');
        _withdraw(_pid, msg.sender, balanceOf(_pid, _msgSender()));
    }
}
function reportEmergency() public override onlyOwner {
    emergency = true;
    emit EmergencyReported(_msgSender());
}

/**
 * @dev CAUTION: DO NOT USE IN NORMAL SITUATION
 * @notice Enable emergency withdraw
 */