// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

interface IWXToken {
    function _changeValidatorAddress(address newValidatorAddress) external;
    function _setVWXTokenAddress(address VWXTokenAddress) external;
    function _terminateMint() external;
}

interface IVWXToken {
    function _changeExchangeRate() external;
}

contract WXMultiSign {
    mapping(address => bool) public isOwner;
    address[] public owners;
    mapping(address => mapping(address => bool)) public validatorAddressConfirmInfo;
    IWXToken public WToken;
    IWXToken public XToken;
    mapping(address => bool) public terminateConfirmInfo;
    address public deployerAddress;
    address public XTokenAddress;

    event ConfirmChangeValidatorAddress(address indexed validatorAddress, address signAddress);
    event ChangeValidatorAddress(address indexed _newValidatorAddress);
    event ConfirmTerminate(address indexed signAddress);

    constructor(address[] memory _owners) {
        owners = _owners;
        for (uint i = 0; i < owners.length; i++) {
            isOwner[owners[i]] = true;
        }
        deployerAddress = msg.sender;
    }

    function getSignAddress(address _newValidatorAddress, bytes memory _signature) internal pure returns(address) {
        require(_signature.length == 65, "Invalid signature.");
        bytes32 r;
        bytes32 s;
        uint8 v;
        
        bytes32 msgHash;

        assembly{
            r := mload(add(_signature, 0x20))
            s := mload(add(_signature, 0x40))
            v := byte(0, mload(add(_signature, 0x60)))            
        }
        msgHash = getEthMessageHash(_newValidatorAddress);
        return ecrecover(msgHash, v, r, s);
    }

    function getEthMessageHash(address _newValidatorAddress) internal pure returns(bytes32) {
        bytes32 messageHash;
        messageHash = keccak256(abi.encodePacked(_newValidatorAddress));
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
    }

    function confirmChangeValidatorAddress(address newValidatorAddress, bytes memory signature) public returns(bool) {
        address _signAddress = getSignAddress(newValidatorAddress, signature);
        require(isOwner[_signAddress], "Invaild signature.");
        require(!validatorAddressConfirmInfo[newValidatorAddress][_signAddress], "Already confirmed.");
        validatorAddressConfirmInfo[newValidatorAddress][_signAddress] = true;
        emit ConfirmChangeValidatorAddress(newValidatorAddress, _signAddress);
        return true;
    }

    modifier isPassed(address _validatorAddress) {
        require(
            validatorAddressConfirmInfo[_validatorAddress][owners[0]]
            && validatorAddressConfirmInfo[_validatorAddress][owners[1]]
            && validatorAddressConfirmInfo[_validatorAddress][owners[2]],
             "Not pass.");
        _;
    }

    function changeValidatorAddress(address newValidatorAddress)
        isPassed(newValidatorAddress) 
        public returns(bool) {
            WToken._changeValidatorAddress(newValidatorAddress);
            XToken._changeValidatorAddress(newValidatorAddress);
            emit ChangeValidatorAddress(newValidatorAddress);
            return true;
    }

    function setTokenAddress(
        address _WTokenAddress, 
        address _XTokenAddress, 
        address _VWXTokenAddress) public returns(bool) {
            require(msg.sender == deployerAddress, "Only deployerAddress.");
            require(XTokenAddress == address(0), "Already set.");
            XTokenAddress = _XTokenAddress;
            WToken = IWXToken(_WTokenAddress);
            XToken = IWXToken(_XTokenAddress);
            WToken._setVWXTokenAddress(_VWXTokenAddress);
            XToken._setVWXTokenAddress(_VWXTokenAddress);
            return true;
    }

    // confirmRenounceOwnership
    function confirmTerminate(bytes memory signature) public returns(bool) {
        address _signAddress = getSignAddress(address(0), signature);
        require(isOwner[_signAddress], "Only owner.");
        terminateConfirmInfo[_signAddress] = true;
        emit ConfirmTerminate(_signAddress);
        return true;
    }

    function terminateMint() public returns(bool) {
        require(
            terminateConfirmInfo[owners[0]]
            && terminateConfirmInfo[owners[1]]
            && terminateConfirmInfo[owners[2]], "Require all owners' confirmation.");
        
        WToken._terminateMint();
        XToken._terminateMint();
        return true;
    }

}

interface IXToken {
    function mintedCount() external view returns(uint32);
}

contract VWXMultiSign {
    address[] public owners;
    IXToken public _XToken;
    mapping(address => bool) public isOwner;
    uint public exchangeRate = 1;
    mapping(uint => mapping(address => bool)) public exchangeRateConfirmInfo;
    mapping(uint => uint) public exchangeRateTimestamp;
    uint public GRACE_PERIOD = 30 days;

    constructor(address[] memory _owners, address XTokenAddress) {
        owners = _owners;
        for (uint i = 0; i < _owners.length; i++) {
            isOwner[_owners[i]] = true;
        }
        _XToken = IXToken(XTokenAddress);
        exchangeRateTimestamp[exchangeRate] = block.timestamp;
    }

    event ConfirmChangeExchangeRate(uint indexed _nextExchangeRate, address _signAddress);
    event ChangeExchangeRate(uint _nextExchangeRate);
    
    function getSignAddress(uint _nounce, bytes memory _signature) internal pure returns(address) {
        require(_signature.length == 65, "Invalid signature.");
        bytes32 r;
        bytes32 s;
        uint8 v;
        
        bytes32 msgHash;

        assembly{
            r := mload(add(_signature, 0x20))
            s := mload(add(_signature, 0x40))
            v := byte(0, mload(add(_signature, 0x60)))            
        }
        msgHash = getEthMessageHash(_nounce);
        return ecrecover(msgHash, v, r, s);
    }

    function getEthMessageHash(uint _nounce) internal pure returns(bytes32) {
        bytes32 messageHash;
        messageHash = keccak256(abi.encodePacked(_nounce));
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
    }

    //Change exchange rate
    function confirmChangeExchangeRate(bytes memory signature) public {
        address _signAddress = getSignAddress(2*exchangeRate, signature);
        require(isOwner[_signAddress], "Only owner.");
        require(exchangeRate < 64, "The max value is 64.");
        require(!exchangeRateConfirmInfo[2 * exchangeRate][_signAddress], "Already confirmed.");
        exchangeRateConfirmInfo[2 * exchangeRate][_signAddress] = true;
        emit ConfirmChangeExchangeRate(2 * exchangeRate, _signAddress);
    }

    modifier isPassedRate() {
        require(
            exchangeRateConfirmInfo[2*exchangeRate][owners[0]]
            && exchangeRateConfirmInfo[2*exchangeRate][owners[1]]
            && exchangeRateConfirmInfo[2*exchangeRate][owners[2]],
            "not Pass");
        _;
    }

    function changeExchangeRate(address VWXTokenAddress) isPassedRate() public returns(bool) {
        uint32 count = _XToken.mintedCount();
        if (exchangeRate < 4) {
            if (exchangeRate == 1) {
                // 1 => 2
                require(count >= 10**3, "Need more than 1,000 minted.");
            }else if (exchangeRate == 2) {
                // 2 => 4
                require(count >= 10**4, "Need more than 10,000 minted.");
            }
        } else {
            require(block.timestamp >= exchangeRateTimestamp[exchangeRate] + GRACE_PERIOD, "Time lock");
            if (exchangeRate == 4) {
                // 4 => 8
                // require 1 WT >=0.000003 Bitcoin
                require(count >= 10**5, "Need more than 100,000 minted.");
            } else if (exchangeRate == 8) {
                // 8 => 16
                // require 1 VWX >=0.00001 Bitcoin
                require(count >= 10**6, "Need more than 1,000,000 minted.");
            } else if (exchangeRate == 16) {
                // 16 => 32
                // require 1 VWX >=0.00002 Bitcoin
                require(count >= 10**7, "Need more than 10,000,000 minted.");
            } else if (exchangeRate == 32) {
                // 32 => 64
                // require 1 VWX >=0.00003 Bitcoin
                require(count >= 10**8, "Need more than 100,000,000 minted.");
            }
        }

        IVWXToken(VWXTokenAddress)._changeExchangeRate();
        exchangeRate *= 2;
        exchangeRateTimestamp[exchangeRate] = block.timestamp;
        emit ChangeExchangeRate(exchangeRate);
        return true;
    }

}