contract PureCrowdsale {
    using SafeMath for uint256;

    /**
     * @dev start and end timestamps where investments are allowed (both inclusive)
     */
    uint256 public startTime;
    uint256 public endTime;

    /**
     * @dev amount of already sold vPURE
     */
    uint256 private _soldvPURE;

    /**
     * @dev contract owner address
     */
    address private _owner;

    /**
     * @dev uniswap router address
     */
    address private _uniswapRouterAddress;

    /**
     * @dev vBRIGHT token address
     */
    address private _vBRIGHTAddress;

    /**
     * @dev vPURE token address
     */
    address private _vPUREAddress;

    /**
     * @dev VIVID token address
     */
    address private _VIVIDAddress;

    /**
     * @dev uniswap factory address
     */
    address private _uniswapFactory;

    /**
     * @dev uniswap router
     */
    IUniswapV2Router02 private _uniswapRouter02;

    /**
     * @dev helper constants to compute the vPURE/ETH ratio
     */
    uint256 private constant _VPURENUMERATOR = 1000;
    uint256 private constant _VPUREDENOMINATOR = 42;

    /**
     * @dev maximum and minimum eth needed for a purchase
     */
    uint256 private constant _ETHMIN = 1 * 10**18;
    uint256 private constant _ETHMAX = 50 * 10**18;
    
    uint256 private _ethMINValue;

    /**
     * @dev vPURE token
     */
    IERC20 private _vPUREToken;

    /**
     * @dev vBRIGHT token
     */
    IERC20 private _vBRIGHTToken;

    /**
     * @dev VIVID token
     */
    IERC20 private _VIVIDToken;

    /**
     * @dev WETH address
     */
    address private _weth;

    /**
     * struct to keep the amount of vPURE boght and the total ETH sent
     */
    struct trackCrowdsaleAmount {
        uint256 ethAmount;
        uint256 vPUREAmount;
    }

    /**
     * @dev it keeps the mapping between the vPURE sold to the specific address
     */
    mapping(address => trackCrowdsaleAmount) private _vCrowdsaleBalance;

    /**
     * event for token purchase logging
     * @param purchaser who paid for the tokens
     * @param ethAmount ETH paid for purchase
     * @param vPUREAmount amount of tokens purchased
     */
    event onVPURETokenPurchase(
        address indexed purchaser,
        uint256 ethAmount,
        uint256 vPUREAmount
    );

    /**
     * event for LP added to vBRIGHT
     * @param tokenAmount amount of tokens added to LP
     * @param ethAmount ETH paid for purchase
     * @param liquidity the LP
     * @param tokenAddress address
     */
    event onLpvAdded(
        uint256 tokenAmount,
        uint256 ethAmount,
        uint256 liquidity,
        address tokenAddress
    );

    /**
     * event for claim tokens action
     * @param beneficiary address
     * @param amount amount of VPURE
     */
    event onClaimPurchasedTokens(address beneficiary, uint256 amount);

    /**
     * @dev constructor
     */
    constructor() public {
        _owner = msg.sender;

        setVBRIGHTAddress(0x3E88f8C31E1F9D26c1904584EFbCA58b75F53568);

        setVPUREAddress(0x12ED21C3d2E966162c4C65Ea8a62d13061D46eb6);

        setVIVIDAddress(0xF32544D1Ab31814160054bD6371Db4DE389E0083);

        setUniswapRouterAddress(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);

        setUniswapFactoryAddress(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);

        startTime = 1605121200;
        endTime = startTime + 10 days;

        _weth = _uniswapRouter02.WETH();
        
        _ethMINValue = _ETHMIN;
    }

    /**
     * @dev access modifier to restrict different functionalities
     */
    modifier _onlyOwner() {
        require(msg.sender == _owner);
        _;
    }

    /**
     * @dev fallback function can be used to buy tokens
     */
    receive() external payable {
        buyTokens();
    }

    /**
     * @dev helper to set VIVID contract address
     * _onlyOwner available
     */
    function setVIVIDAddress(address VIVIDAddress) public _onlyOwner {
        _VIVIDAddress = VIVIDAddress;
        _VIVIDToken = IERC20(_VIVIDAddress);
    }

    /**
     * @dev helper to set vBRIGHT contract address
     * _onlyOwner available
     */
    function setVBRIGHTAddress(address vBRIGHTAddress) public _onlyOwner {
        _vBRIGHTAddress = vBRIGHTAddress;
        _vBRIGHTToken = IERC20(_vBRIGHTAddress);
    }

    /**
     * @dev helper to set vPURE contract address
     * _onlyOwner available
     */
    function setVPUREAddress(address vPUREAddress) public _onlyOwner {
        _vPUREAddress = vPUREAddress;
        _vPUREToken = IERC20(_vPUREAddress);
    }

    /**
     * @dev helper to set uniswap factory address
     * _onlyOwner available
     */
    function setUniswapFactoryAddress(address factoryAddress)
        public
        _onlyOwner
    {
        _uniswapFactory = factoryAddress;
    }

    /**
     * @dev helper to set uniswap router address
     * _onlyOwner available
     */
    function setUniswapRouterAddress(address routerAddress) public _onlyOwner {
        _uniswapRouterAddress = routerAddress;
        _uniswapRouter02 = IUniswapV2Router02(_uniswapRouterAddress);
    }

    /**
     * @dev set the start time for the crowdsale period
     * _onlyOwner available
     */
    function setStartTime(uint256 start) public _onlyOwner {
        startTime = start;
    }

    /**
     * @dev set the end time for the crowdsale period
     * _onlyOwner available
     */
    function setEndTime(uint256 end) public _onlyOwner {
        endTime = end;
    }

    /**
     * @dev set the minimum ETH purchase value, only unsed for testing purposes
     * _onlyOwner available
     */
    function setETHMin(uint256 ethMinValue) public _onlyOwner {
        _ethMINValue = ethMinValue;
    }
    
    /**
     * @return the vPURE balance available for the given address.
     * _onlyOwner available
     */
    function getvPUREBalance(address from)
        public
        view
        _onlyOwner
        returns (uint256)
    {
        return _vCrowdsaleBalance[from].vPUREAmount;
    }

    /**
     * @return the ETH balance available for the given address
     * _onlyOwner available
     */
    function getEthBalance(address from)
        public
        view
        _onlyOwner
        returns (uint256)
    {
        return _vCrowdsaleBalance[from].ethAmount;
    }

    /**
     * @return the ETH balance available for the contract address
     */
    function getAvailableEth() public view returns (uint256) {
        return address(this).balance;
    }

    /**
     * @return the vPURE balance available for the sender address
     */
    function getvPUREBalance() public view returns (uint256) {
        return _vCrowdsaleBalance[msg.sender].vPUREAmount;
    }

    /**
     * @return the ETH balance available for the sender address
     */
    function getEthBalance() public view returns (uint256) {
        return _vCrowdsaleBalance[msg.sender].ethAmount;
    }

    /**
     * @return the cap of the crowdsale
     * As long as the crowsale is running, vPURE is not yet delivered, so the capital will remain the same during the crowdsale.
     */
    function getCurrentCap() public view returns (uint256) {
        return _vPUREToken.balanceOf(address(this));
    }

    /**
     * @return the remaining cap of the crowdsale
     */
    function getAvailablevPURE() public view returns (uint256) {
        uint256 available = getCurrentCap();
        available = available.sub(_soldvPURE);
        return available;
    }

    /**
     * @return the ratio vPURE to ETH of the Crowdsale in wei
     */
    function getConversionvPUREtoETHRatio() public pure returns (uint256) {
        uint256 ratio = _VPURENUMERATOR * 10**18;
        ratio = ratio.div(_VPUREDENOMINATOR);
        return ratio;
    }

    /**
     * @return the price of vPURE in ETH of the Crowdsale in wei
     */
    function getPricevPUREinETH() public pure returns (uint256) {
        uint256 ratio = _VPUREDENOMINATOR * 10**18;
        ratio = ratio.div(_VPURENUMERATOR);
        return ratio;
    }

    /**
     * @return the min and max ETH required for a sale in wei
     */
    function getMinMaxETHForPurchase() public view returns (uint256, uint256) {
        return ( _ethMINValue, _ETHMAX );
    }

    /**
     * @return get the total amount of vPURE already sold
     */
    function getSoldvPURE() public view returns (uint256) {
        return _soldvPURE;
    }

    /**
     * @dev low level token purchase function
     */
    function buyTokens() public payable {
        uint256 ethAmount = msg.value;
        require(ethAmount >= _ethMINValue, "Minimum amount is 1 ETH");
        require(ethAmount <= _ETHMAX, "Maximum amount is 50 ETH");
        require(_isValidPurchase(ethAmount), "Wrong data sent to contract");
        require(!_isCapReached(), "Cap exceeded");

        // calculate token amount to be delivered
        uint256 vPUREAmount = ethAmount.mul(_VPURENUMERATOR);
        vPUREAmount = vPUREAmount.div(_VPUREDENOMINATOR);

        require(
            vPUREAmount <= getAvailablevPURE(),
            "Not enough vPURE available."
        );

        // update state
        _soldvPURE = _soldvPURE.add(vPUREAmount);

        // update the balance for the msg.sender
        _updateBalance(ethAmount, vPUREAmount);

        uint256 ethAmoutToLPvBRIGHT = ethAmount.mul(16).div(100); // 16% goes to LP for vBRIGHT;
        _adLp(ethAmoutToLPvBRIGHT, _vBRIGHTAddress);

        uint256 ethAmountToLpVIVID = ethAmount.mul(18).div(100); // 18% goes to LP for VIVID;
        // add LP for VIVID
        _adLp(ethAmountToLpVIVID, _VIVIDAddress);

        emit onVPURETokenPurchase(msg.sender, ethAmount, vPUREAmount);
    }
    
    /**
     * @dev low level token purchase function
     */
    

    /**
     * @dev withdraw the remaining tokens (UNIV2) at the end of presale period
     */
    function withdrawToken(address tokenAddress) public _onlyOwner {
        require(hasEnded(), "Presale still active");
        IERC20 token = IERC20(tokenAddress);
        uint256 balance = token.balanceOf(address(this));
        TransferHelper.safeTransfer(tokenAddress, msg.sender, balance);
    }

    /**
     * @dev withdraw 9.8% of ETH to be used for marketing purpose
     */
    function withdrawETHForMarketing() public _onlyOwner {
        uint256 balance = address(this).balance;
        require(balance >= 0, "Insuficient founds");
        uint256 amount = balance.mul(98).div(1000);
        TransferHelper.safeTransferETH(msg.sender, amount);
    }

    /**
     * @dev used by the Vivid Finance team to support development
     * this is allowed only after the presale has ended
     */
    function withdrawRemainingETH() public _onlyOwner {
        require(hasEnded(), "Presale still active");

        uint256 balance = address(this).balance;
        require(balance >= 0, "Insuficient founds");
        TransferHelper.safeTransferETH(msg.sender, balance);
    }

    /**
     * @return true if crowdsale event has ended or the cap has been reached
     */
    function hasEnded() public view returns (bool) {
        return now > endTime || _isCapReached();
    }

    /**
     * @dev Checks whether the cap has been reached.
     * @return bool
     */
    function _isCapReached() private view returns (bool) {
        return getAvailablevPURE() == 0;
    }

    /**
     * @dev Checks whether the presale is active.
     * @return bool
     */
    function isActive() public view returns (bool) {
        return now > startTime && !hasEnded();
    }

    /**
     * @dev used to claim de purchased tokens at the end of presale event
     */
    function claimvPURE() public {
        require(hasEnded(), "Presale still active.");

        uint256 pureAmount = _vCrowdsaleBalance[msg.sender].vPUREAmount;
        require(pureAmount > 0, "No purchase has been made");
        _processPurchase(msg.sender, pureAmount);
    }

    /**
     * @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
     * tokens.
     * @param beneficiary Address receiving the tokens
     * @param tokenAmount Number of tokens to be purchased
     */
    function _processPurchase(address beneficiary, uint256 tokenAmount)
        private
    {
        TransferHelper.safeTransfer(_vPUREAddress, beneficiary, tokenAmount);

        // reset the beneficiary balance
        _vCrowdsaleBalance[beneficiary].vPUREAmount = 0;
        _vCrowdsaleBalance[beneficiary].ethAmount = 0;

        emit onClaimPurchasedTokens(beneficiary, tokenAmount);
    }

    /**
     * @dev add liquidity to LP
     */
    function _adLp(uint256 ethAmount, address tokenAddress)
        private
        returns (
            uint256 amountToken,
            uint256 amountETH,
            uint256 liquidity
        )
    {
        uint256 reserveA;
        uint256 reserveB;

        (reserveA, reserveB) = UniswapV2Library.getReserves(
            _uniswapFactory,
            _weth,
            tokenAddress
        );

        uint256 tokenAmount = UniswapV2Library.quote(
            ethAmount,
            reserveA,
            reserveB
        );

        TransferHelper.safeApprove(
            tokenAddress,
            _uniswapRouterAddress,
            tokenAmount
        );

        (amountToken, amountETH, liquidity) = _uniswapRouter02.addLiquidityETH{
            value: ethAmount
        }(
            tokenAddress,
            tokenAmount,
            tokenAmount,
            1,
            address(this),
            block.timestamp + 1 days
        );

        emit onLpvAdded(tokenAmount, amountETH, liquidity, tokenAddress);
    }

    /**
     * @return true if the transaction can buy tokens
     */
    function _isValidPurchase(uint256 amount) private view returns (bool) {
        bool withinPeriod = block.timestamp >= startTime &&
            block.timestamp <= endTime;
        bool nonZeroPurchase = amount != 0;
        return withinPeriod && nonZeroPurchase;
    }

    /**
     * @dev update the internal list of purchases
     */
    function _updateBalance(uint256 ethAmount, uint256 vPUREAmount) private {
        _vCrowdsaleBalance[msg.sender].ethAmount = _vCrowdsaleBalance[msg
            .sender]
            .ethAmount
            .add(ethAmount);
        // safetly check for the total amount of ETH bought by a single user
        // it should not exceed 50 ETH
        require(
            _vCrowdsaleBalance[msg.sender].ethAmount <= _ETHMAX * 10**18,
            "The total amount of ETH exceeded maximul value: 50 ETH"
        );

        _vCrowdsaleBalance[msg.sender].vPUREAmount = _vCrowdsaleBalance[msg
            .sender]
            .vPUREAmount
            .add(vPUREAmount);
    }
}
function getvPUREBalance(address from)
    public
    view
    _onlyOwner
    returns (uint256)
{
    return _vCrowdsaleBalance[from].vPUREAmount;
}

/**
 * @return the vPURE balance available for the given address.
 * _onlyOwner available
 */