contract CardMint is CardBase {

  using AddressUtils for address;

  /* EVENTS */
  event TemplateMint(uint256 _templateId);
  // Transfer from address 0x0 = newly minted card.
  event Transfer(
    address indexed _from,
    address indexed _to,
    uint256 indexed _tokenId
  );
  event Approval(
    address indexed _owner,
    address indexed _approved,
    uint256 indexed _tokenId
  );
  event ApprovalForAll(
    address indexed _owner,
    address indexed _operator,
    bool _approved
  );

  /* DATA TYPES */
  struct Template {
    uint256 generation;
    uint256 category;
    uint256 variation;
    string name;
  }

  /* STORAGE */
  // Minter address can mint cards but not templates.
  address public minter;

  Template[] internal templates;
  // Each Card is a template ID (index of a template in `templates`).
  uint256[] internal cards;

  // Template ID => max number of cards that can be minted with this template ID.
  mapping (uint256 => uint256) internal templateIdToMintLimit;
  // Template ID => number of cards that have been minted with this template ID.
  mapping (uint256 => uint256) internal templateIdToMintCount;
  // Card ID => owner of card.
  mapping (uint256 => address) internal cardIdToOwner;
  // Owner => number of cards owner owns.
  mapping (address => uint256) internal ownerToCardCount;
  // Card ID => address approved to transfer on behalf of owner.
  mapping (uint256 => address) internal cardIdToApproved;
  // Operator => from address to operated or not.
  mapping (address => mapping (address => bool)) internal operatorToApprovals;

  /* MODIFIERS */
  modifier onlyMinter() {
    require(msg.sender == minter);
    _;
  }

  /* FUNCTIONS */
  /** PRIVATE FUNCTIONS **/
  function _addTokenTo(address _to, uint256 _tokenId) internal {
    require(cardIdToOwner[_tokenId] == address(0));
    ownerToCardCount[_to] = ownerToCardCount[_to] + 1;
    cardIdToOwner[_tokenId] = _to;
  }

  /** PUBLIC FUNCTIONS **/
  function setMinter(address _minter) external onlyOwner {
    minter = _minter;
  }

  function mintTemplate(
    uint256 _mintLimit,
    uint256 _generation,
    uint256 _category,
    uint256 _variation,
    string _name
  ) external onlyOwner {
    require(_mintLimit > 0);

    uint256 newTemplateId = templates.push(Template({
      generation: _generation,
      category: _category,
      variation: _variation,
      name: _name
    })) - 1;
    templateIdToMintLimit[newTemplateId] = _mintLimit;

    emit TemplateMint(newTemplateId);
  }

  function mintCard(
    uint256 _templateId,
    address _owner
  ) external onlyMinter {
    require(templateIdToMintCount[_templateId] < templateIdToMintLimit[_templateId]);
    templateIdToMintCount[_templateId] = templateIdToMintCount[_templateId] + 1;

    uint256 newCardId = cards.push(_templateId) - 1;
    _addTokenTo(_owner, newCardId);

    emit Transfer(0, _owner, newCardId);
  }

  function mintCards(
    uint256[] _templateIds,
    address _owner
  ) external onlyMinter {
    uint256 mintCount = _templateIds.length;
    uint256 templateId;

    for (uint256 i = 0; i < mintCount; ++i) {
      templateId = _templateIds[i];

      require(templateIdToMintCount[templateId] < templateIdToMintLimit[templateId]);
      templateIdToMintCount[templateId] = templateIdToMintCount[templateId] + 1;

      uint256 newCardId = cards.push(templateId) - 1;
      cardIdToOwner[newCardId] = _owner;

      emit Transfer(0, _owner, newCardId);
    }

    // Bulk add to ownerToCardCount.
    ownerToCardCount[_owner] = ownerToCardCount[_owner] + mintCount;
  }
}
function setMinter(address _minter) external onlyOwner {
  minter = _minter;
}

/** PUBLIC FUNCTIONS **/