// Contract: VetherPools
// Target function: function buyAsset(address pool, uint amount) public payable returns (uint outputAmount)
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.11;
pragma experimental ABIEncoderV2;

// ERC20 Interface
interface ERC20 {
    function totalSupply() external view returns (uint);
    function balanceOf(address account) external view returns (uint);
    function transfer(address, uint) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint);
    function approve(address, uint) external returns (bool);
    function transferFrom(address, address, uint) external returns (bool);
}
interface POOLS {
    function stakeForMember(uint inputVether, uint inputAsset, address pool, address member) external payable returns (uint units);
}
// Safe Math
library SafeMath {
    function sub(uint a, uint b) internal pure returns (uint) {
        assert(b <= a);
        return a - b;
    }

    function add(uint a, uint b) internal pure returns (uint)   {
        uint c = a + b;
        assert(c >= a);
        return c;
    }

    function mul(uint a, uint b) internal pure returns (uint) {
        if (a == 0) {
            return 0;
        }
        uint c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    function div(uint a, uint b) internal pure returns (uint) {
        require(b > 0, "SafeMath: division by zero");
        uint c = a / b;
        return c;
    }
}

contract VetherPools {
    using SafeMath for uint;

    address public VETHER;
    uint public one = 10**18;
    uint public ETHCAP = 10 * one;
    uint public DAY = 86400;
    uint public DAYCAP = 30*DAY;

    address[] public arrayPools;
    uint public poolCount;
    mapping(address => address[]) public mapPoolStakers;
    mapping(address => PoolData) public poolData;
    struct PoolData {
        bool listed;
        uint genesis;
        uint vether;
        uint asset;
        uint vetherStaked;
        uint assetStaked;
        uint stakerCount;
        uint poolUnits;
        uint fees;
        uint volume;
        uint txCount;
    }
    
    address[] public arrayMembers;
    uint public memberCount;
    mapping(address => MemberData) public memberData;
    struct MemberData {
        address[] arrayPools;
        uint poolCount;
        mapping(address => StakeData) stakeData;
    }

    struct StakeData {
        uint vether;
        uint asset;
        uint stakeUnits;
    }
   
    event Staked(address pool, address member, uint inputAsset, uint inputVether, uint unitsIssued);
    event Unstaked(address pool, address member, uint outputAsset, uint outputVether, uint unitsClaimed);
    event Swapped(address assetFrom, address assetTo, uint inputAmount, uint transferAmount, uint outPutAmount, uint fee, address recipient);

    constructor () public payable {
        VETHER = 0x4Ba6dDd7b89ed838FEd25d208D4f644106E34279;
    }

    receive() external payable {
        buyAsset(address(0), msg.value);
    }

    //==================================================================================//
    // Staking functions

    function stake(uint inputVether, uint inputAsset, address pool) public payable returns (uint units) {
        units = stakeForMember(inputVether, inputAsset, pool, msg.sender);
        return units;
    }

    function stakeForMember(uint inputVether, uint inputAsset, address pool, address member) public payable returns (uint units) {
        require(pool == address(0), "Must be Eth");
        if (!poolData[pool].listed) { 
            require((inputAsset > 0 && inputVether > 0), "Must get both assets for new pool");
            _createNewPool(pool);
        }
        require((poolData[pool].asset + inputAsset <= ETHCAP), "Must not exceed ETH CAP");
        uint actualInputAsset = _handleTransferIn(pool, inputAsset);
        uint actualInputVether = _handleTransferIn(VETHER, inputVether);
        units = _stake(actualInputVether, actualInputAsset, pool, member);
        return units;
    }

    function _createNewPool(address _pool) internal {
        arrayPools.push(_pool);
        poolCount += 1;
        poolData[_pool].listed = true;
        poolData[_pool].genesis = now;
    }

    function _stake(uint _vether, uint _asset, address _pool, address _member) internal returns (uint _units) {
        uint _V = poolData[_pool].vether.add(_vether);
        uint _A = poolData[_pool].asset.add(_asset);
        _units = calcStakeUnits(_asset, _A, _vether, _V);   
        _incrementPoolBalances(_units, _vether, _asset, _pool);                                                  
        _addDataForMember(_member, _units, _vether, _asset, _pool);
        emit Staked(_pool, _member, _asset, _vether, _units);
        return _units;
    }

    //==================================================================================//
    // Unstaking functions

    // Unstake % for self
    function unstake(uint basisPoints, address pool) public returns (bool success) {
        _unstakeToExact(msg.sender, basisPoints, pool);
        return true;
    }
    // Internal - Convert to Exact with Checks
    function _unstakeToExact(address payable member, uint basisPoints, address pool) internal returns (bool success) {
        require(pool == address(0), "Must be Eth");
        require(poolData[pool].listed, "Must be listed");
        require((basisPoints > 0 && basisPoints <= 10000), "Must be valid BasisPoints");
        uint _units = calcPart(basisPoints, memberData[member].stakeData[pool].stakeUnits);
        _unstake(msg.sender, _units, pool);
        return true;
    }
    // Unstake an exact qty of units
    function unstakeExact(uint units, address pool) public returns (bool success) {
        _unstake(msg.sender, units, pool);
        return true;
    }
    // Internal - Unstake function
    function _unstake(address payable member, uint units, address pool) internal returns (bool success) {
        require(pool == address(0), "Must be Eth");
        require(memberData[msg.sender].stakeData[pool].stakeUnits >= units);
        uint _outputVether = calcShare(units, poolData[pool].poolUnits, poolData[pool].vether);
        uint _outputAsset = calcShare(units, poolData[pool].poolUnits, poolData[pool].asset);
        _handleUnstake(units, _outputVether, _outputAsset, member, pool);
        return true;
    }

    // Unstake % Asymmetrically
    function unstakeAsymmetric(uint basisPoints, address pool, bool toVether) public returns (uint outputAmount){
        require(pool == address(0), "Must be Eth");
        uint _units = calcPart(basisPoints, memberData[msg.sender].stakeData[pool].stakeUnits);
        outputAmount = unstakeExactAsymmetric(_units, pool, toVether);
        return outputAmount;
    }
    // Unstake Exact Asymmetrically
    function unstakeExactAsymmetric(uint units, address pool, bool toVether) public returns (uint outputAmount){
        require(pool == address(0), "Must be Eth");
        require((memberData[msg.sender].stakeData[pool].stakeUnits >= units), "Must own the units");
        uint poolUnits = poolData[pool].poolUnits;
        require(units < poolUnits, "Must not be last staker");
        uint _outputVether; uint _outputAsset; 
        if(toVether){
            _outputVether = calcAsymmetricShare(units, poolUnits, poolData[pool].vether);
            _outputAsset = 0;
            outputAmount = _outputVether;
        } else {
            _outputVether = 0;
            _outputAsset = calcAsymmetricShare(units, poolUnits, poolData[pool].asset);
            outputAmount = _outputAsset;
        }
        _handleUnstake(units, _outputVether, _outputAsset, msg.sender, pool);
        return outputAmount;
    }

    // Internal - handle Unstake
    function _handleUnstake(uint _units, uint _outputVether, uint _outputAsset, address payable _member, address _pool) internal {
        _decrementPoolBalances(_units, _outputVether, _outputAsset, _pool);
        _removeDataForMember(_member, _units, _pool);
        emit Unstaked(_pool, _member, _outputAsset, _outputVether, _units);
        _handleTransferOut(_pool, _outputAsset, _member);
        _handleTransferOut(VETHER, _outputVether, _member);
    } 

    //==================================================================================//
    // Upgrade functions

    // Upgrade from this contract to a new one - opt in
    function upgrade(address payable newContract) public {
        address pool = address(0);
        uint _units = memberData[msg.sender].stakeData[pool].stakeUnits;
        uint _outputVether = calcShare(_units, poolData[pool].poolUnits, poolData[pool].vether);
        uint _outputAsset = calcShare(_units, poolData[pool].poolUnits, poolData[pool].asset);
        _decrementPoolBalances(_units, _outputVether, _outputAsset, pool);
        _removeDataForMember(msg.sender, _units, pool);
        emit Unstaked(pool, msg.sender, _outputAsset, _outputVether, _units);
        ERC20(VETHER).approve(newContract, _outputVether);
        POOLS(newContract).stakeForMember{value:_outputAsset}(_outputVether, _outputAsset, pool, msg.sender);
    }

    // Unstake for member after Day Cap
    function unstakeForMember(address payable member, address pool) public returns (bool success) {
        require(now > poolData[pool].genesis + DAYCAP, "Must be after Day Cap");
        _unstakeToExact(member, 10000, pool);
        return true;
    }