contract Gateway is Ownable, SafeMath {
    IERC20Token public tokenContract;
    IEscrow public escrowContract;
    address public admin;
    address public jointerVoting;   //Jointer voting contract can block specific Channel or Wallet

    struct Channel {
        string name;    // name of channel
        bool isBlocked;  // Block entire channel to transfer tokens to any wallets.
        ChannelContract channel;    // address of ChannelContract
    }

    Channel[] channels;     // list of liquidity channels

    event AddChannel(uint256 indexed channelId, address indexed channelAddress, string name);
    event BlockChannel(uint256 indexed channelId, bool isBlock);

    /**
     * @dev Throws if called by any account other than the admin.
     */
    modifier onlyAdmin() {
        require(admin == msg.sender,"Not admin");
        _;
    }

    /**
     * @dev Set token contract address.
     * @param token The address of token contract.
     */
    function setTokenContract(IERC20Token token) external onlyOwner {
        require(token != IERC20Token(0) && tokenContract == IERC20Token(0),"Change address not allowed");
        tokenContract = token;
    }

    /**
     * @dev Set escrow contract address
     * @param escrow The address of escrow contract.
     */
    function setEscrowContract(address payable escrow) external onlyOwner {
        require(escrow != address(0),"Zero address");
        escrowContract = IEscrow(escrow);
    }

    /**
     * @dev Set Jointer Voting (Escrowed) contract address
     * @param newAddress The address of escrow contract.
     */
    function setJointerVotingContract(address payable newAddress) external onlyOwner {
        require(newAddress != address(0),"Zero address");
        jointerVoting = newAddress;
    }

    /**
     * @dev Set gateway admin address
     * @param _admin The address of gateway admin wallet.
     */
    function setAdmin(address _admin) external onlyOwner {
        require(_admin != address(0),"Zero address");
        admin = _admin;
    }

    // Add new Liquidity channel
    function addChannel(string memory name) external onlyOwner {
        uint256 channelId = channels.length;
        require(channelId < 251, "Channels limit reached");
        channels.push();
        channels[channelId].name = name;
        channels[channelId].channel = new ChannelContract(tokenContract, payable(address(escrowContract)), name);
        emit AddChannel(channelId, address(channels[channelId].channel), name);
    }

    function getChannelsNumber() external view returns(uint256) {
        return channels.length;
    }

    // Get details of channel
    function getChannelInfo(uint256 channelId) external view 
        returns(
            string memory name,
            address channelAddress,
            bool isBlocked,
            uint256 amount,
            uint256 spent,
            uint256 walletsNumber
        ) 
    {
        name = channels[channelId].name;
        channelAddress = address(channels[channelId].channel);
        isBlocked = channels[channelId].isBlocked;
        amount = channels[channelId].channel.received();
        spent = channels[channelId].channel.spent();
        walletsNumber = channels[channelId].channel.getWalletsNumber();
    }

    // Add new wallet to selected liquidity channel
    function addWallet(uint256 channelId, string memory name, address payable wallet) external onlyOwner {
        require(wallet != address(0),"Zero address");
        channels[channelId].channel.addWallet(name, wallet);
    }

    // Update wallet address on selected liquidity channel. If wallet is address(0) - wallet removed.
    function updateWallet(uint256 channelId, uint256 walletId, address payable wallet) external onlyOwner {
        channels[channelId].channel.updateWallet(walletId, wallet);
    }

    // In case updating gateway contract, we can change Channel Ownership to the new Gateway contract
    function transferChannelOwnership(uint256 channelId, address newOwner) external onlyOwner {
        channels[channelId].channel.transferOwnership(newOwner);
    }

    // Transfer token to gateway from selected channel
    function transferToGateway(uint256 value, uint256 channelId) external onlyAdmin {
        uint256 approved = escrowContract.transferToGateway(value, channelId);  // amount of approved tokens
        ChannelContract channel = channels[channelId].channel;
        require(tokenContract.transferFrom(address(escrowContract), address(channel), approved),"Transfer to Gateway channel Failed");
        channel.receiveTokens(approved);
    }

    // Gateway transfer token to Escrow from Channel
    function transferFromGateway(uint256 value, uint256 channelId) external onlyAdmin {
        channels[channelId].channel.returnToEscrow(value);
        escrowContract.transferFromGateway(value, channelId);
    }

    // Block selected wallet transfer to.
    function blockWallet(uint256 channelId, uint256 walletId, bool isBlock) external {
        require(msg.sender == jointerVoting, "Only JNTR voting allowed");
        channels[channelId].channel.blockWallet(walletId, isBlock);
    }

    // Block selected channel transfer to any wallet.
    function blockChannel(uint256 channelId, bool isBlock) external {
        require(msg.sender == jointerVoting, "Only JNTR voting allowed");
        channels[channelId].isBlocked = isBlock;
        emit BlockChannel(channelId, isBlock);
    }

    // transfer tokens from Channel to selected wallet (ex. Exchange wallet)
    function transferTokens(uint256 channelId, uint256 walletId, uint256 value) external onlyAdmin {
        channels[channelId].channel.transferTokens(walletId, value);
    }

    // approve tokens from Channel to selected wallet (ex. Exchange wallet)
    function approveTokens(uint256 channelId, uint256 walletId, uint256 value) external onlyAdmin {
        channels[channelId].channel.approveTokens(walletId, value);
    }

    /**
     * @dev Trigger arbitrary function on selected wallet address.
     * @param channelId The channel which have to call
     * @param walletId The wallet to call
     * @param params encoded params
     */
    function trigger(uint256 channelId, uint256 walletId, bytes calldata params) external onlyAdmin {
        channels[channelId].channel.trigger(walletId, params);
    }

    /**
     * @dev Send giveaways (received ETH/ERC20) to Escrow for splitting among participants.
     * When token sold on exchange, withdraw ETH/ERC20 to this contract address.
     * @param channelId Liquidity channel ID which sold tokens.
     * @param soldTokenAmount amount of sold tokens
     * @param receivedToken The ERC20 token address (or 0 for ETH) for which tokens were sold.
     * @param receivedAmount Amount of ETH/ERC20 received for sold tokens.
     */
    function transferGiveaways(uint256 channelId, uint256 soldTokenAmount, address receivedToken, uint256 receivedAmount) external onlyAdmin {
        channels[channelId].channel.transferGiveaways(soldTokenAmount, receivedToken, receivedAmount);
        escrowContract.paymentFromGateway(channelId, receivedToken, receivedAmount, soldTokenAmount);
    }
}
function setTokenContract(IERC20Token token) external onlyOwner {
    require(token != IERC20Token(0) && tokenContract == IERC20Token(0),"Change address not allowed");
    tokenContract = token;
}

/**
 * @dev Set token contract address.
 * @param token The address of token contract.
 */