// File: /contracts/EsgSHIPV2.sol



pragma solidity >=0.5.16;
pragma experimental ABIEncoderV2;

import "./EIP20Interface.sol";
import "./SafeMath.sol";

contract EsgSHIPV2{
    using SafeMath for uint256;
    /// @notice ESG token
    EIP20Interface public esg;

    /// @notice Emitted when referral set invitee
    event SetInvitee(address inviteeAddress);

    /// @notice Emitted when owner set referral
    event SetInviteeByOwner(address referralAddress);

    /// @notice Emitted when ESG is invest  
    event EsgInvest(address account, uint amount, uint month, bool useInterest);

    /// @notice Emitted when ESG is invest by owner  
    event EsgInvestByOwner(address account, uint amount, uint month, uint starttime, uint endtime);

    /// @notice Emitted when ESG is withdrawn 
    event EsgWithdraw(address account, uint amount);

    /// @notice Emitted when ESG is claimed 
    event EsgClaimed(address account, uint amount);

    /// @notice Emitted when change referral info
    event EsgChangeReferrerInfo(address referralAddress, address inviteeAddress, address newInviteeAddress);

    /// @notice Emitted when change Lock info
    event EsgChangeLockInfo(address _user, uint256 _amount, uint256 _start, uint256 _end, uint256 _month, uint256 i);

    struct Lock {
        uint256 amount;
        uint256 start;
        uint256 end;
        uint256 month;
    }

    mapping(uint256 => uint256) public lockRates;

    mapping(address => Lock[]) public locks;

    mapping(address => uint256) public interests;

    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function.");
        _;
    }

    struct Referrer {
        address[] referrals;
        uint256 totalInvestment;
        bool dynamicReward;
    }

    mapping(address => Referrer) public referrers;

    struct User {
        address referrer_addr;
    }

    mapping (address => User) public referrerlist;

    uint256 public referralThreshold = 3000 * 1e18;

    uint256 public dynamicRewardThreshold = 100000 * 1e18;

    uint256 public onetimeRewardPercentage = 7;

    uint256 public dynamicRewardPercentage = 4;

    uint256 public dynamicRewardPercentageEvery = 10;

    uint256 public burnPercentage = 5;

    uint256 public total_deposited;

    uint256 public total_user;

    bool public allow_get_esg = true;

    constructor(address esgAddress) public {
        owner = msg.sender;
        lockRates[9] = 50;
        lockRates[12] = 60;
        lockRates[18] = 100;
        esg = EIP20Interface(esgAddress);
    }

    function setLockRate(uint256 _months, uint256 _rate) public onlyOwner {
        lockRates[_months] = _rate;
    }

    function setReferralThreshold(uint256 _amount) public onlyOwner {
        referralThreshold = _amount;
    }

    function setDynamicRewardThreshold(uint256 _amount) public onlyOwner {
        dynamicRewardThreshold = _amount;
    }

    function setOnetimeRewardPercentage(uint256 _percentage) public onlyOwner {
        onetimeRewardPercentage = _percentage;
    }

    function setDynamicRewardPercentage(uint256 _percentage) public onlyOwner {
        dynamicRewardPercentage = _percentage;
    }

    function setDynamicRewardPercentageEvery(uint256 _percentage) public onlyOwner {
        dynamicRewardPercentageEvery = _percentage;
    }

    function setBurnPercentage(uint256 _percentage) public onlyOwner {
        burnPercentage = _percentage;
    }

    function setInvitee(address inviteeAddress) public returns (bool) {
        require(inviteeAddress != address(0), "inviteeAddress cannot be 0x0.");

        User storage user = referrerlist[inviteeAddress];
        require(user.referrer_addr == address(0), "This account had been invited!");

        Lock[] storage referrerLocks = locks[msg.sender];
        require(referrerLocks.length > 0, "Referrer has no locked amount.");

        uint256 referrerAmount = 0;

        for (uint256 i = 0; i < referrerLocks.length; i++) {
            Lock storage lock = referrerLocks[i];
            referrerAmount += lock.amount;
        }

        require(referrerAmount >= referralThreshold,"Referrer has no referral qualification.");

        Lock[] storage inviteeLocks = locks[inviteeAddress];
        require(inviteeLocks.length == 0, "This account had staked!");

        Referrer storage referrer = referrers[msg.sender];
        referrer.referrals.push(inviteeAddress);

        User storage _user = referrerlist[inviteeAddress];
        _user.referrer_addr = msg.sender;

        emit SetInvitee(inviteeAddress);
        return true;   
    }

    function setInviteeByOwner(address referrerAddress, address[] memory inviteeAddress) public onlyOwner returns (bool) {
        require(referrerAddress != address(0), "referrerAddress cannot be 0x0.");
        require(inviteeAddress.length > 0, "inviteeAddress cannot be 0.");

        Referrer storage referrer = referrers[referrerAddress];
        referrer.referrals = inviteeAddress;

        for(uint256 i = 0; i < inviteeAddress.length; i++){
            address _inviteeAddress = inviteeAddress[i];
            User storage _user = referrerlist[_inviteeAddress];
            if(_user.referrer_addr == address(0)){
                _user.referrer_addr = referrerAddress;
            }
        }

        emit SetInviteeByOwner(referrerAddress);
        return true;   
    }

    function getInviteelist(address referrerAddress) public view returns (address[] memory) {
        require(referrerAddress != address(0), "referrerAddress cannot be 0x0.");
        Referrer storage referrer = referrers[referrerAddress];
        return referrer.referrals;
    }

    function getReferrer(address inviteeAddress) public view returns (address) {
        require(inviteeAddress != address(0), "inviteeAddress cannot be 0x0.");
        User storage user = referrerlist[inviteeAddress];
        return user.referrer_addr;
    }

    function invest(uint256 _months, uint256 _amount, bool _useInterest) public returns (bool) {
        require(allow_get_esg == true, "No invest allowed!");
        require(lockRates[_months] > 0, "Invalid lock period.");
        require(_amount > 0, "Invalid amount.");

        if (_useInterest) {
            uint256 interest = calculateInterest(msg.sender);
            require(interest >= _amount, "Insufficient interest.");
            interests[msg.sender] -= _amount;
        } else {
            esg.transferFrom(msg.sender, address(this), _amount);
        }

        locks[msg.sender].push(
            Lock(
                _amount,
                block.timestamp,
                block.timestamp + _months * 30 days,
                _months
            )
        );

        total_deposited = total_deposited + _amount;
        total_user = total_user + 1;
            
        User storage user = referrerlist[msg.sender];

        if(user.referrer_addr != address(0)){
            referrers[user.referrer_addr].totalInvestment += _amount;

            if (referrers[user.referrer_addr].totalInvestment >= dynamicRewardThreshold) {
                referrers[user.referrer_addr].dynamicReward = true;
            }
            uint256 onetimeTotalReward = _amount.mul(lockRates[_months]).div(100).mul(onetimeRewardPercentage).div(100);
            uint256 onetimeReward = onetimeTotalReward.div(12).mul(_months);
            esg.transfer(user.referrer_addr, onetimeReward);
        }

        emit EsgInvest(msg.sender, _amount, _months, _useInterest);
        return true;
    }

    function investByOwner(uint256 start, uint256 end, uint256 _amount, uint256 month, address inviteeAddress) public onlyOwner returns (bool) {
        require(start > 0, "start cannot be 0.");
        require(end > 0, "start cannot be 0.");
        require(_amount > 0, "Invalid amount.");
        require(month > 0, "month cannot be 0.");
        require(inviteeAddress != address(0), "inviteeAddress cannot be 0x0.");

        locks[inviteeAddress].push(
            Lock(
                _amount,
                start,
                end,
                month
            )
        );

        total_deposited = total_deposited + _amount;
        total_user = total_user + 1;
            
        User storage user = referrerlist[inviteeAddress];

        if(user.referrer_addr != address(0)){
            referrers[user.referrer_addr].totalInvestment += _amount;

            if (referrers[user.referrer_addr].totalInvestment >= dynamicRewardThreshold) {
                referrers[user.referrer_addr].dynamicReward = true;
            }
        }

        emit EsgInvestByOwner(inviteeAddress, _amount, month, start, end);
        return true;
    }

    function withdraw() public returns (bool) {
        require(allow_get_esg == true, "No withdrawal allowed!");
        Lock[] storage userLocks = locks[msg.sender];
        require(userLocks.length > 0, "No locked amount.");

        uint256 totalAmount = 0;
        uint256 index = 0;
        uint256 totalInterest = interests[msg.sender];

        while (index < userLocks.length) {
            Lock storage lock = userLocks[index];
            if (block.timestamp >= lock.end) {
                totalAmount += lock.amount;
                userLocks[index] = userLocks[userLocks.length - 1];
                userLocks.pop();
                uint256 interest = (block.timestamp.sub(lock.start)).mul(lock.amount).mul(lockRates[lock.month]).div(100).div(360).div(86400);
                if (interest > 0) {
                    totalInterest += interest;
                    lock.start = block.timestamp;
                    totalAmount += totalInterest;
                }
            } else {
                index++;
            }
        }

        require(totalAmount > 0, "No amount to withdraw.");

        esg.transfer(msg.sender, totalAmount);

        interests[msg.sender] = 0;
        total_deposited -= totalAmount;

        User storage user = referrerlist[msg.sender];

        if (user.referrer_addr != address(0)) {
            referrers[user.referrer_addr].totalInvestment -= totalAmount;

            if (referrers[user.referrer_addr].totalInvestment < dynamicRewardThreshold) {
                referrers[user.referrer_addr].dynamicReward = false;
            }
        }

        uint256 userAmount = 0;

        for (uint256 i = 0; i < userLocks.length; i++) {
            Lock storage lock = userLocks[i];
            userAmount += lock.amount;
        }

        if (userAmount < referralThreshold) {
            Referrer storage referrer = referrers[msg.sender];
            if(referrer.referrals.length > 0){
                for(uint256 i = 0; i < referrer.referrals.length; i++){
                    address invitee_add = referrer.referrals[i];
                    delete referrerlist[invitee_add];
                }
            }
            delete referrers[msg.sender].referrals;
        }

        emit EsgWithdraw(msg.sender, totalAmount); 
        return true;
    }

    function claim() public returns (bool) {
        require(allow_get_esg == true, "No claim allowed!");
        Lock[] storage userLocks = locks[msg.sender];
        require(userLocks.length > 0, "No locked amount.");

        uint256 totalInterest = interests[msg.sender];

        for (uint256 i = 0; i < userLocks.length; i++) {
            Lock storage lock = userLocks[i];
            uint256 interest = (block.timestamp.sub(lock.start)).mul(lock.amount).mul(lockRates[lock.month]).div(100).div(360).div(86400);
            if (interest > 0) {
                totalInterest += interest;
                lock.start = block.timestamp;
            }
        }

        require(totalInterest > 0, "No interest to claim.");

        interests[msg.sender] = 0;

        uint256 burnAmount = totalInterest.mul(burnPercentage).div(1000);
        esg.transfer(address(esg), burnAmount);
        totalInterest -= burnAmount;

        esg.transfer(msg.sender, totalInterest);

        Referrer storage referrer = referrers[msg.sender];
        User storage user = referrerlist[msg.sender];

        if (user.referrer_addr != address(0)) {
            uint256 dynamicRewardEvery = totalInterest.mul(dynamicRewardPercentageEvery).div(100);
            esg.transfer(user.referrer_addr, dynamicRewardEvery);
            if (referrers[user.referrer_addr].dynamicReward) {
                uint256 dynamicReward = totalInterest.mul(dynamicRewardPercentage).div(100);
                esg.transfer(user.referrer_addr, dynamicReward);
            }
        }

        emit EsgClaimed (msg.sender, totalInterest); 
        return true;
    }

    function calculateInterest(address _user) public view returns (uint256) {
        Lock[] storage userLocks = locks[_user];
        if (userLocks.length == 0) {
            return 0;
        }
        uint256 totalInterest = interests[_user];

        for (uint256 i = 0; i < userLocks.length; i++) {
            Lock storage lock = userLocks[i];
            uint256 interest = (block.timestamp.sub(lock.start)).mul(lock.amount).mul(lockRates[lock.month]).div(100).div(360).div(86400);
            if (interest > 0) {
                totalInterest += interest;
            }
        }

        return totalInterest;
    }

    function getLockInfo(address _user) public view returns (
            uint256[] memory,
            uint256[] memory,
            uint256[] memory,
            uint256[] memory,
            uint256[] memory
        )
    {
        Lock[] storage userLocks = locks[_user];
                uint256 length = userLocks.length;

        uint256[] memory amounts = new uint256[](length);
        uint256[] memory starts = new uint256[](length);
        uint256[] memory ends = new uint256[](length);
        uint256[] memory rates = new uint256[](length);
        uint256[] memory interest = new uint256[](length);

        for (uint256 i = 0; i < length; i++) {
            Lock storage lock = userLocks[i];
            amounts[i] = lock.amount;
            starts[i] = lock.start;
            ends[i] = lock.end;
            rates[i] = lockRates[lock.month];
            interest[i] = (block.timestamp.sub(lock.start)).mul(lock.amount).mul(lockRates[lock.month]).div(100).div(360).div(86400);
        }

        return (amounts, starts, ends, rates, interest);
    }

    function changeReferrerInfo(address referralAddress, address inviteeAddress, address newInviteeAddress) public onlyOwner returns (bool) {
        require(referralAddress != address(0), "referralAddress cannot be 0x0.");
        require(inviteeAddress != address(0), "inviteeAddress cannot be 0x0.");
        require(newInviteeAddress != address(0), "newInviteeAddress cannot be 0x0.");

        Referrer storage referrer = referrers[referralAddress];
        if(referrer.referrals.length > 0){
            for(uint256 i = 0; i < referrer.referrals.length; i++){
                address invitee_add = referrer.referrals[i];
                if(inviteeAddress == invitee_add){
                    referrer.referrals[i] = newInviteeAddress;
                    break;
                }
            }
        }

        delete referrerlist[inviteeAddress];
        User storage _user = referrerlist[newInviteeAddress];
        _user.referrer_addr = referralAddress;

        emit EsgChangeReferrerInfo(referralAddress, inviteeAddress, newInviteeAddress);
        return true;
    }

    function changeLockInfo(address _user, uint256 _amount, uint256 _start, uint256 _end, uint256 _month, uint256 i) public onlyOwner returns (bool) {
        require(_user != address(0), "_user cannot be 0x0.");
        Lock storage userLocks = locks[_user][i];
        userLocks.amount = _amount;
        userLocks.start = _start;
        userLocks.end = _end;
        userLocks.month = _month;

        emit EsgChangeLockInfo(_user, _amount, _start, _end, _month, i);
        return true;
    }

    function close() public onlyOwner {
        allow_get_esg = false;
    }

    function open() public onlyOwner {
        allow_get_esg = true;
    }

    function transferOwnership(address newOwner) onlyOwner public {
        if (newOwner != address(0)) {
        owner = newOwner;
      }
    }
}

// File: /contracts/SafeMath.sol



pragma solidity ^0.5.16;

// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/Math.sol
// Subject to the MIT license.

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting with custom message on overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, errorMessage);

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on underflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot underflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction underflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on underflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot underflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, errorMessage);

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers.
     * Reverts on division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers.
     * Reverts with custom message on division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }

    /**
   * @dev gives square root of given x.
   */
  function sqrt(uint256 x) internal pure returns(uint256 y) {
        uint256 z = ((add(x, 1)) / 2);
        y = x;
        while (z < y) {
            y = z;
            z = ((add((x / z), z)) / 2);
        }
  }

  /**
   * @dev gives square. multiplies x by x
   */
  function sq(uint256 x) internal pure returns(uint256) {
       return (mul(x, x));
  }

  /**
   * @dev x to the power of y
   */
  function pwr(uint256 x, uint256 y) internal pure returns(uint256) {
    if (x == 0)
      return (0);
    else if (y == 0)
      return (1);
    else {
      uint256 z = x;
      for (uint256 i = 1; i < y; i++)
        z = mul(z, x);
      return (z);
    }
  }
}
// File: /contracts/EIP20Interface.sol



pragma solidity ^0.5.16;

/**
 * @title ERC 20 Token Standard Interface
 *  https://eips.ethereum.org/EIPS/eip-20
 */
interface EIP20Interface {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);

    /**
      * @notice Get the total number of tokens in circulation
      * @return The supply of tokens
      */
    function totalSupply() external view returns (uint256);

    /**
     * @notice Gets the balance of the specified address
     * @param owner The address from which the balance will be retrieved
     * @return The balance
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
      * @notice Transfer `amount` tokens from `msg.sender` to `dst`
      * @param dst The address of the destination account
      * @param amount The number of tokens to transfer
      * @return Whether or not the transfer succeeded
      */
    function transfer(address dst, uint256 amount) external returns (bool success);

    /**
      * @notice Transfer `amount` tokens from `src` to `dst`
      * @param src The address of the source account
      * @param dst The address of the destination account
      * @param amount The number of tokens to transfer
      * @return Whether or not the transfer succeeded
      */
    function transferFrom(address src, address dst, uint256 amount) external returns (bool success);

    /**
      * @notice Approve `spender` to transfer up to `amount` from `src`
      * @dev This will overwrite the approval amount for `spender`
      *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
      * @param spender The address of the account which may transfer tokens
      * @param amount The number of tokens that are approved (-1 means infinite)
      * @return Whether or not the approval succeeded
      */
    function approve(address spender, uint256 amount) external returns (bool success);

    /**
      * @notice Get the current allowance from `owner` for `spender`
      * @param owner The address of the account which owns the tokens to be spent
      * @param spender The address of the account which may transfer tokens
      * @return The number of tokens allowed to be spent (-1 means infinite)
      */
    function allowance(address owner, address spender) external view returns (uint256 remaining);

    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 amount);
}