// File: MultiSender.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;

import "./Ownable.sol";
import "./StandardToken.sol";
import "./SafeMath.sol";
import "./ERC20.sol";
import "./ERC20Basic.sol";

contract MultiSender is Ownable {
    using SafeMath for uint256;

    event LogTokenMultiSent(address token, uint256 total);
    event LogGetToken(address token, address receiver, uint256 balance);

    address public receiverAddress;
    uint256 public txFee = 0 ether;
    uint256 public VIPFee = 0 ether;

    /* VIP List */
    mapping(address => bool) public vipList;

    /*
     *  get balance
     */
    function getBalance(address _tokenAddress) public onlyOwner {
        address _receiverAddress = address(uint160(getReceiverAddress()));
        if (_tokenAddress == address(0)) {
            _receiverAddress.transfer(address(this).balance);
            return;
        }
        StandardToken token = StandardToken(_tokenAddress);
        uint256 balance = token.balanceOf(address(this));
        token.transfer(_receiverAddress, balance);
        emit LogGetToken(_tokenAddress, _receiverAddress, balance);
    }

    /*
     *  Register VIP
     */
    function registerVIP() public payable {
        require(msg.value >= VIPFee);
        address _receiverAddress = getReceiverAddress();
        require(address(uint160(_receiverAddress)).send(msg.value));
        vipList[msg.sender] = true;
    }

    /*
     *  VIP list
     */
    function addToVIPList(address[] memory _vipList) public onlyOwner {
        for (uint256 i = 0; i < _vipList.length; i++) {
            vipList[_vipList[i]] = true;
        }
    }

    /*
     * Remove address from VIP List by Owner
     */
    function removeFromVIPList(address[] memory _vipList) public onlyOwner {
        for (uint256 i = 0; i < _vipList.length; i++) {
            vipList[_vipList[i]] = false;
        }
    }

    function isVIP(address _address) internal view returns (bool) {
        return vipList[_address];
    }

    /*
     * set receiver address
     */
    function setReceiverAddress(address _addr) public onlyOwner {
        require(_addr != address(0));
        receiverAddress = _addr;
    }

    /*
     * get receiver address
     */
    function getReceiverAddress() public view returns (address) {
        if (receiverAddress == address(0)) {
            return owner;
        }
        return receiverAddress;
    }

    /*
     * set vip fee
     */
    function setVIPFee(uint256 _fee) public onlyOwner {
        VIPFee = _fee;
    }

    /*
     * set tx fee
     */
    function setTxFee(uint256 _fee) public onlyOwner {
        txFee = _fee;
    }

    function ethSendSameValue(address[] memory _to, uint256 _value) internal {
        uint256 sendAmount = _to.length.sub(1).mul(_value);
        uint256 remainingValue = msg.value;
        bool vip = isVIP(msg.sender);
        if (vip) {
            require(remainingValue >= sendAmount);
        } else {
            require(remainingValue >= sendAmount.add(txFee));
        }
        require(_to.length <= 255);

        for (uint8 i = 1; i < _to.length; i++) {
            remainingValue = remainingValue.sub(_value);
            require(address(uint160(_to[i])).send(_value));
        }

        emit LogTokenMultiSent(
            0x000000000000000000000000000000000000bEEF,
            msg.value
        );
    }

    function ethSendDifferentValue(
        address[] memory _to,
        uint256[] memory _value
    ) internal {
        uint256 sendAmount = _value[0];
        uint256 remainingValue = msg.value;
        bool vip = isVIP(msg.sender);
        if (vip) {
            require(remainingValue >= sendAmount);
        } else {
            require(remainingValue >= sendAmount.add(txFee));
        }
        require(_to.length == _value.length);
        require(_to.length <= 255);

        for (uint8 i = 1; i < _to.length; i++) {
            remainingValue = remainingValue.sub(_value[i]);
            require(address(uint160(_to[i])).send(_value[0]));
        }

        emit LogTokenMultiSent(
            0x000000000000000000000000000000000000bEEF,
            msg.value
        );
    }

    function coinSendSameValue(
        address _tokenAddress,
        address[] memory _to,
        uint256 _value
    ) internal {
        uint256 sendValue = msg.value;
        bool vip = isVIP(msg.sender);
        if (!vip) {
            require(sendValue >= txFee);
        }
        require(_to.length <= 255);

        address from = msg.sender;
        uint256 sendAmount = _to.length.sub(1).mul(_value);

        StandardToken token = StandardToken(_tokenAddress);
        for (uint8 i = 1; i < _to.length; i++) {
            token.transferFrom(from, _to[i], _value);
        }

        emit LogTokenMultiSent(_tokenAddress, sendAmount);
    }

    function coinSendDifferentValue(
        address _tokenAddress,
        address[] memory _to,
        uint256[] memory _value
    ) internal {
        uint256 sendValue = msg.value;
        bool vip = isVIP(msg.sender);
        if (!vip) {
            require(sendValue >= txFee);
        }
        require(_to.length == _value.length);
        require(_to.length <= 255);

        uint256 sendAmount = _value[0];
        StandardToken token = StandardToken(_tokenAddress);

        for (uint8 i = 1; i < _to.length; i++) {
            token.transferFrom(msg.sender, _to[i], _value[i]);
        }

        emit LogTokenMultiSent(_tokenAddress, sendAmount);
    }

    /*
    Send ether with the same value by an explicit call method
    */
    function sendEth(address[] memory _to, uint256 _value) public payable {
        ethSendSameValue(_to, _value);
    }

    /*
    Send ether with different values by an explicit call method
    */
    function multisend(
        address[] memory _to,
        uint256[] memory _value
    ) public payable {
        ethSendDifferentValue(_to, _value);
    }

    /*
    Send ether with different values by an implicit call method
    */
    function mutiSendETHWithDifferentValue(
        address[] memory _to,
        uint256[] memory _value
    ) public payable {
        ethSendDifferentValue(_to, _value);
    }

    /*
    Send ether with the same value by an implicit call method
    */
    function mutiSendETHWithSameValue(
        address[] memory _to,
        uint256 _value
    ) public payable {
        ethSendSameValue(_to, _value);
    }

    /*
    Send tokens with the same value by an implicit call method
    */
    function mutiSendCoinWithSameValue(
        address _tokenAddress,
        address[] memory _to,
        uint256 _value
    ) public payable {
        coinSendSameValue(_tokenAddress, _to, _value);
    }

    /*
    Send tokens with different values by an implicit call method, this method can save some fee.
    */
    function mutiSendCoinWithDifferentValue(
        address _tokenAddress,
        address[] memory _to,
        uint256[] memory _value
    ) public payable {
        coinSendDifferentValue(_tokenAddress, _to, _value);
    }

    /*
    Send tokens with different values by an explicit call method
    */
    function multisendToken(
        address _tokenAddress,
        address[] memory _to,
        uint256[] memory _value
    ) public payable {
        coinSendDifferentValue(_tokenAddress, _to, _value);
    }

    /*
    Send tokens with the same value by an explicit call method
    */
    function drop(
        address _tokenAddress,
        address[] memory _to,
        uint256 _value
    ) public payable {
        coinSendSameValue(_tokenAddress, _to, _value);
    }
}

// File: ERC20Basic.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;

contract ERC20Basic {
  uint public totalSupply;
  function balanceOf(address who) public constant returns (uint);
  function transfer(address to, uint value) public;
  event Transfer(address indexed from, address indexed to, uint value);
}
// File: ERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;

import "./ERC20Basic.sol";

contract ERC20 is ERC20Basic {
  function allowance(address owner, address spender) public constant returns (uint);
  function transferFrom(address from, address to, uint value) public;
  function approve(address spender, uint value) public;
  event Approval(address indexed owner, address indexed spender, uint value);
}
// File: SafeMath.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;
library SafeMath {
  function mul(uint a, uint b) internal pure  returns (uint) {
    uint c = a * b;
    require(a == 0 || c / a == b);
    return c;
  }
  function div(uint a, uint b) internal pure returns (uint) {
    require(b > 0);
    uint c = a / b;
    require(a == b * c + a % b);
    return c;
  }
  function sub(uint a, uint b) internal pure returns (uint) {
    require(b <= a);
    return a - b;
  }
  function add(uint a, uint b) internal pure returns (uint) {
    uint c = a + b;
    require(c >= a);
    return c;
  }
  function max64(uint64 a, uint64 b) internal  pure returns (uint64) {
    return a >= b ? a : b;
  }
  function min64(uint64 a, uint64 b) internal  pure returns (uint64) {
    return a < b ? a : b;
  }
  function max256(uint256 a, uint256 b) internal  pure returns (uint256) {
    return a >= b ? a : b;
  }
  function min256(uint256 a, uint256 b) internal  pure returns (uint256) {
    return a < b ? a : b;
  }
}
// File: StandardToken.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;

import "./BasicToken.sol";

contract StandardToken is BasicToken, ERC20 {
  mapping (address => mapping (address => uint)) allowed;

  function transferFrom(address _from, address _to, uint _value) public {
    balances[_to] = balances[_to].add(_value);
    balances[_from] = balances[_from].sub(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    Transfer(_from, _to, _value);
  }

  function approve(address _spender, uint _value) public{
    require((_value == 0) || (allowed[msg.sender][_spender] == 0)) ;
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
  }

  function allowance(address _owner, address _spender) public constant returns (uint remaining) {
    return allowed[_owner][_spender];
  }
}
// File: Ownable.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;

contract Ownable {
    address public owner;

    function Ownable() public{
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
    function transferOwnership(address newOwner) onlyOwner public{
        if (newOwner != address(0)) {
            owner = newOwner;
        }
    }
}
// File: BasicToken.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.4.0;

import "./ERC20.sol";
import "./SafeMath.sol";

contract BasicToken is ERC20Basic {

  using SafeMath for uint;

  mapping(address => uint) balances;

  function transfer(address _to, uint _value) public{
    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    Transfer(msg.sender, _to, _value);
  }

  function balanceOf(address _owner) public constant returns (uint balance) {
    return balances[_owner];
  }
}