contract SoE is
  NFTokenEnumerable,
  NFTokenMetadata,
  Ownable
{
  using SafeMath for uint256;

  mapping (uint256 => uint256) public signatureChangePrice;
  mapping (uint256 => bool) public isImmutable;
  uint256 constant public baseSaleTimeApart = 1 days;
  uint256 constant public maxSupply = 42;
  uint256 constant public initialNumberOfMints = 2;
  uint256 constant public amountInOneSale = 8;
  uint256 public saleStart;
  uint256 public initialPrice; 
  uint256 public immutablePrice;

  event Minted(address indexed receiver, uint256 tokenId);
  event TokenUriSet(uint256 indexed tokenId, string uri);

  /**
   * @dev Contract constructor.
   * @param _name A descriptive name for a collection of NFTs.
   * @param _symbol An abbreviated name for NFTokens.
   */
  constructor(
    string memory _name,
    string memory _symbol,
    uint256 _initialPrice,
    uint256 _immutablePrice
  )
  {
    nftName = _name;
    nftSymbol = _symbol;
    initialPrice = _initialPrice;
    immutablePrice = _immutablePrice;

    _mint(msg.sender, 0);
    setImmutableWithUri(0, "we are gonna make it");
    _mint(msg.sender, 1);
    _setTokenUri(1, "furansu");
    _setSignatureChangePrice(1, initialPrice);
  }

  function setSaleStartTime(uint _saleStart) public onlyOwner {
    saleStart = _saleStart;
  }

  /**
   * i need to withdraw
   */
  function withdraw() public onlyOwner {
    uint balance = address(this).balance;
    payable(msg.sender).transfer(balance);
  }


  /**
   * sale time
   */
  function saleTime(uint256 _tokenId) 
    public
    view 
    returns (uint256 time)
  {
    if (saleStart != 0) {
      time = saleStart + 
        baseSaleTimeApart * 
        (2 ** ((_tokenId - initialNumberOfMints) / amountInOneSale) - 1);
    } else {
      time = 2**256-1;
    }
  }

  /**
   * @dev Mints a new signature, only approved buyer can mint
   * @param _uri the wish.
   */
  function mintSignature(
    uint256 _tokenId,
    string memory _uri
  )
    public
  {
    require(_tokenId.add(1) <= maxSupply, "Purchase would exceed max supply of SoE");
    require(saleTime(_tokenId) <= block.timestamp, "Sale not yet started");

    _mint(msg.sender, _tokenId);
    _setTokenUri(_tokenId, _uri);
    _setSignatureChangePrice(_tokenId, initialPrice);
    emit Minted(msg.sender, _tokenId);
    emit TokenUriSet(_tokenId, _uri);
  }

  /**
   * set token uri by the token owner
   */
  function setTokenUri(
    uint256 _tokenId, 
    string memory _uri
  ) 
    payable
    external 
    canOperate(_tokenId) 
  {
    require(isImmutable[_tokenId] == false, "This token is immutable");

    uint256 oldPrice = signatureChangePrice[_tokenId];
    uint256 newPrice = oldPrice.mul(2);
    if (msg.sender != owner) {
      require(oldPrice <= msg.value, "Ether value sent is not correct");
    }
    
    _setTokenUri(_tokenId, _uri);
    _setSignatureChangePrice(_tokenId, newPrice);
    emit TokenUriSet(_tokenId, _uri);
  }

  function tokenURI(
    uint256 tokenId
  ) 
    public 
    view 
    override 
  returns (string memory) 
  {
    string memory message = NFTokenMetadata.tokenURI(tokenId);
    if (keccak256(bytes(message)) == keccak256(bytes(""))) {
      return "";
    }
    string memory svg = getSvg(message);
    string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "', message, '", "description": "Signatures on Ethereum are mutable, signature-like NFTs generated with owner\'s input", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '"}'))));
    return string(abi.encodePacked('data:application/json;base64,', json));
  }


  function getSvg(string memory message) 
    public 
    view
    returns(string memory)
  {
    string[3] memory parts;
  
    parts[0] = '<svg width="600" height="600" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg"><path d="';
    parts[1] = string(_getPath(message));
    parts[2] = '" stroke-width="3" stroke="black" fill="transparent" /></svg>';

    return string(abi.encodePacked(parts[0], parts[1], parts[2]));
  }

  function _getPath(string memory message) 
    internal 
    view
    returns(bytes memory)
  {
    string[8] memory parts;
    bytes32 hash = _getHash(message);

    uint16[32] memory points;
    uint8 i;
    for (i = 0; i < 32; i++) {
      points[i] = uint16(uint8(hash[i])) % 13 * 25 + 150;
    }

    parts[0] = string(abi.encodePacked("M ", toString(points[0]), " ", toString(points[1]), " "));
    parts[1] = string(abi.encodePacked("C ", toString(points[2]), " ", toString(points[3]), " ", toString(points[4]), " ", toString(points[5]), " ", toString(points[6]), " ", toString(points[7]), " "));

    for (i = 2; i < 8; i++) {
      parts[i] = string(abi.encodePacked("S ", toString(points[i*4]), " ", toString(points[i*4 + 1]), " ", toString(points[i*4 + 2]), " ", toString(points[i*4 + 3]), " "));
    }

    return abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7]);
  }

  function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

    if (value == 0) {
        return "0";
    }
    uint256 temp = value;
    uint256 digits;
    while (temp != 0) {
        digits++;
        temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    while (value != 0) {
        digits -= 1;
        buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
        value /= 10;
    }
    return string(buffer);
  }
  
  function _getHash(string memory message) 
    internal 
    view 
    returns(bytes32)
  {
    return keccak256(abi.encodePacked(message));
  }

  /**
   * set immutable with uri
   */
  function setImmutableWithUri(
    uint256 _tokenId,
    string memory _uri
  ) 
    payable
    public 
    canOperate(_tokenId) 
  {
    if (msg.sender != owner) {
      require(immutablePrice <= msg.value, "Ether value sent is not correct");
    }
    require(isImmutable[_tokenId] == false, "This token is immutable");
    _setTokenUri(_tokenId, _uri);
    isImmutable[_tokenId] = true;
  }

  /**
   * @dev Mints a new NFT.
   * @notice This is an internal function which should be called from user-implemented external
   * mint function. Its purpose is to show and properly initialize data structures when using this
   * implementation.
   * @param _to The address that will own the minted NFT.
   * @param _tokenId of the NFT to be minted by the msg.sender.
   */
  function _mint(
    address _to,
    uint256 _tokenId
  )
    internal
    override(NFToken, NFTokenEnumerable)
    virtual
  {
    NFTokenEnumerable._mint(_to, _tokenId);
  }

  /**
   * @notice Use and override this function with caution. Wrong usage can have serious consequences.
   * @dev Removes a NFT from an address.
   * @param _from Address from wich we want to remove the NFT.
   * @param _tokenId Which NFT we want to remove.
   */
  function _removeNFToken(
    address _from,
    uint256 _tokenId
  )
    internal
    override(NFToken, NFTokenEnumerable)
  {
    NFTokenEnumerable._removeNFToken(_from, _tokenId);
  }

  /**
   * @notice Use and override this function with caution. Wrong usage can have serious consequences.
   * @dev Assigns a new NFT to an address.
   * @param _to Address to wich we want to add the NFT.
   * @param _tokenId Which NFT we want to add.
   */
  function _addNFToken(
    address _to,
    uint256 _tokenId
  )
    internal
    override(NFToken, NFTokenEnumerable)
  {
    NFTokenEnumerable._addNFToken(_to, _tokenId);
  }

  /**
   * @dev Helper function that gets NFT count of owner. This is needed for overriding in enumerable
   * extension to remove double storage(gas optimization) of owner nft count.
   * @param _owner Address for whom to query the count.
   * @return Number of _owner NFTs.
   */
  function _getOwnerNFTCount(
    address _owner
  )
    internal
    override(NFToken, NFTokenEnumerable)
    view
    returns (uint256)
  {
    return NFTokenEnumerable._getOwnerNFTCount(_owner);
  }

  /**
   * set wish change price internally
   */
  function _setSignatureChangePrice(
    uint256 _tokenId,
    uint256 _price
  )
    internal
  {
    signatureChangePrice[_tokenId] = _price;
  }

}
/**
 * SoE 2021
 * @author Anona
 */
function saleTime(uint256 _tokenId) 
    public
    view 
    returns (uint256 time)
  {
    if (saleStart != 0) {
      time = saleStart + 
        baseSaleTimeApart * 
        (2 ** ((_tokenId - initialNumberOfMints) / amountInOneSale) - 1);
    } else {
      time = 2**256-1;
    }
  }
/**
 * sale time
 */