// 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/LocalNounsMinter.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 LocalNounsMinter is Ownable {
  // Fires when the purchase executed
  event MintSelectedPrefecture(uint256 prefectureId, uint256 amount, address minter);

  ILocalNounsToken public token;
  ITokenGate public immutable tokenGate;

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

  uint256 public mintMax = 1500;

  mapping(address => uint256) public preferentialPurchacedCount;

  enum SalePhase {
    Locked,
    PreSale,
    PublicSale
  }

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

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

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

  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) {
    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(msg.sender, _prefectureId, _amount);

    emit MintSelectedPrefecture(_prefectureId, _amount, msg.sender);
  }

  function withdraw() external payable onlyOwner {
    require(administratorsAddress != address(0), "administratorsAddress shouldn't be 0");
    (bool sent, ) = payable(administratorsAddress).call{ value: address(this).balance }('');
    require(sent, 'failed to move fund to administratorsAddress contract');
  }
}