contract NFTMarket is Ownable {

  using SafeMath for uint;
  using ECDSA for bytes32;

  string private signatureSalt;
  uint private allowableSecond;
  bool private aclstatus;

  //Whitelistのmapping配列
  mapping(address => bool) public whitelists;

  constructor () public {
    allowableSecond = 86400;
    aclstatus = true;
  }

  function getSignatureSalt() public view onlyOwner returns(string memory) {
		return signatureSalt;
	}

	function setSignatureSalt(string calldata _signatureSalt) external onlyOwner {
		signatureSalt = _signatureSalt;
	}

  function getAllowableSecond() public view onlyOwner returns(uint) {
		return allowableSecond;
	}

	function setAllowableSecond(uint _allowableSecond) external onlyOwner {
		allowableSecond = _allowableSecond;
	}
  

  function validateSignatureMessageToken(bytes memory _signatureMessage, uint _timestamp, address _erc721Address,address _erc20Address,uint _tokenId, address _from, uint _value, bytes32 _royaltyhash) private view returns(bool){
    require(block.timestamp - _timestamp < allowableSecond || _timestamp - block.timestamp < allowableSecond, string(abi.encodePacked("Invalid timestamp ", uintToStr(block.timestamp), ":", uintToStr(_timestamp), " (",  uintToStr(allowableSecond ),")")) );
    bytes32 message = keccak256(abi.encodePacked(_erc721Address, _erc20Address, _tokenId, _from, _value, _royaltyhash, signatureSalt, _timestamp));
    address verifyaddress = message.toEthSignedMessageHash().recover(_signatureMessage);
    if(owner() == verifyaddress){
      return true;
    } else {
      return getWhitelistinvoker(verifyaddress);
    }
  }

  function transferValue(address _erc20address, address payable to, address payable payer, uint value, address payable[] memory _receiveAddresses, uint[] memory _receiveFees) private {
    require(value > 0, 'The value is not enough.');
    if(_erc20address != address(0x0)){
      ERC20 ftcontract = ERC20(_erc20address);
      for (uint i = 0; i < _receiveAddresses.length; i++) {
        uint fee = _receiveFees[i];
        value = value.sub(fee);
        ftcontract.transferFrom(payer, _receiveAddresses[i], fee);
      }
      ftcontract.transferFrom(payer, to, value);
    } else {
      for (uint i = 0; i < _receiveAddresses.length; i++) {
        uint fee = _receiveFees[i];
        value = value.sub(fee);
        _receiveAddresses[i].transfer(fee);
      }
      to.transfer(value);
    }
  }
  
  function transferTokenAndValue(address _erc721Address, address _erc20address, address payable contractowner,uint _value, uint _tokenId, address payable _from, address payable _to, address payable[] memory _receiveAddresses, uint[] memory _receiveFees) private {
    ERC721 nftContract = ERC721(_erc721Address);
    address ownerAddress = nftContract.ownerOf(_tokenId);
    require(ownerAddress == _from, 'Owner information is incorrect.');
    require(ownerAddress != _to, 'Transfer is incorrect.');
    if (_value > 0) {
      if(contractowner != address(0x0)){
        transferValue(_erc20address,  _from, contractowner, _value, _receiveAddresses, _receiveFees);
      } else {
        transferValue(_erc20address,  _from, _to, _value, _receiveAddresses, _receiveFees);
      }
    }
    nftContract.safeTransferFrom(_from, _to, _tokenId);
  }

  function purchaseTokenByETH(bytes calldata _signatureMessage,uint timestamp, address _erc721Address, uint _tokenId, address payable _from, address payable[] calldata _receiveAddresses, uint[] calldata _receiveFees) external payable {
    uint value = msg.value;
    address payable to = msg.sender;
    require(aclstatus,"Restricts the execution of contracts");
    require(value > 0, 'The value is not enough.');
    require(validateSignatureMessageToken(_signatureMessage, timestamp, _erc721Address, address(0x0),_tokenId, _from, value, keccak256(abi.encodePacked(_receiveAddresses, _receiveFees))), 'Signature message is invalid.');
    transferTokenAndValue(_erc721Address, address(0x0), address(0x0),value, _tokenId, _from, to, _receiveAddresses, _receiveFees);
  }

  function purchaseTokenByERC20(bytes calldata _signatureMessage,uint timestamp, address _erc721Address,address _erc20address, uint _tokenId, uint _value,address payable _from, address payable[] calldata _receiveAddresses, uint[] calldata _receiveFees) external payable {
    uint value = _value;
    address payable to = msg.sender;
    require(aclstatus,"Restricts the execution of contracts");
    require(value > 0, 'The value is not enough.');
    require(validateSignatureMessageToken(_signatureMessage, timestamp, _erc721Address, _erc20address,_tokenId, _from, value, keccak256(abi.encodePacked(_receiveAddresses, _receiveFees))), 'Signature message is invalid.');
    transferTokenAndValue(_erc721Address, _erc20address, address(0x0),value, _tokenId, _from, to, _receiveAddresses, _receiveFees);
  }

  function purchaseTokenByFiat(address _erc721Address,address _erc20Address,uint _tokenId, uint _value,address payable _from, address payable _to, address payable[] calldata _receiveAddresses, uint[] calldata _receiveFees) external payable {
    require(msg.sender == owner() || getWhitelistinvoker(msg.sender),'Not have call permission');
    uint value;
    if(_erc20Address != address(0x0)){
      value = _value;
    } else {
      value = msg.value;
    }
    transferTokenAndValue(_erc721Address, _erc20Address, msg.sender, value, _tokenId, _from, _to, _receiveAddresses, _receiveFees);
  }
  

  function RoiyaltysplitERC20(address _erc20address ,address payable[] calldata _splitAddresses ,uint[] calldata _splitFees) external payable {
    require(msg.sender == owner() || getWhitelistinvoker(msg.sender),'Not have call permission');
    require(_splitAddresses.length > 0 && _splitFees.length > 0 ,'No split destination address or No split Fee');
    require(_splitAddresses.length == _splitFees.length,'Defference element num splitaddress and splitfees array');
    if(_erc20address != address(0x0)){
      ERC20 ftcontract = ERC20(_erc20address);
      for(uint i = 0; i < _splitAddresses.length ; i++){
        ftcontract.transferFrom(msg.sender, _splitAddresses[i], _splitFees[i]);
      }
    } else {
      uint value = msg.value;
      for(uint i = 0; i < _splitAddresses.length ; i++){
        value = value.sub(_splitFees[i]);
        _splitAddresses[i].transfer(_splitFees[i]);
      }
      msg.sender.transfer(value);
    }
  }

  //PUBLIC関数のACL制御関数
  /**
   * acl status に関して
   * aclstatus = false : コントラクト実行制限
   * aclstatus = true  : コントラクト実行可能
   */
  function Aclcontroll (bool _acl) public {
    require(msg.sender == owner() || getWhitelistinvoker(msg.sender),'Not have call permission');
    aclstatus = _acl;
  }
  

  function airdroptoken(uint _distributeAmount ,address payable[] calldata _airdropAddresses) external payable {
    require(msg.sender == owner() || getWhitelistinvoker(msg.sender),'Not have call permission');
    require(msg.value > 0,'There is no distribution amount');
    require(_airdropAddresses.length > 0,'No AirDrop destination address');
    require(_airdropAddresses.length.mul(_distributeAmount) == msg.value,' Total amount of distribution is incorrect');
    for(uint i = 0; i < _airdropAddresses.length ; i++){
      _airdropAddresses[i].transfer(_distributeAmount);
    }
  }


  function setWhitelistinvoker(address[] memory _whitelists) public onlyOwner{
      require(_whitelists.length > 0,'Not found regist target of whitelist address');
      for(uint i=0;i < _whitelists.length;i++){
          whitelists[_whitelists[i]] = true;
      }
  }


  function deleteWhitelistinvoker(address deletetargetaddress) public onlyOwner{
      delete whitelists[deletetargetaddress];
  }


  function getWhitelistinvoker(address contractInvoker) public view returns (bool){
      return whitelists[contractInvoker];
  }

  function uintToStr(uint _i) internal pure returns (string memory _uintAsString) {
    uint number = _i;
    if (number == 0) {
      return "0";
    }
    uint j = number;
    uint len;
    while (j != 0) {
      len++;
      j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint k = len - 1;
    while (number != 0) {
      bstr[k--] = byte(uint8(48 + number % 10));
      number /= 10;
    }
    return string(bstr);
  }

}
function Aclcontroll (bool _acl) public {
  require(msg.sender == owner() || getWhitelistinvoker(msg.sender),'Not have call permission');
  aclstatus = _acl;
}

/**
 * acl status に関して
 * aclstatus = false : コントラクト実行制限
 * aclstatus = true  : コントラクト実行可能
 */