contract Ethertify is usingOraclize {
  EntityLib.Data ed;
  CertsLib.Data cd;

  /** 
   * Creates a contract
   */
  function Ethertify() public {
    ed.nEntities = 0;
    cd.nCerts = 0;
  }

  // MODIFIERS

 /**
   * True if the method is executed by oraclize. False otherwise.
   */
  modifier isOraclize() {
    require (msg.sender == oraclize_cbAddress());
    _;
  }

  // ENTITY METHODS

  /**
   * Creates a new entity
   * @param entityHash {string} - The ipfs multihash address of the entity information in json format
   * @param urlHash {bytes32} - The sha256 hash of the URL of the entityç
   * @param expirationDate {uint} - The expiration date of the current entity
   * @param renewalPeriod {uint} - The time period which will be added to the current date or expiration date when a renewal is requested
   * @return {uint} The id of the created entity
   */
  function createSigningEntity(string entityHash, bytes32 urlHash, uint expirationDate, uint renewalPeriod) public returns (uint) {
    uint entityId = ++ed.nEntities;
    EntityLib.create(ed, entityId, entityHash, urlHash, expirationDate, renewalPeriod);      
    return entityId;
  }

  /**
   * Sets a new expiration date for the entity. It will trigger an entity validation through the oracle, so it must be paid
   * @param entityId {uint} - The id of the entity
   * @param expirationDate {uint} - The new expiration date of the entity
   * @param url {string} - The URL of the entity (for validation through the oracle)
   * @param oraclizeGas {uint} - The maximum gas to use during the oraclize callback execution. Will default to 90000 if not provided.
   * @param oraclizeGasPrice {uint} - The gas price to use during the oraclize callback invocation.
   * @return {bytes32} - The id of the oraclize query
   */
  function setExpiration(uint entityId, uint expirationDate, string url, uint oraclizeGas, uint oraclizeGasPrice) public payable returns (bytes32) {
    EntityLib.setExpiration(ed, entityId, expirationDate);
    return validateSigningEntity(entityId, url, oraclizeGas, oraclizeGasPrice);
  }
  
  /**
   * Sets a new renewal interval
   * @param entityId {uint} - The id of the entity
   * @param renewalPeriod {uint} - The new renewal interval (in seconds)
   */
  function setRenewalPeriod (uint entityId, uint renewalPeriod) public {
    EntityLib.setRenewalPeriod(ed, entityId, renewalPeriod);
  }


  /**
   * Requests the validation of a signing entity
   * @param entityId {uint} - The id of the entity to validate
   * @param url {string} - The URL of the entity
   * @param oraclizeGas {uint} - The maximum gas to use during the oraclize callback execution. Will default to 90000 if not provided.
   * @param oraclizeGasPrice {uint} - The gas price to use during the oraclize callback invocation.
   * @return {bytes32} - The id of the oraclize query
   */
  function validateSigningEntity(uint entityId, string url, uint oraclizeGas, uint oraclizeGasPrice) public payable returns (bytes32) {
    uint maxGas = oraclizeGas == 0 ? 88000 : oraclizeGas; // 67000 gas from the process validation callback + 21000 for the transfer
    
    if (EntityLib.canValidateSigningEntity(ed, entityId, url)) {
      oraclize_setCustomGasPrice(oraclizeGasPrice);
      uint queryCost = oraclize_getPrice("URL", maxGas);
      if (queryCost > msg.value) {
        OraclizeNotEnoughFunds(entityId, queryCost);
        return 0;
      }

      string memory query = strConcat("html(", url, ").xpath(/html/head/meta[@name='ethertify-entity']/@content)");
      bytes32 queryId = oraclize_query("URL", query, maxGas);
      ed.entityIds[queryId] = entityId;
      EntityLib.setOraclizeQueryId(ed, entityId, queryId);
      return queryId;
    }
  }

  /**
   * Updates entity data
   * @param entityId {uint} - The id of the entity
   * @param entityHash {string} - The ipfs multihash address of the entity information in json format
   * @param urlHash {bytes32} - The sha256 hash of the URL of the entity
   * @param oraclizeGas {uint} - The maximum gas to use during the oraclize callback execution. Will default to 90000 if not provided.
   * @param oraclizeGasPrice {uint} - The gas price to use during the oraclize callback invocation.
   * @param url {string} - The url used during the validation (after setting the data)
   */
  function updateEntityData(uint entityId, string entityHash, bytes32 urlHash, string url, uint oraclizeGas, uint oraclizeGasPrice) public payable {
    EntityLib.updateEntityData(ed, entityId, entityHash, urlHash);
    validateSigningEntity(entityId, url, oraclizeGas, oraclizeGasPrice);
  }

  /**
   * Update the signer data in the requestes entities
   * @param entityIds {array} - The ids of the entities to update
   * @param signerDataHash {string} - The ipfs multihash of the new signer data
   */
  function updateSignerData(uint[] entityIds, string signerDataHash) public {
    EntityLib.updateSignerData(ed, entityIds, signerDataHash);
  }

  /**
   * Accepts a new signer data update in the entity
   * @param entityId {uint} - The id of the entity
   * @param signerAddress {address} - The address of the signer update to be accepted
   * @param signerDataHash {uint} - The IPFS multihash address of signer information in json format to be accepted
   */
  function acceptSignerUpdate(uint entityId, address signerAddress, string signerDataHash) public {
    EntityLib.acceptSignerUpdate(ed, entityId, signerAddress, signerDataHash);
  }

  /**
   * Requests the renewal of a signing entity
   * @param entityId {uint} - The id of the entity to validate
   * @param url {string} - The URL of the entity
   * @param oraclizeGas {uint} - The maximum gas to use during the oraclize callback execution. Will default to 90000 if not provided.
   * @param oraclizeGasPrice {uint} - The gas price to use during the oraclize callback invocation.
   * @return {bytes32} - The id of the oraclize query
   */
  function requestRenewal(uint entityId, string url, uint oraclizeGas, uint oraclizeGasPrice) public payable returns (bytes32) {
    if (EntityLib.canRenew(ed, entityId, url)) {
      ed.entities[entityId].status = 4;
      return validateSigningEntity(entityId, url, oraclizeGas, oraclizeGasPrice); 
    }
  }

  /**
   * Close an entity. This status will not allow further operations on the entity.
   * @param entityId {uint} - The id of the entity
   */
  function closeEntity(uint entityId) public {
    EntityLib.closeEntity(ed, entityId);
  }
  
  /**
   * Executes automatically when oraclize query is finished
   * @param queryId {bytes32} - The id of the oraclize query (returned by the call to oraclize_query method)
   * @param result {string} - The result of the query
   */
  function __callback(bytes32 queryId, string result) isOraclize() public {
    EntityLib.processValidation(ed, queryId, result);
  }

  /**
   * Registers a new signer in an entity
   * @param entityId {uint} - The id of the entity
   * @param signerAddress {address} - The address of the signer to be registered
   * @param signerDataHash {uint} - The IPFS multihash address of signer information in json format
   */
  function registerSigner(uint entityId, address signerAddress, string signerDataHash) public {
    EntityLib.registerSigner(ed, entityId, signerAddress, signerDataHash);
  }

  /**
   * Removes a signer from an entity
   * @param entityId {uint} - The id of the entity
   * @param signerAddress {address} - The address of the signer to be removed
   */
  function removeSigner(uint entityId, address signerAddress) public {
    EntityLib.removeSigner(ed, entityId, signerAddress);
  }

  /**
   * Leave the specified entity (remove signer if found)
   * @param entityId {uint} - The id of the entity
   */
  function leaveEntity(uint entityId) public {
    EntityLib.leaveEntity(ed, entityId);
  }

  /**
   * Confirms signer registration
   * @param entityId {uint} - The id of the entity
   * @param signerDataHash {string} - The ipfs data hash of the signer to confirm
   */
  function confirmSignerRegistration(uint entityId, string signerDataHash) public {
    EntityLib.confirmSignerRegistration(ed, entityId, signerDataHash);
  }

  // CERTIFICATE METHODS

  /**
   * Creates a new POE certificate
   * @param dataHash {bytes32} - The hash of the certified data
   * @param certHash {bytes32} - The sha256 hash of the json certificate
   * @param ipfsDataHash {string} - The ipfs multihash address of the data (0x00 means unkwon)
   * @param ipfsCertHash {string} - The ipfs multihash address of the certificate in json format (0x00 means unkwon)
   * @return The id of the created certificate     
   */
  function createPOECertificate(bytes32 dataHash, bytes32 certHash, string ipfsDataHash, string ipfsCertHash) public returns (uint) {
    return CertsLib.createPOECertificate(cd, dataHash, certHash, ipfsDataHash, ipfsCertHash);
  }
  
  /**
   * Creates a new certificate (with known owner)
   * @param dataHash {bytes32} - The hash of the certified data
   * @param certHash {bytes32} - The sha256 hash of the json certificate
   * @param ipfsDataHash {string} - The ipfs multihash address of the data (0x00 means unkwon)
   * @param ipfsCertHash {string} - The ipfs multihash address of the certificate in json format (0x00 means unkwon)
   * @param entityId {uint} - The entity id which issues the certificate (0 if not issued by an entity)
   * @return {uint} The id of the created certificate     
   */
  function createCertificate(bytes32 dataHash, bytes32 certHash, string ipfsDataHash, string ipfsCertHash, uint entityId) public returns (uint) {
    return CertsLib.createCertificate(cd, ed, dataHash, certHash, ipfsDataHash, ipfsCertHash, entityId);
  }

  /**
   * Request transfering the ownership of a certificate.
   * The owner can be a peer or an entity (never both), so only one of newOwner or newEntity must be different than 0.
   * If the specified certificateId belongs to an entity, the msg.sender must be a valid signer for the entity. Otherwise the msg.sender must be the current owner.
   * @param certificateId {uint} - The id of the certificate to transfer
   * @param newOwner {address} - The address of the new owner
   */
  function requestCertificateTransferToPeer(uint certificateId, address newOwner) public {
    return CertsLib.requestCertificateTransferToPeer(cd, ed, certificateId, newOwner);
  }

  /**
   * Request transfering the ownership of a certificate.
   * The owner can be a peer or an entity (never both), so only one of newOwner or newEntity must be different than 0.
   * If the specified certificateId belongs to an entity, the msg.sender must be a valid signer for the entity. Otherwise the msg.sender must be the current owner.
   * @param certificateId {uint} - The id of the certificate to transfer
   * @param newEntityId {uint} - The id of the new entity
   */
  function requestCertificateTransferToEntity(uint certificateId, uint newEntityId) public {
    return CertsLib.requestCertificateTransferToEntity(cd, ed, certificateId, newEntityId);
  }

  /**
   * Accept the certificate transfer
   * @param certificateId {uint} - The id of the certificate to transfer
   */
  function acceptCertificateTransfer(uint certificateId) public {
    return CertsLib.acceptCertificateTransfer(cd, ed, certificateId);
  }

  /**
   * Cancel the certificate transfer
   * @param certificateId {uint} - The id of the certificate to transfer
   */
  function cancelCertificateTransfer(uint certificateId) public {
    return CertsLib.cancelCertificateTransfer(cd, ed, certificateId);
  }

  /**
   * Updates ipfs multihashes of a particular certificate
   * @param certId {uint} - The id of the certificate
   * @param ipfsDataHash {string} - The ipfs multihash address of the data (0x00 means unkwon)
   * @param ipfsCertHash {string} - The ipfs multihash address of the certificate in json format (0x00 means unkwon)
   */
  function setIPFSData(uint certId, string ipfsDataHash, string ipfsCertHash) public {
    CertsLib.setIPFSData(cd, certId, ipfsDataHash, ipfsCertHash);
  }
  
  // SIGNATURE METHODS

  /**
   * Requests the signature for a certificate to an entity
   * @param certificateId {uint} - The id of the certificate
   * @param entityId {uint} - The id of the entity
   */
  function requestSignatureToEntity(uint certificateId, uint entityId) public {
    SignLib.requestSignatureToEntity(ed, cd, certificateId, entityId);
  }

  /**
   * Requests the signature for a certificate to a peer
   * @param certificateId {uint} - The id of the certificate
   * @param peer {address} - The address of the peer
   */
  function requestSignatureToPeer(uint certificateId, address peer) public {
    SignLib.requestSignatureToPeer(ed, cd, certificateId, peer);
  }

  /**
   * Entity signs a certificate with pending request
   * @param entityId {uint} - The id of the entity
   * @param certificateId {uint} - The id of the certificate
   * @param expiration {uint} - The expiration time of the signature (in seconds)
   * @param purpose {bytes32} - The sha-256 hash of the purpose data
   */
  function signCertificateAsEntity(uint entityId, uint certificateId, uint expiration, bytes32 purpose) public {
    SignLib.signCertificateAsEntity(ed, cd, entityId, certificateId, expiration, purpose);
  }

  /**
   * Peer signs a certificate with pending request
   * @param certificateId {uint} - The id of the certificate
   * @param expiration {uint} - The expiration time of the signature (in seconds)
   * @param purpose {bytes32} - The sha-256 hash of the purpose data
   */
  function signCertificateAsPeer(uint certificateId, uint expiration, bytes32 purpose) public {
    SignLib.signCertificateAsPeer(cd, certificateId, expiration, purpose);
  }

  // CUSTOM GETTERS
  /**
   * Get default info from internal contract
   */
  function internalState() constant public returns (uint numEntities, uint numCertificates) {
    return (
      ed.nEntities,
      cd.nCerts
    );
  }

  /**
   * Gets the entity data by id
   * @param entityId {uint} - The id of the entity
   * @return {object} The data of a signing entity
   */
  function getSigningEntityInfo(uint entityId) constant public returns (address owner, string dataHash, uint status, bytes32 urlHash, uint expiration, uint renewalPeriod, uint numSigners) {
    return (
      ed.entities[entityId].owner,
      ed.entities[entityId].dataHash,
      ed.entities[entityId].status,
      ed.entities[entityId].urlHash,
      ed.entities[entityId].expiration,
      ed.entities[entityId].renewalPeriod,
      ed.entities[entityId].signersArr.length
    );
  }

  /**
  * Get the last oraclize query Id of the specified entity
  * @param entityId {uint} - The id of the entity
  * @return {bytes32} The last oraclize query id of the entity
  */
  function getOraclizeQuery(uint entityId) constant public returns (bytes32 oraclizeQueryId) {
    return ed.entities[entityId].oraclizeQueryId;
  }

  /**
   * Gets the signers data from an entity (by address or index)
   * @param entityId {uint} - The id of the entity
   * @param signerAddress {address} - The address of the signer (if known)
   * @param index {uint} - The index of the signer
   * @return {object} The signer details
   */  
  function getSignerData(uint entityId, address signerAddress, uint index) constant public returns (address signer, uint status, string ipfsMultiHash) {
    uint s = 0;
    string memory h = "";
    
    if (signerAddress != 0) {      
      s = ed.entities[entityId].signers[signerAddress].status;
      h = ed.entities[entityId].signers[signerAddress].signerDataHash;
    } else if (signerAddress == 0 && index < ed.entities[entityId].signersArr.length) {
      signerAddress = ed.entities[entityId].signersArr[index];
      s = ed.entities[entityId].signers[signerAddress].status;
      h = ed.entities[entityId].signers[signerAddress].signerDataHash;
    }

    return (signerAddress, s, h);
  }

  /**
   * Gets the certificate data by id
   * @param certificateId {uint} - The id of the certificate
   * @return {object} The data of a certificate
   */
  function getCertificateInfo(uint certificateId) constant public returns (address owner, uint entityId, bytes32 certHash, string ipfsCertHash, bytes32 dataHash, string ipfsDataHash, uint numEntitySignatures, uint numPeerSignatures, address newOwnerTransferRequest, uint newEntityTransferRequest) {
    CertsLib.CertData storage cert = cd.certificates[certificateId];
    CertsLib.TransferData storage req = cd.transferRequests[certificateId];
    return (
      cert.owner,
      cert.entityId,
      cert.certHash,
      cert.ipfsCertHash,
      cert.dataHash,
      cert.ipfsDataHash,
      cert.entitiesArr.length,
      cert.signaturesArr.length,
      req.newOwner,
      req.newEntityId
    );
  }

  /**
   * Gets the entity signature info from a certificate and signing entity
   * @param certificateId {uint} - The id of the certificate
   * @param entityId {uint} - The id of the entity
   * @param entityIndex {uint} - The index of the entity in the array
   * @return {uint, uint} The status/purpose and expiration date
   */
  function getEntitySignatureInfoFromCertificate(uint certificateId, uint entityId, uint entityIndex) constant public returns (uint id, bytes32 status, uint expiration) {
    bytes32 s = 0x0;
    uint e = 0;
    if (entityId != 0 ) {
      s = cd.certificates[certificateId].entities[entityId].status;
      e = cd.certificates[certificateId].entities[entityId].exp;
    } else if (entityId == 0) {
      entityId = cd.certificates[certificateId].entitiesArr[entityIndex];
      s = cd.certificates[certificateId].entities[entityId].status;
      e = cd.certificates[certificateId].entities[entityId].exp;
    } else {
      entityId = 0;
    }   
    return (entityId, s, e);
  }

    /**
   * Gets the peer signature info from a certificate and signing entity
   * @param certificateId {uint} - The id of the certificate
   * @param peerAddress {address} - If the address is supplied the info is retrieved from the peer
   * @param peerIndex {uint} - The index of the peer to retrieve data from
   * @return {uint, uint} The status/purpose and expiration date
   */
  function getPeerSignatureInfoFromCertificate(uint certificateId, address peerAddress, uint peerIndex) constant public returns (address addr, bytes32 status, uint expiration) {
    bytes32 s = 0x0;
    uint e = 0;
    if (peerAddress != 0) {
      s = cd.certificates[certificateId].signatures[peerAddress].status;
      e = cd.certificates[certificateId].signatures[peerAddress].exp;
    } else if (peerAddress == 0) {
      peerAddress = cd.certificates[certificateId].signaturesArr[peerIndex];
      s = cd.certificates[certificateId].signatures[peerAddress].status;
      e = cd.certificates[certificateId].signatures[peerAddress].exp;
    }
    return (peerAddress, s, e);
  }

  // EVENTS  
  event EntityCreated(uint indexed entityId);
  event EntityValidated(uint indexed entityId);
  event EntityDataUpdated(uint indexed entityId);
  event EntityInvalid(uint indexed entityId);
  event SignerAdded(uint indexed entityId, address indexed signerAddress);
  event SignerDataUpdated(uint[] entities, address indexed signerAddress);
  event SignerUpdateAccepted(uint indexed entityId, address indexed signerAddress);
  event SignerRemoved(uint indexed entityId, address signerAddress);
  event EntityClosed(uint indexed entityId);
  event SignerConfirmed(uint indexed entityId, address signerAddress);
  event EntityExpirationSet(uint indexed entityId);
  event EntityRenewalSet(uint indexed entityId);

  event POECertificate(uint indexed certificateId);
  event Certificate(uint indexed certificateId);
  event CertificateTransferRequestedToPeer(uint indexed certificateId, address newOwner);
  event CertificateTransferRequestedToEntity(uint indexed certificateId, uint newEntityId);
  event CertificateTransferAccepted(uint indexed certificateId, address newOwner, uint newEntityId);
  event CertificateTransferCancelled(uint indexed certificateId);

  event EntitySignatureRequested(uint indexed certificateId, uint indexed entityId);
  event PeerSignatureRequested(uint indexed certificateId, address indexed signerAddress);
  event CertificateSignedByEntity(uint indexed certificateId, uint indexed entityId, address indexed signerAddress);
  event CertificateSignedByPeer(uint indexed certificateId, address indexed signerAddress);

  event OraclizeNotEnoughFunds(uint indexed entityId, uint queryCost);
 }
function Ethertify() public {
  ed.nEntities = 0;
  cd.nCerts = 0;
}

/** 
 * Creates a contract
 */