library SignLib {

  // METHODS

  /**
   * Requests the signature for a certificate to an entity.
   * Only one request possible (future ones are renewals)
   * @param ed {object} - The data containing the entity mappings
   * @param cd {object} - The data containing the certificate mappings
   * @param certificateId {uint} - The id of the certificate
   * @param entityId {uint} - The id of the entity
   */
  function requestSignatureToEntity(EntityLib.Data storage ed, CertsLib.Data storage cd, uint certificateId, uint entityId) canRequestSignature(ed, cd, certificateId) isValid(ed, entityId) notHasSigningRequest(cd, certificateId, entityId) public {
    CertsLib.CertData storage certificate = cd.certificates[certificateId];
    addMissingSignature(certificate, entityId, 0x1, 0);
    EntitySignatureRequested(certificateId, entityId);
  }

  /**
   * Requests the signature for a certificate to a peer
   * Only one request possible (future ones are renewals)
   * @param cd {object} - The data containing the certificate mappings
   * @param certificateId {uint} - The id of the certificate
   * @param peer {address} - The address of the peer
   */
  function requestSignatureToPeer(EntityLib.Data storage ed, CertsLib.Data storage cd, uint certificateId, address peer) canRequestSignature(ed, cd, certificateId) notHasPeerSignature(cd, certificateId, peer) public {
    CertsLib.CertData storage certificate = cd.certificates[certificateId];
    addMissingPeerSignature(certificate, peer, 0x1, 0);
    PeerSignatureRequested(certificateId, peer);
  }

    /**
    * Entity signs a certificate with pending request
    * @param ed {object} - The data containing the entity mappings
    * @param cd {object} - The data containing the certificate mappings
    * @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(EntityLib.Data storage ed, CertsLib.Data storage cd, uint entityId, uint certificateId, uint expiration, bytes32 _purpose) isValid(ed, entityId) signerBelongsToEntity(ed, entityId) hasPendingSignatureOrIsOwner(ed, cd, certificateId, entityId) public {
    CertsLib.CertData storage certificate = cd.certificates[certificateId];
    bytes32 purpose = (_purpose == 0x0 || _purpose == 0x1) ? bytes32(0x2) : _purpose;
    addMissingSignature(certificate, entityId, purpose, expiration);
    CertificateSignedByEntity(certificateId, entityId, msg.sender);
  }

  /**
   * Peer signs a certificate with pending request
   * @param cd {object} - The data containing the certificate mappings
   * @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(CertsLib.Data storage cd, uint certificateId, uint expiration, bytes32 _purpose) hasPendingPeerSignatureOrIsOwner(cd, certificateId) public {
    CertsLib.CertData storage certificate = cd.certificates[certificateId];
    bytes32 purpose = (_purpose == 0x0 || _purpose == 0x1) ? bytes32(0x2) : _purpose;
    addMissingPeerSignature(certificate, msg.sender, purpose, expiration);
    CertificateSignedByPeer(certificateId, msg.sender);
  }

  // HELPER FUNCTIONS

  /**
   * Add an entity signature to the entity signatures array (if missing) and set the specified status and expiration
   * @param certificate {object} - The certificate to add the peer signature
   * @param entityId {uint} - The id of the entity signing the certificate
   * @param status {uint} - The status/purpose of the signature
   * @param expiration {uint} - The expiration time of the signature (in seconds)
   */
  function addMissingSignature(CertsLib.CertData storage certificate, uint entityId, bytes32 status, uint expiration) private {
    uint[] storage entitiesArr = certificate.entitiesArr;
    for (uint i = 0; i < entitiesArr.length && entitiesArr[i] != entityId; i++) {}
    if (i == entitiesArr.length) {
      entitiesArr.push(entityId);
    }
    certificate.entities[entityId].status = status;
    certificate.entities[entityId].exp = expiration;
  }

  /**
   * Add a peer signature to the signatures array (if missing) and set the specified status and expiration
   * @param certificate {object} - The certificate to add the peer signature
   * @param peer {address} - The address of the peer to add signature
   * @param status {uint} - The status/purpose of the signature
   * @param expiration {uint} - The expiration time of the signature (in seconds)
   */
  function addMissingPeerSignature(CertsLib.CertData storage certificate, address peer, bytes32 status, uint expiration) private {
    address[] storage signaturesArr = certificate.signaturesArr;
    for (uint i = 0; i < signaturesArr.length && signaturesArr[i] != peer; i++) {}
    if (i == signaturesArr.length) {
      signaturesArr.push(peer);
    }
    certificate.signatures[peer].status = status;
    certificate.signatures[peer].exp = expiration;
  }

  // MODIFIERS

  /**
   * Returns True if msg.sender is the owner of the specified certificate or the sender is a confirmed signer of certificate entity. False otherwise.
   * @param cd {object} - The data containing the certificate mappings
   * @param id {uint} - The id of the certificate
   */
  modifier canRequestSignature(EntityLib.Data storage ed, CertsLib.Data storage cd, uint id) {
    require (cd.certificates[id].owner == msg.sender ||
      (cd.certificates[id].entityId > 0 && EntityLib.isValid(ed, cd.certificates[id].entityId) && ed.entities[cd.certificates[id].entityId].signers[msg.sender].status == 0x2)
    );
    _;
  }

  /**
   * Returns True if specified entity is validated or waiting to be renewed, not expired and not closed. False otherwise.
   * @param ed {object} - The data containing the entity mappings
   * @param id {uint} - The id of the entity to check 
   */
  modifier isValid(EntityLib.Data storage ed, uint id) {
    require (EntityLib.isValid(ed, id));
    _;
  }

  /**
   * Returns True if specified certificate has not been validated yet by entity. False otherwise.
   * @param cd {object} - The data containing the certificate mappings
   * @param certificateId {uint} - The id of the certificate to check
   * @param entityId {uint} - The id of the entity to check
   */
  modifier notHasSigningRequest(CertsLib.Data storage cd, uint certificateId, uint entityId) {
    require (cd.certificates[certificateId].entities[entityId].status != 0x1);
    _;    
  }

  /**
   * Returns True if specified certificate has not been signed yet. False otherwise;   
   * @param cd {object} - The data containing the certificate mappings
   * @param certificateId {uint} - The id of the certificate to check
   * @param signerAddress {address} - The id of the certificate to check
   */
  modifier notHasPeerSignature(CertsLib.Data storage cd, uint certificateId, address signerAddress) {    
    require (cd.certificates[certificateId].signatures[signerAddress].status != 0x1);
    _;
  }

  /**
   * True if sender address is the owner of the entity or is a signer registered in entity. False otherwise.
   * @param ed {object} - The data containing the entity mappings
   * @param entityId {uint} - The id of the entity 
   */
  modifier signerBelongsToEntity(EntityLib.Data storage ed, uint entityId) {
    require (entityId > 0 && (bytes(ed.entities[entityId].signers[msg.sender].signerDataHash).length != 0) && (ed.entities[entityId].signers[msg.sender].status == 0x2));
    _;
  }

  /**
   * True if a signature request has been sent to entity or the issuer of the certificate is requested entity itself. False otherwise.
   * @param cd {object} - The data containing the certificate mappings
   * @param certificateId {uint} - The id of the certificate to check
   * @param entityId {uint} - The id of the entity to check
   */
  modifier hasPendingSignatureOrIsOwner(EntityLib.Data storage ed, CertsLib.Data storage cd, uint certificateId, uint entityId) {
    require (cd.certificates[certificateId].entities[entityId].status == 0x1 || cd.certificates[certificateId].entityId == entityId);
    _;
  }

  /**
   * True if a signature is pending for the sender or the sender is the owner. False otherwise.
   * @param cd {object} - The data containing the certificate mappings
   * @param certificateId {uint} - The id of the certificate to check
   */
  modifier hasPendingPeerSignatureOrIsOwner(CertsLib.Data storage cd, uint certificateId) {
    require (cd.certificates[certificateId].signatures[msg.sender].status == 0x1 || cd.certificates[certificateId].owner == msg.sender);
    _;
  }

  // EVENTS
  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);
}
function requestSignatureToEntity(EntityLib.Data storage ed, CertsLib.Data storage cd, uint certificateId, uint entityId) canRequestSignature(ed, cd, certificateId) isValid(ed, entityId) notHasSigningRequest(cd, certificateId, entityId) public {
  CertsLib.CertData storage certificate = cd.certificates[certificateId];
  addMissingSignature(certificate, entityId, 0x1, 0);
  EntitySignatureRequested(certificateId, entityId);
}

/**
 * Requests the signature for a certificate to an entity.
 * Only one request possible (future ones are renewals)
 * @param ed {object} - The data containing the entity mappings
 * @param cd {object} - The data containing the certificate mappings
 * @param certificateId {uint} - The id of the certificate
 * @param entityId {uint} - The id of the entity
 */