// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: contracts/interfaces/ITokenGate.sol



// SPDX-License-Identifier: MIT

/*
 * Created by Satoshi Nakajima (@snakajima)
 */

pragma solidity ^0.8.6;

interface ITokenGate {
  // Intentially same as ERC721's balanceOf
  function balanceOf(address _wallet) external view returns (uint256 balance);
}

// File: contracts/localNouns/interfaces/ILocalNounsToken.sol



// SPDX-License-Identifier: GPL-3.0

/// @title Interface for NounsSeeder

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

pragma solidity ^0.8.6;

interface ILocalNounsToken {
  function mintSelectedPrefecture(address to, uint256 prefectureId, uint256 _amount) external returns (uint256 tokenId);

  function setMinter(address _minter) external;

  // iLocalNounsTokenでERC721のtotalSupplyを使用したいけど、二重継承でエラーになるので個別関数を準備
  function totalSupply2() external returns (uint256);

  // Fires when the owner puts the trade
  event PutTradePrefecture(uint256 indexed tokenId, uint256[] _prefectures, address _tradeAddress);

  // Fires when the owner cancel the trade
  event CancelTradePrefecture(uint256 indexed tokenId);

  // Fires when the purchase executed
  event Purchase(uint256 indexed tokenId, address _buyer);

  // Fires when the trade executed
  event ExecuteTrade(uint256 indexed targetTokenId, address _lister, uint256 indexed ownedTokenId, address _executer);

}

// File: contracts/localNouns/LocalNounsMinter2.sol



// SPDX-License-Identifier: GPL-3.0

/// @title Interface for NounsSeeder

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

pragma solidity ^0.8.6;

import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol';
import './interfaces/ILocalNounsToken.sol';
import '../interfaces/ITokenGate.sol';

contract LocalNounsMinter2 is Ownable {
  // Fires when the purchase executed
  event MintSelectedPrefecture(uint256 prefectureId, uint256 amount, address minter);

  ILocalNounsToken public token;
  ITokenGate public immutable tokenGate;
  
  address[] public royaltyAddresses; // ロイヤリティ送信先ウォレット
  mapping(address => uint256) public royaltyRatio; // ロイヤリティ送信先ウォレットごとの割合
  uint256 royaltyRatioTotal; // royaltyRatioの合計(割戻用)

  uint256 public mintPriceForSpecified = 0.03 ether;
  uint256 public mintPriceForNotSpecified = 0.01 ether;

  uint256 public mintMax = 1500;

  enum SalePhase {
    Locked,
    PreSale,
    PublicSale
  }

  SalePhase public phase = SalePhase.PublicSale; // セールフェーズ

  address public administratorsAddress; // 運営ウォレット

  constructor(ILocalNounsToken _token, ITokenGate _tokenGate) {
    token = _token;
    administratorsAddress = msg.sender;
    tokenGate = _tokenGate;

    // ロイヤリティ送信先(コンストラクタではデプロイアドレス100%)
    royaltyAddresses = [msg.sender];
    royaltyRatio[msg.sender] = 1;
    royaltyRatioTotal = 1;
  }

  function setMintMax(uint256 _mintMax) external onlyOwner {
    mintMax = _mintMax;
  }

  function setLocalNounsToken(ILocalNounsToken _token) external onlyOwner {
    token = _token;
  }

  function setMintPriceForSpecified(uint256 _price) external onlyOwner {
    mintPriceForSpecified = _price;
  }

  function setMintPriceForNotSpecified(uint256 _price) external onlyOwner {
    mintPriceForNotSpecified = _price;
  }

  function setPhase(SalePhase _phase) external onlyOwner {
    phase = _phase;
  }

  function setAdministratorsAddress(address _admin) external onlyOwner {
    administratorsAddress = _admin;
  }

  function mintSelectedPrefecture(uint256 _prefectureId, uint256 _amount) public payable returns (uint256 tokenId) {
    return mintSelectedPrefecture2(_prefectureId, _amount, msg.sender);
  }

  function mintSelectedPrefecture2(
    uint256 _prefectureId,
    uint256 _amount,
    address _mintTo
  ) public payable returns (uint256 tokenId) {
    if (phase == SalePhase.Locked) {
      revert('Sale is locked');
    } else if (phase == SalePhase.PreSale) {
      require(tokenGate.balanceOf(msg.sender) > 0, 'TokenGate token is needed');
    }

    require(token.totalSupply2() + _amount <= mintMax, 'Over the mint limit');

    uint256 mintPrice;
    if (_prefectureId == 0) {
      mintPrice = mintPriceForNotSpecified;
    } else {
      mintPrice = mintPriceForSpecified;
    }
    require(msg.value >= mintPrice * _amount, 'Must send the mint price');

    tokenId = token.mintSelectedPrefecture(_mintTo, _prefectureId, _amount);

    _sendRoyalty(msg.value);

    emit MintSelectedPrefecture(_prefectureId, _amount, _mintTo);
  }

  function withdraw() external payable onlyOwner {
    _sendRoyalty(address(this).balance);
  }

  // send royalties to admin and developper
  function _sendRoyalty(uint _royalty) internal {
    for (uint256 i = 0; i < royaltyAddresses.length; i++) {
      _trySendRoyalty(royaltyAddresses[i], (_royalty * royaltyRatio[royaltyAddresses[i]]) / royaltyRatioTotal);
    }
  }

  function _trySendRoyalty(address to, uint amount) internal {
    (bool sent, ) = payable(to).call{ value: amount }('');
    require(sent, 'Failed to send');
  }

  function setRoyaltyAddresses(address[] memory _addr, uint256[] memory ratio) external onlyOwner {
    // 引数の整合性チェック
    require(_addr.length == ratio.length, 'Invalid Arrays length');
    royaltyAddresses = _addr;
    royaltyRatioTotal = 0;

    for (uint256 i = 0; i < _addr.length; i++) {
      royaltyRatio[_addr[i]] = ratio[i];
      royaltyRatioTotal += ratio[i];
    }
  }

}