contract ILO is Ownable, ReEntrancyGuard {
    // prevents over and under flow
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using SafeERC20 for ERC20;

    // Info of each user.
    struct UserInfo {
        uint256 amount; // total invested csx
        uint256 lpAllocated; // total lp tokens allocated
        uint256 projectTokenAllocated; // total project token allocated
        address addr; // address of investor
        bool isOwner; // investor or owner
        bool isLpSwapped; // whether user has swapped lp or not
    }

    // Info of pool.
    struct PoolInfo {
        IERC20 CSX; // csx token address
        IERC20 nativeToken; // Project token
        uint256 fundraisingDurationDays;
        uint256 maxPoolContribution;
        uint256 minInvestorContribution;
        uint256 maxInvestorContribution;
        uint256 forDistribution; // allocation for investor distribution
        bool checkOwnerContribution; // chech if owner has contributed native tokens
        address projectOwnerAddress; // project owner address
        uint256 projectOwnerContribution; // project owner total invested native token amount
    }

    // The CSX TOKEN!
    IERC20 public CSX;

    // Project token
    IERC20 public nativeToken;

    // Info of each pool.
    PoolInfo public poolInfo;

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

    // Deposited amount CSX in SLP
    uint256 public depositedCSX;

    // total CSX contributed by investors
    uint256 public totalInvestorContributed = 0;

    // start time
    uint256 public fundraisingStartTimestamp;

    // days
    uint256 public fundraisingDurationDays;

    // hard cap
    uint256 public maxPoolContribution;

    // project tokens for investment
    uint256 public tokensForInvestment;

    // min amount for each investor to contribute
    uint256 public minInvestorContribution;

    // max amount for each investor to contribute
    uint256 public maxInvestorContribution;

    // total number of investors
    uint256 public investorsCount;

    // total number of tranfers
    uint256 public transfersCount;

    // map of holders
    mapping(uint256 => address) private holders;

    // map of inverstor address and amount
    mapping(address => uint256) public investorContributed;

    // total number of LP created
    uint256 public totalLPCreated = 0;

    // unlock timestamp of lp
    uint256 public lpUnlockStartTimestamp = 0;

    // total lp avaliable for forDistribution after 50% owner allocation
    uint256 public forDistribution = 0;

    // check refund status of ILO
    bool private refund = false;

    // address of contract owner
    address private ownerAddress;

    event Deposit(address user, uint256 amount);
    event Withdraw(address user, uint256 amount);
    event EmergencyWithdraw(address user, uint256 amount);
    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 _value
    );

    constructor(
        IERC20 _CSX,
        IERC20 _nativeToken,
        uint256 _fundraisingDurationDays,
        uint256 _startTimestamp,
        uint256 _maxPoolContribution,
        uint256 _minInContribution,
        uint256 _maxInContribution,
        uint256 _tokensForInvestment,
        address _ownerAddress
    ) public {
        CSX = _CSX;
        fundraisingDurationDays = _fundraisingDurationDays * 1 days;
        maxPoolContribution = _maxPoolContribution;
        minInvestorContribution = _minInContribution;
        maxInvestorContribution = _maxInContribution;
        nativeToken = IERC20(_nativeToken);
        fundraisingStartTimestamp = _startTimestamp;
        ownerAddress = _ownerAddress;
        forDistribution = _maxPoolContribution;
        tokensForInvestment = _tokensForInvestment;

        // tranfer the ownership if sender does not match with _ownerAddress
        if (_ownerAddress != msg.sender) {
            transferOwnership(_ownerAddress);
        }

        totalLPCreated = totalLPCreated.add(
            maxPoolContribution.mul(2)
        );

        // allocate 50% LPs to owner
        userInfo[msg.sender] = UserInfo(
            maxPoolContribution,
            maxPoolContribution,
            tokensForInvestment,
            msg.sender,
            true,
            false
        );

        // staking pool
        poolInfo = PoolInfo({
            CSX: CSX,
            nativeToken: nativeToken,
            fundraisingDurationDays: _fundraisingDurationDays *
                1 days,
            maxPoolContribution: _maxPoolContribution,
            minInvestorContribution: _minInContribution,
            maxInvestorContribution: _maxInContribution,
            forDistribution: totalLPCreated.div(2),
            checkOwnerContribution: false,
            projectOwnerAddress: msg.sender,
            projectOwnerContribution: _tokensForInvestment // tokens dedicated for investors
        });
    }

    //////////////////
    //
    // EXTERNAL functions
    //
    //////////////////

    // Invest CSX tokens to ILO
    // function will not be called internally - external
    function contributeCSXToken(uint256 _amount)
        external
        noReentrant
    {
        PoolInfo storage pool = poolInfo;

        require(checkOwnerContribution(), 'ILO not started');
        require(isRefunded() == false, 'Refund already initiated');
        require(_amount > 0, 'invalid amount');
        require(
            isFundraisingFinished() == false,
            'fundraising already finished'
        );
        require(
            pool.projectOwnerAddress != address(msg.sender),
            "Project owner can't invest in this pool."
        );

        uint256 contributed = investorContributed[msg.sender];
        require(
            contributed.add(_amount) >= minInvestorContribution &&
                contributed.add(_amount) <= maxInvestorContribution,
            'invalid amount'
        );

        if (contributed == 0) {
            holders[investorsCount] = msg.sender;
            investorsCount = investorsCount.add(1);
        }

        transfersCount = transfersCount.add(1);

        uint256 _totalInvestorContributed = totalInvestorContributed
            .add(_amount);

        require(
            _totalInvestorContributed <= pool.forDistribution,
            'fundraising already finished'
        );

        if (_totalInvestorContributed <= maxPoolContribution) {
            investorContributed[msg.sender] = contributed.add(
                _amount
            );
            totalInvestorContributed = totalInvestorContributed.add(
                _amount
            );
        } else {
            uint256 cAmount = maxPoolContribution.sub(
                totalInvestorContributed
            );
            investorContributed[msg.sender] = contributed.add(
                cAmount
            );
            totalInvestorContributed = maxPoolContribution;
        }

        CSX.transferFrom(msg.sender, address(this), _amount);
        depositedCSX = depositedCSX.add(_amount);

        emit Deposit(msg.sender, _amount);

        // update amount
        UserInfo storage user = userInfo[msg.sender];
        user.amount = user.amount.add(_amount);
        user.lpAllocated = user.lpAllocated.add(_amount);
        user.projectTokenAllocated = user.projectTokenAllocated.add(
            pool.projectOwnerContribution.mul(_amount).div(
                pool.maxPoolContribution
            )
        );
        user.isLpSwapped = false;
        user.isOwner = false;
        emit Withdraw(msg.sender, _amount);
    }

    // Deposit LP tokens to ILO for Project token allocation.
    function swapLPforProjectTokens() external noReentrant {
        PoolInfo storage pool = poolInfo;
        UserInfo storage user = userInfo[msg.sender];
        require(user.isLpSwapped == false, 'Swap already done !');
        require(checkOwnerContribution(), 'ILO not started');
        require(
            isFundraisingSucceed() == true,
            'fundraising not succeeded'
        );

        user.isLpSwapped = true;

        if (user.isOwner == true) {
            uint256 ownerAmount = user.lpAllocated.mul(95).div(100);
            CSX.transfer(msg.sender, ownerAmount);
            emit Withdraw(msg.sender, ownerAmount);

            CSX.transfer(
                ownerAddress,
                user.lpAllocated.sub(ownerAmount)
            );
            emit Withdraw(
                ownerAddress,
                user.lpAllocated.sub(ownerAmount)
            );
        } else {
            pool.nativeToken.safeTransfer(
                msg.sender,
                user
                    .lpAllocated
                    .mul(pool.projectOwnerContribution)
                    .div(pool.maxPoolContribution)
            );
            emit Withdraw(msg.sender, user.lpAllocated);
        }
    }

    // Add Project tokens to ILO, make sure to check the ratio of lp:pt
    function contributeProjectToken(uint256 _amount)
        external
        noReentrant
    {
        PoolInfo storage pool = poolInfo;
        UserInfo storage user = userInfo[msg.sender];

        require(
            _amount >= pool.projectOwnerContribution,
            'Incorrect project token value'
        );
        require(_amount > 0, 'invalid amount');
        require(user.isOwner == true, 'User not owner');
        require(
            checkOwnerContribution() == false,
            'Project tokens already contributed'
        );

        pool.checkOwnerContribution = true;

        pool.nativeToken.transferFrom(
            msg.sender,
            address(this),
            _amount
        );
        emit Deposit(msg.sender, _amount);
    }

    // Allows sending multiple ERC20 standard token to a group of accounts.
    function refundNativeTokens()
        external
        onlyOwner
        returns (bool success)
    {
        require(checkOwnerContribution(), 'ILO not started');
        require(isRefunded() == false, 'Refund already initiated');
        require(
            isFundraisingFinished() == true,
            'fundraising is not finished!'
        );
        require(
            isFundraisingSucceed() == false,
            'fundraising is succeed!'
        );
        PoolInfo storage pool = poolInfo;
        refund = true;

        nativeToken.safeTransfer(
            pool.projectOwnerAddress,
            pool.projectOwnerContribution
        );

        for (uint256 i = 0; i < investorsCount; i++) {
            require(
                CSX.transfer(
                    holders[i],
                    investorContributed[holders[i]]
                )
            );
        }
        return true;
    }

    // View function to see CSX staked
    function getUserStakeDetails(address _user)
        external
        view
        returns (uint256)
    {
        UserInfo storage user = userInfo[_user];
        return user.amount;
    }

    //////////////////
    //
    // PUBLIC functions
    //
    //////////////////

    // check fundraising is finished
    function isFundraisingFinished() public view returns (bool) {
        PoolInfo storage pool = poolInfo;

        if (
            block.timestamp >=
            fundraisingStartTimestamp.add(fundraisingDurationDays)
        ) {
            return true;
        }
        if (pool.forDistribution == totalInvestorContributed) {
            return true;
        }
        if (totalLPCreated <= 0) {
            return true;
        }
        return false;
    }

    // check for project owner project contribution
    function checkOwnerContribution() public view returns (bool) {
        PoolInfo storage pool = poolInfo;

        if (
            nativeToken.balanceOf(address(this)) >= 0 &&
            pool.checkOwnerContribution == true
        ) {
            return true;
        }
        return false;
    }

    // check for refund
    function isRefunded() public view returns (bool) {
        if (refund == true) {
            return true;
        }
        return false;
    }

    // check fundraising is succeed
    function isFundraisingSucceed() public view returns (bool) {
        return totalInvestorContributed >= maxPoolContribution;
    }

    // Safe CSX transfer function, just in case if rounding error causes pool to not have enough CSXs.
    function safeCSXTransfer(address _to, uint256 _amount) internal {
        uint256 CSXBal = CSX.balanceOf(address(this));
        if (_amount > CSXBal) {
            CSX.transfer(_to, CSXBal);
        } else {
            CSX.transfer(_to, _amount);
        }
    }
}
// ReEntrancyGuard to prevent Reentrancy
function isFundraisingFinished() public view returns (bool) {
    PoolInfo storage pool = poolInfo;

    if (
        block.timestamp >=
        fundraisingStartTimestamp.add(fundraisingDurationDays)
    ) {
        return true;
    }
    if (pool.forDistribution == totalInvestorContributed) {
        return true;
    }
    if (totalLPCreated <= 0) {
        return true;
    }
    return false;
}

// check fundraising is finished