contract ShadowsToken is
    Initializable,
    OwnableUpgradeable,
    AuthorizeableUpgradeable,
    ERC20PausableUpgradeable
{
    event AddFrozenWallet(address indexed _address);
    event Pause();
    event Unpause();

    mapping(address => FrozenWallet) public frozenWallets;
    VestingType[] public vestingTypes;
    // Maximum Total Supply 100 M
    uint256 constant maxTotalSupply = 1e8 ether;
    // Release Date 15.12.2020
    uint256 constant releaseTime = 1614697200;

    function initialize() external initializer {
        __Ownable_init();
        __Authorizeable_init();
        __ERC20_init("Shadows Network", "DOWS");
        __ERC20Pausable_init();

        _mint(address(0x24F6e9860b05B21bcfB9d4f56D09aa6Aefb589e3), 2000000 ether);
        _mint(address(0xF2faAC2a134381124ee0Be7Ec76258e8a4c11C7b), 2000000 ether);

        vestingTypes.push(
            VestingType(
                1e19,
                2e19,
                0,
                true,
                true
            )
        ); // 20% unlock immediately on TGE, rest unlocked over 8 months
        vestingTypes.push(
            VestingType(
                9375e15,
                25e18,
                0,
                true,
                true
            )
        ); // 25% unlock immediately on TGE, rest unlocked over 8 months
        vestingTypes.push(
            VestingType(416e16, 0, 270 days, false, true)
        ); // First unlock at month 10, rest unlock over 24 months
    }

    function addAllocations(
        address[] memory addresses,
        uint256[] memory totalAmounts,
        uint256 vestingTypeIndex
    ) external onlyOwner returns (bool) {
        require(
            addresses.length == totalAmounts.length,
            "Address and totalAmounts length must be same"
        );
        require(
            vestingTypes[vestingTypeIndex].vesting,
            "Vesting type isn't found"
        );

        VestingType memory vestingType = vestingTypes[vestingTypeIndex];
        uint256 addressesLength = addresses.length;

        for (uint256 i = 0; i < addressesLength; i++) {
            address address_ = addresses[i];
            uint256 totalAmount = totalAmounts[i];
            uint256 monthlyAmount =
                totalAmounts[i].mul(vestingType.monthlyRate).div(
                    100000000000000000000
                );
            uint256 initialAmount =
                totalAmounts[i].mul(vestingType.initialRate).div(
                    100000000000000000000
                );
            uint256 afterDay = vestingType.afterDays;
            bool immediately = vestingType.immediately;

            addFrozenWallet(
                address_,
                totalAmount,
                monthlyAmount,
                initialAmount,
                afterDay,
                immediately
            );
        }

        return true;
    }

    function addFrozenWallet(
        address wallet,
        uint256 totalAmount,
        uint256 monthlyAmount,
        uint256 initialAmount,
        uint256 afterDays,
        bool immediately
    ) internal {
        if (!frozenWallets[wallet].scheduled) {
            _mint(wallet, totalAmount);

            // Add wallet to frozen wallets
            frozenWallets[wallet] = FrozenWallet(
                wallet,
                totalAmount,
                monthlyAmount,
                initialAmount,
                releaseTime.add(afterDays),
                immediately,
                true
            );

            emit AddFrozenWallet(wallet);
        }
    }

    function mint(address account, uint256 amount) external onlyAuthorized {
        _mint(account, amount);
    }

    function _mint(address account, uint256 amount) internal override {
        uint256 totalSupply = super.totalSupply();
        require(
            maxTotalSupply >= totalSupply.add(amount),
            "Max total supply over"
        );

        super._mint(account, amount);
    }

    // @override
    function _beforeTokenTransfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual override {
        require(canTransfer(sender, amount), "Wait for the vesting day!");
        super._beforeTokenTransfer(sender, recipient, amount);
    }

    function getTransferableAmount(address sender)
        public
        view
        returns (uint256)
    {
        FrozenWallet memory frozenWallet = frozenWallets[sender];
        uint256 months =
            getMonths(frozenWallet.immediately, frozenWallet.startDay);
        uint256 monthlyTransferableAmount =
            frozenWallet.monthlyAmount.mul(months);
        uint256 transferableAmount =
            monthlyTransferableAmount.add(frozenWallet.initialAmount);

        return
            transferableAmount > frozenWallet.totalAmount
                ? frozenWallet.totalAmount
                : transferableAmount;
    }

    function getRequiredAmount(address sender) public view returns (uint256) {
        uint256 transferableAmount = getTransferableAmount(sender);
        return frozenWallets[sender].totalAmount.sub(transferableAmount);
    }

    // Transfer control
    // Zero control has from erc20
    function canTransfer(address sender, uint256 amount)
        public
        view
        returns (bool)
    {
        // Control is scheduled wallet
        if (!frozenWallets[sender].scheduled) {
            return true;
        }

        uint256 balance = balanceOf(sender);
        uint256 requiredAmount = getRequiredAmount(sender);

        if (
            !isStarted(frozenWallets[sender].startDay) ||
            balance.sub(amount) < requiredAmount
        ) {
            return false;
        }

        return true;
    }

    // Frozen wallet can get initial amount with immediately parameter
    function getMonths(bool immediately, uint256 startDay)
        public
        view
        returns (uint256)
    {
        uint256 delay = immediately ? 0 : 1;

        if (block.timestamp < startDay) {
            return 0;
        }

        uint256 diff = block.timestamp.sub(startDay);
        uint256 months = diff.div(30 days).add(delay);

        return months;
    }

    function isStarted(uint256 startDay) public view returns (bool) {
        if (block.timestamp < releaseTime || block.timestamp < startDay) {
            return false;
        }

        return true;
    }

    function pause(bool status) external onlyOwner {
        if (status) {
            _pause();
            emit Pause();
        } else {
            _unpause();
            emit Unpause();
        }
    }
}
function getMonths(bool immediately, uint256 startDay)
    public
    view
    returns (uint256)
{
    uint256 delay = immediately ? 0 : 1;

    if (block.timestamp < startDay) {
        return 0;
    }

    uint256 diff = block.timestamp.sub(startDay);
    uint256 months = diff.div(30 days).add(delay);

    return months;
}

// Frozen wallet can get initial amount with immediately parameter