// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract Contracts {

  mapping(bytes32=>bool) private ids; // contract id, equal to hash of bid
  mapping(bytes32=>uint) private durations; // contract duration in blocks
  mapping(bytes32=>uint) private sizes; // storage size in bytes
  mapping(bytes32=>bytes32) private contentHashes; // hash of data to be stored
  mapping(bytes32=>uint) private proofPeriods; // period between proofs
  mapping(bytes32=>uint) private proofTimeouts; // timeout for proof submission
  mapping(bytes32=>uint) private prices; // price in coins
  mapping(bytes32=>address) private hosts; // host that provides storage

  function _duration(bytes32 id) internal view returns (uint) {
    return durations[id];
  }

  function _size(bytes32 id) internal view returns (uint) {
    return sizes[id];
  }

  function _contentHash(bytes32 id) internal view returns (bytes32) {
    return contentHashes[id];
  }

  function _proofPeriod(bytes32 id) internal view returns (uint) {
    return proofPeriods[id];
  }

  function _proofTimeout(bytes32 id) internal view returns (uint) {
    return proofTimeouts[id];
  }

  function _price(bytes32 id) internal view returns (uint) {
    return prices[id];
  }

  function _host(bytes32 id) internal view returns (address) {
    return hosts[id];
  }

  function _newContract(
    uint duration,
    uint size,
    bytes32 contentHash,
    uint proofPeriod,
    uint proofTimeout,
    bytes32 nonce,
    uint price,
    address host,
    uint bidExpiry,
    bytes memory requestSignature,
    bytes memory bidSignature
  )
    internal
    returns (bytes32 id)
  {
    bytes32 requestHash = _hashRequest(
      duration,
      size,
      contentHash,
      proofPeriod,
      proofTimeout,
      nonce
    );
    bytes32 bidHash = _hashBid(requestHash, bidExpiry, price);
    _checkSignature(requestSignature, requestHash, msg.sender);
    _checkSignature(bidSignature, bidHash, host);
    _checkBidExpiry(bidExpiry);
    _checkId(bidHash);
    id = bidHash;
    ids[id] = true;
    durations[id] = duration;
    sizes[id] = size;
    contentHashes[id] = contentHash;
    proofPeriods[id] = proofPeriod;
    proofTimeouts[id] = proofTimeout;
    prices[id] = price;
    hosts[id] = host;
  }

  // Creates hash for a storage request that can be used to check its signature.
  function _hashRequest(
    uint duration,
    uint size,
    bytes32 hash,
    uint proofPeriod,
    uint proofTimeout,
    bytes32 nonce
  )
    private pure
    returns (bytes32)
  {
    return keccak256(abi.encodePacked(
      "[dagger.request.v1]",
      duration,
      size,
      hash,
      proofPeriod,
      proofTimeout,
      nonce
    ));
  }

  // Creates hash for a storage bid that can be used to check its signature.
  function _hashBid(bytes32 requestHash, uint expiry, uint price)
    private pure
    returns (bytes32)
  {
    return keccak256(abi.encodePacked(
      "[dagger.bid.v1]",
      requestHash,
      expiry,
      price
    ));
  }

  // Checks a signature for a storage request or bid, given its hash.
  function _checkSignature(bytes memory signature, bytes32 hash, address signer)
    private pure
  {
    bytes32 messageHash = ECDSA.toEthSignedMessageHash(hash);
    address recovered = ECDSA.recover(messageHash, signature);
    require(recovered == signer, "Invalid signature");
  }

  function _checkBidExpiry(uint expiry) private view {
    require(expiry > block.timestamp, "Bid expired");
  }

  function _checkId(bytes32 id) private view {
    require(
      !ids[id],
      "A contract with this id already exists"
    );
  }
}