contract NFTInitialSeller is Ownable {
  using Counters for Counters.Counter;
  using SafeMath for uint256;

  enum SaleStep {
    None,
    EarlyBirdSale,
    Airdrop,
    SecondSale,
    SoldOut
  }

  uint16 public constant MAX_NFT_SUPPLY = 1001;

  // Early bird sale prices
  uint256 public constant METAKING_PRICE = 0.5 ether;
  uint256 public constant METAVATARS_PRICE = 0.5 ether;
  uint256 public constant ARCHAEONS_PRICE = 0.3 ether;
  uint256 public constant AEONS_PRICE = 0.3 ether;
  uint256 public constant EOS_PRICE = 0.19 ether;

  // Second sale price
  uint256 public constant SECOND_SALE_PRICE = 0.3 ether;

  uint256 public startTime;

  uint16 public pendingCount = MAX_NFT_SUPPLY - 155;

  bool[1002] public minted;

  IFacelessNFT public facelessNFT;
  IFacelessNFT public oldFacelessNFT;

  uint16[10000] private _pendingIds;

  // First sale is Early Bird Sale
  SaleStep private _currentSale = SaleStep.None;

  address private _dev;

  address[] private _presaleAddresses;

  modifier airdropPeriod() {
    require(
      _currentSale == SaleStep.Airdrop,
      "NFTInitialSeller: Airdrop ended"
    );
    _;
  }

  modifier earlyBirdSalePeriod() {
    require(
      _currentSale == SaleStep.EarlyBirdSale,
      "NFTInitialSeller: Early Bird Sale ended"
    );
    _;
  }

  modifier secondSalePeriod() {
    require(
      _currentSale == SaleStep.SecondSale,
      "NFTInitialSeller: Second Sale ended"
    );
    _;
  }

  modifier periodStarted() {
    require(
      block.timestamp >= startTime,
      "NFTInitialSeller: Period not started"
    );
    _;
  }

  constructor(
    address oldNftAddress,
    address nftAddress,
    address dev,
    address[] memory presaleAddresses
  ) {
    oldFacelessNFT = IFacelessNFT(oldNftAddress);
    facelessNFT = IFacelessNFT(nftAddress);
    _dev = dev;
    _presaleAddresses = presaleAddresses;
  }

  function setCurrentSale(SaleStep _sale) external onlyOwner {
    require(_currentSale != _sale, "NFTInitialSeller: step already set");
    _currentSale = _sale;
  }

  function setStartTime(uint256 _startTime) external onlyOwner {
    require(_startTime > 0, "NFTInitialSeller: invalid _startTime");
    require(_startTime > block.timestamp, "NFTInitialSeller: old start time");
    startTime = _startTime;
  }

  function airdropTransfer(address to, uint16 tokenId)
    external
    airdropPeriod
    onlyOwner
  {
    uint16 nftIndex = _getPendingIndexById(tokenId, 647, 200);
    require(nftIndex >= 647, "NFTInitialSeller: too low index");
    require(nftIndex <= 846, "NFTInitialSeller: too high index");
    require(!minted[tokenId], "NFTInitialSeller: already minted");
    _popPendingAtIndex(nftIndex);
    minted[tokenId] = true;
    facelessNFT.mint(to, tokenId);
  }

  function standardPurchase(uint16 tokenId)
    external
    payable
    earlyBirdSalePeriod
    periodStarted
  {
    uint16 nftIndex = _getPendingIndexById(tokenId, 847, 155);
    require(nftIndex >= 847, "NFTInitialSeller: too low index");
    require(nftIndex <= 1001, "NFTInitialSeller: too high index");
    require(!minted[tokenId], "NFTInitialSeller: already minted");
    require(
      msg.value == _getMintPrice(tokenId),
      "NFTInitialSeller: invalid ether value"
    );
    _popPendingAtIndex(nftIndex);
    minted[tokenId] = true;
    facelessNFT.mint(msg.sender, tokenId);
  }

  /**
   * @dev Mint 'numberOfNfts' new tokens
   */
  function randomPurchase(uint256 numberOfNfts)
    external
    payable
    secondSalePeriod
    periodStarted
  {
    require(pendingCount > 0, "NFTInitialSeller: All minted");
    require(numberOfNfts > 0, "NFTInitialSelle: numberOfNfts cannot be 0");
    require(
      numberOfNfts <= 20,
      "NFTInitialSeller: You may not buy more than 20 NFTs at once"
    );
    require(
      facelessNFT.totalSupply().add(numberOfNfts) <= MAX_NFT_SUPPLY,
      "NFTInitialSeller: sale already ended"
    );
    require(
      SECOND_SALE_PRICE.mul(numberOfNfts) == msg.value,
      "NFTInitialSeller: invalid ether value"
    );

    for (uint i = 0; i < numberOfNfts; i++) {
      _randomMint(msg.sender);
    }
  }

  /**
   * @dev Withdraw total eth balance on the contract to owner
   */
  function withdraw() external onlyOwner {
    uint256 devShares = (address(this).balance * 15) / 100;
    uint256 treasuryShares = address(this).balance - devShares;
    (bool sentTreasury, ) = payable(msg.sender).call{value: treasuryShares}("");
    (bool sentDev, ) = payable(_dev).call{value: devShares}("");
    require(sentTreasury && sentDev, "NFTInitialSeller: Failed to withdraw");
  }

  function getPendingAtIndex(uint16 _index) public view returns (uint16) {
    return _pendingIds[_index] + _index;
  }

  function _getPendingIndexById(
    uint16 tokenId,
    uint16 startIndex,
    uint16 totalCount
  ) internal view returns (uint16) {
    for (uint16 i = 0; i < totalCount; i++) {
      uint16 pendingTokenId = getPendingAtIndex(i + startIndex);
      if (pendingTokenId == tokenId) {
        return i + startIndex;
      }
    }
    revert("NFTInitialSeller: invalid token id(pending index)");
  }

  function _getMintPrice(uint16 tokenId) internal pure returns (uint256) {
    require(tokenId >= 847, "NFTInitialSeller: low token id");
    if (tokenId <= 848) return METAKING_PRICE;
    if (tokenId <= 851) return METAVATARS_PRICE;
    if (tokenId <= 862) return ARCHAEONS_PRICE;
    if (tokenId <= 901) return AEONS_PRICE;
    if (tokenId <= 1001) return EOS_PRICE;
    revert("NFTInitialSeller: invalid token id(mint price)");
  }

  function _popPendingAtIndex(uint16 _index) internal returns (uint16) {
    uint16 tokenId = getPendingAtIndex(_index);
    if (_index != pendingCount) {
      uint16 lastPendingId = getPendingAtIndex(pendingCount);
      _pendingIds[_index] = lastPendingId - _index;
    }
    pendingCount--;
    return tokenId;
  }

  function _randomMint(address _to) internal {
    uint16 index = uint16((_getRandom() % pendingCount) + 1);
    uint256 tokenId = _popPendingAtIndex(index);
    minted[tokenId] = true;
    facelessNFT.mint(_to, tokenId);
  }

  function migrate() external onlyOwner {
    for (uint16 idx = 0; idx < _presaleAddresses.length; idx++) {
      address minter = _presaleAddresses[idx];
      for (uint16 i = 847; i <= 1001; i++) {
        if (minter == oldFacelessNFT.ownerOf(i)) {
          // oldFacelessNFT.transferFrom(minter, address(0), i);
          facelessNFT.mint(minter, i);
          minted[i] = true;
        }
      }
    }
  }

  function _getRandom() internal view returns (uint256) {
    return
      uint256(
        keccak256(
          abi.encodePacked(block.difficulty, block.timestamp, pendingCount)
        )
      );
  }
}
/**
 * Meta Kings - 4
 * Metavatars - 9
 * Archaeons - 19
 * Aeons - 69
 * Eos - 900
 *
 * Total number of NFTs: 1001
 */
function withdraw() external onlyOwner {
  uint256 devShares = (address(this).balance * 15) / 100;
  uint256 treasuryShares = address(this).balance - devShares;
  (bool sentTreasury, ) = payable(msg.sender).call{value: treasuryShares}("");
  (bool sentDev, ) = payable(_dev).call{value: devShares}("");
  require(sentTreasury && sentDev, "NFTInitialSeller: Failed to withdraw");
}

/**
 * @dev Withdraw total eth balance on the contract to owner
 */