contract MultiChainCapitalV2 is Context, IERC20, Ownable {
    using SafeMath for uint256;
    using Address for address;

    /**
      * [ADDRESS MAPPINGS]
      *
      */

    mapping (address => uint256) private _rOwned;
    mapping (address => uint256) private _tOwned;
    mapping (address => mapping (address => uint256)) private _allowances;

    mapping (address => bool) private _isExcludedFromFee;

    mapping (address => bool) private _isMultisig;

    address[] private multisigParties;

    bool public multisigDestroyed = false;

    struct TreasuryNomination {
        address nominee;
        bool valid;
    }

    struct WhitelistNomination {
        address nominee;
        bool valid;
    }

    struct DestructionNomination {
        bool valid;
    }

    mapping (address => TreasuryNomination) private _treasuryNominations;

    mapping (address => WhitelistNomination) private _whitelistNominations;

    mapping (address => DestructionNomination) private _destructionNominations;

    /**
      * [MISC. VARIABLES]
      *
      */

    uint256 private constant MAX = ~uint256(0);
    uint256 private _tTotal = 1000000000000 * 10**9;
    uint256 private _rTotal = (MAX - (MAX % _tTotal));
    uint256 private _tFeeTotal;

    string private _name = 'MultiChainCapital';
    string private _symbol = 'MCC';
    uint8 private _decimals = 9;

    uint256 private _taxFee = 10;
    uint256 private _teamFee = 10;
    uint256 private _previousTaxFee = _taxFee;
    uint256 private _previousTeamFee = _teamFee;

    address payable public MCCTreasury;

    IUniswapV2Router02 public immutable uniswapV2Router;
    address public immutable uniswapV2Pair;

    bool inSwap = false;
    bool public swapEnabled = true;

    // Has the treasury been linked yet:
    bool treasuryLinked = false;

    uint256 private _maxTxAmount = 100000000000000e9;
    // We will set a minimum amount of tokens to be swaped => 5M
    uint256 private _numOfTokensToExchangeForTeam = 5 * 10**3 * 10**9;

    /**
      * [EVENTS]
      *
      */

    event MinTokensBeforeSwapUpdated(
        uint256 minTokensBeforeSwap
    );
    event SwapEnabledUpdated(
        bool enabled
    );

    /**
      * [MODIFIERS]
      *
      */

    modifier lockTheSwap {
        inSwap = true;
        _;
        inSwap = false;
    }

    modifier onlyMultisig {
        require(_isMultisig[msg.sender], "You are not a multisig party.");
        _;
    }

    /**
      * [CONSTRUCTOR]
      *
      */

    constructor (
        address payable _MCCTreasury,
        address[] memory _multisigParties
        //IUniswapV2Router02 _uniswapV2Router
    ) public {
        require(_multisigParties.length == 7, "There must be 7 multisig parties.");

        MCCTreasury = _MCCTreasury;
        _rOwned[_msgSender()] = _rTotal;

        multisigParties = _multisigParties;
        for (uint8 i = 0; i < multisigParties.length; i++) {
            _isMultisig[multisigParties[i]] = true;
        }

        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); // UniswapV2 for Ethereum network
        // Create a uniswap pair for this new token
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());

        // set the rest of the contract variables
        uniswapV2Router = _uniswapV2Router;

        // Exclude contract from fee
        _isExcludedFromFee[address(this)] = true;

        emit Transfer(
            address(0),
            _msgSender(),
            _tTotal
        );
    }

    /**
      * [STANDARD ERC20 FUNCTIONS]
      *
      */

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view override returns (uint256) {
        return _tTotal;
    }

    function balanceOf(
        address account
    ) public view override returns (uint256) {
        return tokenFromReflection(_rOwned[account]);
    }

    function transfer(
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(
        address owner,
        address spender
    ) public view override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(
        address spender,
        uint256 amount
    ) public override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
      * [ALLOWANCE FUNCTIONS]
      *
      */

    function increaseAllowance(
        address spender,
        uint256 addedValue
    ) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(
        address spender,
        uint256 subtractedValue
    ) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
      * [TAX WHITELIST FUNCTIONS]
      *
      */

    function isExcludedFromFee(
        address account
    ) public view returns (bool) {
        return _isExcludedFromFee[account];
    }

    /**
      * [BUY TAX (REFLECTION) FUNCTIONS]
      *
      */

    function reflectionFromToken(
        uint256 tAmount,
        bool deductTransferFee
    ) public view returns (uint256) {
        require(tAmount <= _tTotal, "Amount must be less than supply");
        if (!deductTransferFee) {
            (uint256 rAmount,,,,,,) = _getValues(tAmount);
            return rAmount;
        } else {
            (,uint256 rTransferAmount,,,,,) = _getValues(tAmount);
            return rTransferAmount;
        }
    }

    function tokenFromReflection(
        uint256 rAmount
    ) public view returns (uint256) {
        require(rAmount <= _rTotal, "Amount must be less than total reflections");
        uint256 currentRate =  _getRate();
        return rAmount.div(currentRate);
    }

    function deliver(
        uint256 tAmount
    ) public {
        address sender = _msgSender();
        (uint256 rAmount,,,,,,) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rTotal = _rTotal.sub(rAmount);
        _tFeeTotal = _tFeeTotal.add(tAmount);
    }

    /**
      * [SELL TAX FUNCTIONS]
      *
      */

    function sendETHToTeam(
        uint256 amount
    ) private {
        MCCTreasury.transfer(amount);
    }


    /**
      * [UTILITY FUNCTIONS]
      *
      */

    function removeAllFee() private {
        if(_taxFee == 0 && _teamFee == 0) return;

        _previousTaxFee = _taxFee;
        _previousTeamFee = _teamFee;

        _taxFee = 0;
        _teamFee = 0;
    }

    function restoreAllFee() private {
        _taxFee = _previousTaxFee;
        _teamFee = _previousTeamFee;
    }

    /**
      * [CUSTOM STANDARD FUNCTION EXTENSIONS]
      *
      */

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) private {
        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);
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "Transfer amount must be greater than zero");

        if (sender != owner() && recipient != owner()) {
            require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount.");
        }

        // is the token balance of this contract address over the min number of
        // tokens that we need to initiate a swap?
        // also, don't get caught in a circular team event.
        // also, don't swap if sender is uniswap pair.
        uint256 contractTokenBalance = balanceOf(address(this));

        if (contractTokenBalance >= _maxTxAmount) {
            contractTokenBalance = _maxTxAmount;
        }

        bool overMinTokenBalance = contractTokenBalance >= _numOfTokensToExchangeForTeam;
        if (!inSwap && swapEnabled && overMinTokenBalance && sender != uniswapV2Pair) {
            // We need to swap the current tokens to ETH and send to the team wallet
            swapTokensForEth(contractTokenBalance);

            uint256 contractETHBalance = address(this).balance;
            if(contractETHBalance > 0) {
                sendETHToTeam(address(this).balance);
            }
        }

        //indicates if fee should be deducted from transfer
        bool takeFee = true;

        //if any account belongs to _isExcludedFromFee account then remove the fee
        if(_isExcludedFromFee[sender] || _isExcludedFromFee[recipient]){
            takeFee = false;
        }

        //transfer amount, it will take tax and team fee
        _tokenTransfer(sender,recipient,amount,takeFee);
    }

    function _tokenTransfer(
        address sender,
        address recipient,
        uint256 amount,
        bool takeFee
    ) private {
        if(!takeFee) {
            removeAllFee();
        }

        _transferStandard(sender, recipient, amount);

        if(!takeFee) {
            restoreAllFee();
        }
    }

    function _transferStandard(
        address sender,
        address recipient,
        uint256 tAmount
    ) private {
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 rTeam, uint256 tTransferAmount, uint256 tFee, uint256 tTeam) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount);
        _takeTeam(rTeam);
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    /**
      * [UNISWAP V2 FUNCTIONS]
      *
      */

    function swapTokensForEth(
        uint256 tokenAmount
    ) private lockTheSwap{
        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // make the swap
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }

    //to recieve ETH from uniswapV2Router when swaping
    receive() external payable {}

    /**
      * [MISC. PRIVATE FEE FUNCTIONS]
      *
      */

    function _takeTeam(
        uint256 rTeam
    ) private {
        _rOwned[address(this)] = _rOwned[address(this)].add(rTeam);
    }

    function _reflectFee(
        uint256 rFee,
        uint256 tFee
    ) private {
        _rTotal = _rTotal.sub(rFee);
        _tFeeTotal = _tFeeTotal.add(tFee);
    }

    /**
      * [PRIVATE PURE FUNCTIONS]
      *
      */

    function _getTValues(
        uint256 tAmount,
        uint256 taxFee,
        uint256 teamFee
    ) private pure returns (uint256, uint256, uint256) {
        uint256 tFee = tAmount.mul(taxFee).div(100);
        uint256 tTeam = tAmount.mul(teamFee).div(100);
        uint256 tTransferAmount = tAmount.sub(tFee).sub(tTeam);
        return (tTransferAmount, tFee, tTeam);
    }

    function _getRValues(
        uint256 tAmount,
        uint256 tFee,
        uint256 tTeam,
        uint256 currentRate
    ) private pure returns (uint256, uint256, uint256, uint256) {
        uint256 rAmount = tAmount.mul(currentRate);
        uint256 rFee = tFee.mul(currentRate);
        uint256 rTeam = tTeam.mul(currentRate);
        uint256 rTransferAmount = rAmount.sub(rFee).sub(rTeam);
        return (rAmount, rTransferAmount, rFee, rTeam);
    }

    /**
      * [PRIVATE VIEW FUNCTIONS]
      *
      */

    function _getValues(
        uint256 tAmount
    ) private view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
        (uint256 tTransferAmount, uint256 tFee, uint256 tTeam) = _getTValues(tAmount, _taxFee, _teamFee);
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 rTeam) = _getRValues(tAmount, tFee, tTeam, _getRate());
        return (rAmount, rTransferAmount, rFee, rTeam, tTransferAmount, tFee, tTeam);
    }

    function _getRate() private view returns (uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply.div(tSupply);
    }

    function _getCurrentSupply() private view returns (uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;
        if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }

    function _getTaxFee() private view returns (uint256) {
        return _taxFee;
    }

    function _getMaxTxAmount() private view returns (uint256) {
        return _maxTxAmount;
    }

    /**
      * [PUBLIC VIEW FUNCTIONS]
      *
      */

    function totalFees() public view returns (uint256) {
        return _tFeeTotal;
    }

    function _getETHBalance() public view returns (uint256 balance) {
        return address(this).balance;
    }

    function isMultisigDestroyed() public view returns (bool) {
        return multisigDestroyed;
    }

    function isMultisigParty(
        address _party
    ) public view returns (bool) {
        return _isMultisig[_party];
    }

    function getTreasury() public view returns (address payable) {
        return MCCTreasury;
    }

    /**
      * [MULTISIG]
      *
      */

    function _nominateTreasury(
        address _nominee,
        bool _valid
    ) external onlyMultisig() {
        require(multisigDestroyed == false, "The multisig has been destroyed.");
        require(treasuryLinked == false, "The treasury contract has already been linked.");

        _treasuryNominations[msg.sender].nominee = _nominee;
        _treasuryNominations[msg.sender].valid = _valid;
    }

    function _linkTreasury(
        address _MCCTreasury
    ) external onlyMultisig() {
        require(multisigDestroyed == false, "The multisig has been destroyed.");
        require(treasuryLinked == false, "The treasury contract has already been linked.");

        uint8 agreements = 0;

        for (uint8 i = 0; i < multisigParties.length; i++) {
            if (_treasuryNominations[multisigParties[i]].nominee == _MCCTreasury && _treasuryNominations[multisigParties[i]].valid == true) {
                agreements++;
            }
        }

        require(agreements >= 5, "Multisig requires 5/7 approval.");

        _isExcludedFromFee[_MCCTreasury] = true;
        MCCTreasury = payable(_MCCTreasury);

        treasuryLinked = true;
    }

    function _nominateFeeExclusion(
        address _nominee,
        bool _valid
    ) external onlyMultisig() {
        require(multisigDestroyed == false, "The multisig has been destroyed.");

        _whitelistNominations[msg.sender].nominee = _nominee;
        _whitelistNominations[msg.sender].valid = _valid;
    }

    function setExcludeFromFee(
        address _nominee,
        bool _excluded
    ) external onlyMultisig() {
        require(multisigDestroyed == false, "The multisig has been destroyed.");

        uint8 agreements = 0;

        for (uint8 i = 0; i < multisigParties.length; i++) {
            if (_treasuryNominations[multisigParties[i]].nominee == _nominee && _treasuryNominations[multisigParties[i]].valid == _excluded) {
                agreements++;
            }
        }

        require(agreements >= 5, "Multisig requires 5/7 approval.");

        _isExcludedFromFee[_nominee] = _excluded;
    }

    function _nominateDestruction(
        bool _valid
    ) external onlyMultisig() {
        require(multisigDestroyed == false, "The multisig has been destroyed.");

        _destructionNominations[msg.sender].valid = _valid;
    }

    function _destroyMultisig() external onlyMultisig() {
        require(multisigDestroyed == false, "The multisig has been destroyed.");

        uint8 agreements = 0;

        for (uint8 i = 0; i < multisigParties.length; i++) {
            if (_destructionNominations[multisigParties[i]].valid == true) {
                agreements++;
            }
        }

        require(agreements >= 5, "Multisig requires 5/7 approval.");

        multisigDestroyed = true;
    }
}
function _getTValues(
    uint256 tAmount,
    uint256 taxFee,
    uint256 teamFee
) private pure returns (uint256, uint256, uint256) {
    uint256 tFee = tAmount.mul(taxFee).div(100);
    uint256 tTeam = tAmount.mul(teamFee).div(100);
    uint256 tTransferAmount = tAmount.sub(tFee).sub(tTeam);
    return (tTransferAmount, tFee, tTeam);
}

/**
 * [PRIVATE PURE FUNCTIONS]
 *
 */