contract TokenManager is Owned, usingProvable {

  address public immutable token;
  uint256 public provableUpdateInterval;
  string public provableQuery;
  byte public provableProofType;
  uint256 public provableGasPrice;
  uint256 public provableGasLimit;
  bool public autoUpdate;
  bytes32 private provableQueryID;

  event LogFundsReceived(address indexed sender, uint256 amount);
  event LogFundsRecovered(address recipient, uint256 amount);
  event LogNewRateReceived(uint256[2] rate, bytes indexed proof, string status);
  event LogNewQuery(uint256 cost, string status);
  event LogProvableSettingsUpdated(uint256 interval, string query, byte proofType, uint256 gasPrice, uint256 gasLimit,
      bool autoUpdate);

  constructor(address _token)
    internal
  {
    token = _token;
  }

  modifier onlyProvable() {
    require(msg.sender == provable_cbAddress(), "Not Provable");
    _;
  }

  modifier onlyCurrentQuery(bytes32 _id) {
    require(provableQueryID == _id, "Bad ID");
    _;
  }

  receive() external payable {
    emit LogFundsReceived(msg.sender, msg.value);
  }

  // TODO: Consider removing gasPrice from here.
  function updateProvableSettings(uint256 _interval, string calldata _query, byte _proofType, uint256 _gasPrice,
      uint256 _gasLimit, bool _autoUpdate)
    onlyOwner
    external
  {
    provableUpdateInterval = _interval;
    provableQuery = _query;

    if (provableProofType != _proofType) {
      provableProofType = _proofType;
      provable_setProof(provableProofType);
    }

    autoUpdate = _autoUpdate;

    updateProvableGasPrice(_gasPrice);

    provableGasLimit = _gasLimit;

    emit LogProvableSettingsUpdated(provableUpdateInterval, provableQuery, provableProofType, provableGasPrice,
        provableGasLimit, autoUpdate);
  }

  function recoverFunds()
    onlyOwner
    external
  {
    uint256 balance = address(this).balance;
    (bool success,) = msg.sender.call{value:balance}("");
    require(success);
    emit LogFundsRecovered(msg.sender, balance);
  }

  function updateExchangeRate()
    external
    payable
    onlyOwner
  {
    if (msg.value > 0)
      emit LogFundsReceived(msg.sender, msg.value);
    doUpdateExchangeRate();
  }

  function updateProvableGasPrice(uint256 _gasPrice)
    onlyOwner
    public
  {
    if (provableGasPrice != _gasPrice) {
      provableGasPrice = _gasPrice;
      provable_setCustomGasPrice(provableGasPrice);
    }

    // TODO: Consider emitting a log.
  }

  function __callback(bytes32 _id, string memory _result, bytes memory _proof)
    override
    public
    onlyProvable
  {
    try this.decimalPriceToRate(_id, _result) returns (uint256[2] memory rate) {
      emit LogNewRateReceived(rate, _proof, "Success");
      VOWToken(token).setUSDRate(rate[0], rate[1]);
      if (autoUpdate)
        doUpdateExchangeRate();
    } catch Error(string memory reason) {
      emit LogNewRateReceived([uint256(0),uint256(0)], _proof, reason);
    }
  }

  function decimalPriceToRate(bytes32 _id, string memory _priceString)
    public
    view
    onlyCurrentQuery(_id)
    returns (uint256[2] memory rate_)
  {
    rate_ = LTokenManager.decimalPriceToRate(_priceString);
  }

  function doUpdateExchangeRate()
    private
  {
    uint256 cost = provable_getPrice("URL", provableGasLimit);
    if (cost > address(this).balance)
      emit LogNewQuery(cost, "Query not sent, requires ETH");
    else {
      provableQueryID = provable_query(provableUpdateInterval, "URL", provableQuery, provableGasLimit);
      emit LogNewQuery(cost, "Query sent, awaiting result...");
    }
  }
}
function updateProvableSettings(uint256 _interval, string calldata _query, byte _proofType, uint256 _gasPrice,
    uint256 _gasLimit, bool _autoUpdate)
  onlyOwner
  external
{
  provableUpdateInterval = _interval;
  provableQuery = _query;

  if (provableProofType != _proofType) {
    provableProofType = _proofType;
    provable_setProof(provableProofType);
  }

  autoUpdate = _autoUpdate;

  updateProvableGasPrice(_gasPrice);

  provableGasLimit = _gasLimit;

  emit LogProvableSettingsUpdated(provableUpdateInterval, provableQuery, provableProofType, provableGasPrice,
      provableGasLimit, autoUpdate);
}

// TODO: Consider removing gasPrice from here.