contract Minter is Ownable {

  using SafeERC20 for IERC20;
  using SafeMath for uint256;
  
  // events
  event MintedSeed(uint256 indexed kind, uint256 indexed id, uint256 tip, address indexed owner);
  event MintedFun(uint256 indexed kind, uint256 indexed id, uint256 tip, address indexed owner);
  event NewTipLeader(address indexed leader);

  // contracts
  address public fun;
  address public seed;

  // fun kind
  uint256 public constant FUN_PRICE = 1e16; // 0.01 ETH
  uint256 public constant FUN_MINT_LIMIT = 100000;
  uint256 public funMinted;
  
  // seed kind
  uint256 public constant SEED_MINT_LIMIT = 5;
  uint256 public constant PRICE_STEP = 5 * 1e17; // 0.5 ETH
  mapping (uint256 => uint256) public seedMinted;
  
  // tipping state
  mapping (address => uint256) public tips;
  address public tipLeader;
  
  constructor(address _fun, address _seed) public {
    fun = _fun;
    seed = _seed;
  }

  /// @dev Mints a dick from The Seed Collection, tips do not count
  function mint(uint256 _kind) external payable {
    require(Dicks(seed).kindExists(_kind), "dick kind does not exist");
    require(seedMinted[_kind] < SEED_MINT_LIMIT, "all gone");
    seedMinted[_kind] = seedMinted[_kind].add(1);
    uint256 price = seedMinted[_kind].mul(PRICE_STEP);
    require(msg.value >= price, "too cheap?");
    uint256 id = Dicks(seed).mint(msg.sender, _kind);
    emit MintedSeed(_kind, id, 0, msg.sender);
  }
  
  /// @dev Mints a dick from The Seed Collection (FUN), and counts tips
  function mintFun(uint256 _kind) external payable {
    require(Dicks(fun).kindExists(_kind), "dick kind does not exist");
    require(funMinted < FUN_MINT_LIMIT, "all are gone!");
    require(msg.value >= FUN_PRICE, "too cheap!");
    uint256 tip = msg.value.sub(FUN_PRICE);
    tips[msg.sender] = tips[msg.sender].add(tip);
    funMinted = funMinted.add(1);
    uint256 id = Dicks(fun).mint(msg.sender, _kind);
    emit MintedFun(_kind, id, tip, msg.sender);
    if (tips[msg.sender] > tips[tipLeader]) {
      tipLeader = msg.sender;
      emit NewTipLeader(msg.sender);
    }
  }

  /// @dev Mints a range of dick from The Seed Collection (FUN)
  function mintFunRange(uint256 _from, uint256 _to) external payable {
    uint256 number = _to.sub(_from).add(1);
    uint256 price = number.mul(FUN_PRICE);
    require(funMinted.add(number).sub(1) < FUN_MINT_LIMIT, "all are gone!");
    require(msg.value >= price, "too cheap!");
    for (uint256 kind = _from; kind < _to; kind++) {
      require(Dicks(fun).kindExists(kind), "dick kind does not exist");
      uint256 id = Dicks(fun).mint(msg.sender, kind);
      emit MintedFun(kind, id, 0, msg.sender);
    }

    // last iteration handeled separately to account for a tip
    uint256 tip = msg.value.sub(price);
    require(Dicks(fun).kindExists(_to), "dick kind does not exist");
    uint256 id = Dicks(fun).mint(msg.sender, _to);
    emit MintedFun(_to, id, tip, msg.sender);
    funMinted = funMinted.add(number);

    // update tip
    tips[msg.sender] = tips[msg.sender].add(tip);
    if (tips[msg.sender] > tips[tipLeader]) {
      tipLeader = msg.sender;
      emit NewTipLeader(msg.sender);
    }
  }

  /// @dev Allows the owner to stop minting of a dick kind
  function stopMinting(uint256 _kind) external onlyOwner {
    require(Dicks(seed).kindExists(_kind), "dick kind does not exist");
    seedMinted[_kind] = SEED_MINT_LIMIT;
  }

  /// @dev Owner can drain Ether
  function extract(address payable _recipient, uint256 _amount) external onlyOwner {
    _recipient.transfer(_amount);
  }

  /// @dev Owner can drain ERC20 tokens
  function extractToken(address _token, address _recipient, uint256 _amount) external onlyOwner {
    IERC20(_token).safeTransfer(_recipient, _amount);
  }
}
function mint(uint256 _kind) external payable {
  require(Dicks(seed).kindExists(_kind), "dick kind does not exist");
  require(seedMinted[_kind] < SEED_MINT_LIMIT, "all gone");
  seedMinted[_kind] = seedMinted[_kind].add(1);
  uint256 price = seedMinted[_kind].mul(PRICE_STEP);
  require(msg.value >= price, "too cheap?");
  uint256 id = Dicks(seed).mint(msg.sender, _kind);
  emit MintedSeed(_kind, id, 0, msg.sender);
}

/// @dev Mints a dick from The Seed Collection, tips do not count