contract ERC20 is IERC20, Ownable {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;
    uint16[19] allLockupPeriods = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360, 400, 450, 480, 540, 600, 720];

    uint256 private _totalSupply;
    uint256 private _vestingMinAmount = 1000000;

    PrivateSaleKeeper.PrivateData salesKeeper;
    using PrivateSaleKeeper for PrivateSaleKeeper.PrivateData;

    DateTime d = new DateTime();

    uint private startDay = d.toTimestamp(2021, 9, 30);

    function setLockupPeriod(uint _phase) private pure returns (uint) {
        if(_phase == 1) {
             return 240 days;
        } else if (_phase == 2) {
             return 180 days;
        } else if (_phase == 3) {
             return 180 days;
        } else {
             return (_phase * 1 days);
        }     
    }

    function getAllLockupPeriods() public view returns (uint16[19] memory) {
        return allLockupPeriods;
    }
    
    function getAmounts(address account) public view returns (uint256[22] memory) {
        require(account == _msgSender() || isOwner(), "You dont have right to use this function");
        return salesKeeper.get(account);
    }

    function getPrivateSaleAmount(address account, uint phase) public view returns (uint256) {
        require(account == _msgSender(), "You dont have right to use this function");
        return getAmounts(account)[phase-1];
    }

    function savePrivateSaleRecord(address _to, uint256 _amount, uint256 _phase) internal {

        require(contains(_phase), "Invalid phase number (1 - 3 or see getALockupPeriods for the allowed lockup days)");

        uint256 _phasee = _phase;
        if(_phase == 0 || _phase > 3) {
            for (uint i = 0; i < allLockupPeriods.length; i++) {
                if(_phase == allLockupPeriods[i]){
                    _phasee = i + 4;
                }
            }
        }

        salesKeeper.insert(_to, _amount, _phasee-1);
    }

    function _vestingTransfer(address recipient, uint256 amount, uint256[] memory lockupDays, uint256[] memory percents) internal {
        require(lockupDays.length + 1 == percents.length, "Invalid number of parameters (number of parameters of percents must be one more than the number of parameters of lockupDays)");
        require(arraySum(percents) == 100, "The sum of vesting percentages does not add up to 100");
        for (uint i=0; i<lockupDays.length; i++) {
            if(!contains(lockupDays[i]))
            {  
                revert("Invalid lockup day number (see getALockupPeriods for the allowed lockup days)");
            }
        }

        // For StartDate or (lockup period of 0)
        uint256 _phase = 0;
        uint256 _vestedAmount = amount.mul(percents[0]).div(100);
        savePrivateSaleRecord(recipient, _vestedAmount, _phase);

        // for others
        for (uint i=1; i<percents.length; i++) {
            _phase = lockupDays[i-1];
            _vestedAmount = amount.mul(percents[i]).div(100);
            savePrivateSaleRecord(recipient, _vestedAmount, _phase);
        }

    }

    function getStartDay() public view returns(uint256, uint8, uint8) {
        return (d.getYear(startDay), d.getMonth(startDay), d.getDay(startDay));
    }
  
    function changeStartDate(uint16 year, uint8 month, uint8 day) public onlyOwner {
        startDay = d.toTimestamp(year, month, day);
    }


    function getReleaseTimeStamp(uint phase) internal view returns(uint) {
        if(phase <= 3) {
            return startDay + setLockupPeriod(phase);
        } else {
            return startDay + setLockupPeriod(allLockupPeriods[phase-4]);
        }
    }

    function getReleaseTimeStampForDays(uint numDays) internal view returns (uint) {
        return startDay + (numDays * 1 days);
    }

    function getReleaseDate(uint phase) public view returns(uint256, uint8, uint8) {
        require(phase > 0 && phase <= 3, "Invalid phase number");
        return getDate(getReleaseTimeStamp(phase));
    }

    function getDate(uint day) internal view returns (uint256, uint8, uint8) {
        return (d.getYear(day), d.getMonth(day), d.getDay(day));
    }

    function availableForTransfer(address account) public view returns (uint256) {
        require(account == _msgSender() || _msgSender() == address(this), "You don't have right to use this function");

        uint256 presaleAmountNotAvailable = totalLocked(account);

        uint256 tokensUnlocked = balanceOf(account) - presaleAmountNotAvailable;

        return tokensUnlocked;
    }

    function totalLocked(address account) public view returns (uint256) {
        uint256 presaleAmountNotAvailable = 0;
        uint256[22] memory records = getAmounts(account);

        for (uint i=1; i<records.length+1; i++) {
            if (now < getReleaseTimeStamp(i)) { // Private Transfer in LockupPeriod
                presaleAmountNotAvailable += getPrivateSaleAmount(account, i);
            } 
        }

        return presaleAmountNotAvailable;
    }

    function contains(uint _phase) internal view returns (bool){

        if(_phase == 1 || _phase == 2 || _phase == 3) {
            return true;
        }

        for (uint i=0; i<allLockupPeriods.length; i++) {
            if(allLockupPeriods[i] == _phase)
            {  
                return true;
            }
        }

        return false;
    }

    function arraySum(uint256[] memory _array) private pure returns (uint256 _sum) 
    {
        _sum = 0;
        for (uint i = 0; i < _array.length; i++) {
            _sum += _array[i];
        }
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */

    function transfer(address recipient, uint256 amount) public returns (bool) {

        if(availableForTransfer(_msgSender()) >= amount) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        } else {
            revert("Attempt to transfer more tokens than what is allowed at this time");
        }
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {

         _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
    
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "ERC20: Amount not greater than zero");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See {_burn} and {_approve}.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
}
/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20Mintable}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
function allowance(address owner, address spender) public view returns (uint256) {
    return _allowances[owner][spender];
}

/**
 * @dev See {IERC20-allowance}.
 */