contract ESController {

  bytes32 constant TLD_NODE = 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae; // namehash('eth')
  bytes32 constant ETHSIMPLE_NAMEHASH = 0xff60be0907d071946e59cea1ebac55c2e39e886f0101b78c305f67bdc2c4bd73; // namehash('ethsimple.eth')
  bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; // namehash('addr.reverse')

  event commitmentSubmitted(bytes32 _commitment, address _owner, uint _submitted);
  event registrationSubmitted(bytes32 _commitment, address _owner, uint _submitted);

  ESENSFactory public ensFactory;
  ENS public registry;
  ETHRegistrarController public permanentRegistrar; 
  PublicResolver public resolver; 
  BaseRegistrar public baseRegistrar; 

  address payable public registrarOwner; 
  address public registrarOperator; 
  
  uint networkFee = 135; 
  uint gasEstimate = 286191 * 10000000000; 
  // TODO: Connect to gas price oracle to estimate what the server will be paying, 
  // and subtract that from the amount sent to the registrar, 
  // also use that in the price estimator
  
  struct Commitment {
    uint totalSent;
    address owner;
    address sender;
  }
  
  mapping (bytes32 => Commitment) public commitments;

  modifier registrar_owner_only() {
    require(msg.sender == registrarOwner);
    _;
  }

  constructor(uint _network_id, address _operator) public {
    registrarOwner = msg.sender;
    registrarOperator = _operator;

    if (_network_id == 1) {
      ensFactory = ESENSFactory(0x306193c2ab1E659EE9ba4E5f0633B80D29CE33B3);
      permanentRegistrar = ETHRegistrarController(0xF0AD5cAd05e10572EfcEB849f6Ff0c68f9700455);
      baseRegistrar = BaseRegistrar(0xFaC7BEA255a6990f749363002136aF6556b31e04);
    } else if (_network_id == 3) {
      ensFactory = ESENSFactory(0xC8349c6dab9682E45E5B482CC633b50b0e458ba8);
      permanentRegistrar = ETHRegistrarController(0x357DBd063BeA7F0713BF88A3e97B7436B0235979);
      baseRegistrar = BaseRegistrar(0x227Fcb6Ddf14880413EF4f1A3dF2Bbb32bcb29d7);
    } else {
      revert("Provide network id");
    }
    registry = ENS(address(ensFactory.registry()));
    resolver = PublicResolver(address(ensFactory.resolver()));
  }

  function makeCommitment(string memory _name, bytes32 _secret) view public returns(bytes32) {
    return permanentRegistrar.makeCommitment(_name, address(this), _secret);
  }
  
  // Estimate the cost for the user before the user queries commit()
  function registrationCost(string memory _name, uint _duration) view public returns(uint) {
      return permanentRegistrar.rentPrice(_name, _duration) * networkFee + gasEstimate;
  }

  function commit(bytes32 _commitment, address _owner) public payable {
      require(commitments[_commitment].owner == address(0), "Domain registration has already been submitted");
      // TODO: Check if name already exists
      require(msg.value >= gasEstimate, "You must submit Ether to register a domain");
      
      commitments[_commitment] = Commitment(msg.value, _owner, msg.sender);
      permanentRegistrar.commit(_commitment);

      emit commitmentSubmitted(_commitment, _owner, msg.value);
  }
  
  function register (
      string memory _name, 
    //   address _registrarAddress,
      uint _duration, 
      bytes32 _secret,
      bytes32 _namehash,
      address _resolver,
      bytes32 _contentHash
    ) public {

      bytes32 commitmentSha = permanentRegistrar.makeCommitment(_name, address(this), _secret);
      Commitment memory commitment = commitments[commitmentSha];

      // Let the registrar owner or purchaser query this
      require(msg.sender == registrarOwner || msg.sender == commitment.owner || msg.sender == commitment.sender || msg.sender == registrarOperator);

      // Leaving the amount sent to the registrar unchecked, can allow for 'coupons', 
      // need to put in stronger checks to ensure we don't lose money on gas fees
      permanentRegistrar.register.value(commitment.totalSent - gasEstimate)(_name, address(this), _duration, _secret); // User can't register a domain for longer than their payment will allow
      registry.setResolver(_namehash, _resolver);
      resolver.setAddr(_namehash, commitment.owner);
      // if (_contentHash != 0x0000000000000000000000000000000000000000000000000000000000000000) {
      resolver.setContent(_namehash, _contentHash);
      // }
      baseRegistrar.reclaim(uint256(keccak256(bytes(_name))), commitment.owner);
      baseRegistrar.safeTransferFrom(address(this), commitment.owner, uint256(keccak256(bytes(_name)))); // TODO Note: This throws an error if someone tries to register a contract address

      emit registrationSubmitted(commitmentSha, commitment.owner, commitment.totalSent);      

      delete commitments[commitmentSha]; // Recover some gas
  }
  
  function withdraw() public registrar_owner_only {
      registrarOwner.transfer(address(this).balance);
  }
  
  function setNetworkPower(uint fee) public registrar_owner_only {
      networkFee = fee;
  }
  
  function setGasEstimate(uint _gasEstimate) public registrar_owner_only {
      gasEstimate = _gasEstimate;
  }
  
  function transferOwnership(address payable _newOwner) public registrar_owner_only {
      registrarOwner = _newOwner;
  }
  
  function setOperator(address payable _newOperator) public registrar_owner_only {
      registrarOperator = _newOperator;
  }
  
  function removeCommitment(bytes32 _commitment) public registrar_owner_only {
      delete commitments[_commitment]; // Recover some gas
  }
  
  // TODO: Maybe backup domain recovery method, though method atomicity should prevent the contract from ever owning a domain
  
  function() external payable { }
  
  function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4){
    return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
  } 
}
function registrationCost(string memory _name, uint _duration) view public returns(uint) {
    return permanentRegistrar.rentPrice(_name, _duration) * networkFee + gasEstimate;
}

// Estimate the cost for the user before the user queries commit()