pragma solidity 0.5.3;



/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor () internal {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
    }

    /**
     * @return the address of the owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner());
        _;
    }

    /**
     * @return true if `msg.sender` is the owner of the contract.
     */
    function isOwner() public view returns (bool) {
        return msg.sender == _owner;
    }

    /**
     * @dev Allows the current owner to relinquish control of the contract.
     * It will not be possible to call the functions with the `onlyOwner`
     * modifier anymore.
     * @notice Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0));
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}


/**
 * @title Secondary
 * @dev A Secondary contract can only be used by its primary account (the one that created it)
 */
contract OwnableSecondary is Ownable {
  address private _primary;

  event PrimaryTransferred(
    address recipient
  );

  /**
   * @dev Sets the primary account to the one that is creating the Secondary contract.
   */
  constructor() internal {
    _primary = msg.sender;
    emit PrimaryTransferred(_primary);
  }

  /**
   * @dev Reverts if called from any account other than the primary or the owner.
   */
   modifier onlyPrimaryOrOwner() {
     require(msg.sender == _primary || msg.sender == owner(), "not the primary user nor the owner");
     _;
   }

   /**
    * @dev Reverts if called from any account other than the primary.
    */
  modifier onlyPrimary() {
    require(msg.sender == _primary, "not the primary user");
    _;
  }

  /**
   * @return the address of the primary.
   */
  function primary() public view returns (address) {
    return _primary;
  }

  /**
   * @dev Transfers contract to a new primary.
   * @param recipient The address of new primary.
   */
  function transferPrimary(address recipient) public onlyOwner {
    require(recipient != address(0), "new primary address is null");
    _primary = recipient;
    emit PrimaryTransferred(_primary);
  }
}






contract ImmutableEternalStorageInterface is OwnableSecondary {
  /********************/
  /** PUBLIC - WRITE **/
  /********************/
  function createUint(bytes32 key, uint value) external;

  function createString(bytes32 key, string calldata value) external;

  function createAddress(bytes32 key, address value) external;

  function createBytes(bytes32 key, bytes calldata value) external;

  function createBytes32(bytes32 key, bytes32 value) external;

  function createBool(bytes32 key, bool value) external;

  function createInt(bytes32 key, int value) external;

  /*******************/
  /** PUBLIC - READ **/
  /*******************/
  function getUint(bytes32 key) external view returns(uint);

  function uintExists(bytes32 key) external view returns(bool);

  function getString(bytes32 key) external view returns(string memory);

  function stringExists(bytes32 key) external view returns(bool);

  function getAddress(bytes32 key) external view returns(address);

  function addressExists(bytes32 key) external view returns(bool);

  function getBytes(bytes32 key) external view returns(bytes memory);

  function bytesExists(bytes32 key) external view returns(bool);

  function getBytes32(bytes32 key) external view returns(bytes32);

  function bytes32Exists(bytes32 key) external view returns(bool);

  function getBool(bytes32 key) external view returns(bool);

  function boolExists(bytes32 key) external view returns(bool);

  function getInt(bytes32 key) external view returns(int);

  function intExists(bytes32 key) external view returns(bool);
}





contract StatementRegisteryInterface is OwnableSecondary {
  /********************/
  /** PUBLIC - WRITE **/
  /********************/
  function recordStatement(string calldata buildingPermitId, uint[] calldata statementDataLayout, bytes calldata statementData) external returns(bytes32);

  /*******************/
  /** PUBLIC - READ **/
  /*******************/
  function statementIdsByBuildingPermit(string calldata id) external view returns(bytes32[] memory);

  function statementExists(bytes32 statementId) public view returns(bool);

  function getStatementString(bytes32 statementId, string memory key) public view returns(string memory);

  function getStatementPcId(bytes32 statementId) external view returns (string memory);

  function getStatementAcquisitionDate(bytes32 statementId) external view returns (string memory);

  function getStatementRecipient(bytes32 statementId) external view returns (string memory);

  function getStatementArchitect(bytes32 statementId) external view returns (string memory);

  function getStatementCityHall(bytes32 statementId) external view returns (string memory);

  function getStatementMaximumHeight(bytes32 statementId) external view returns (string memory);

  function getStatementDestination(bytes32 statementId) external view returns (string memory);

  function getStatementSiteArea(bytes32 statementId) external view returns (string memory);

  function getStatementBuildingArea(bytes32 statementId) external view returns (string memory);

  function getStatementNearImage(bytes32 statementId) external view returns(string memory);

  function getStatementFarImage(bytes32 statementId) external view returns(string memory);

  function getAllStatements() external view returns(bytes32[] memory);
}



contract StatementRegistery is StatementRegisteryInterface {
  ImmutableEternalStorageInterface public dataStore;
  string[] public buildingPermitIds;
  mapping(bytes32 => uint) public statementCountByBuildingPermitHash;

  event NewStatementEvent(string indexed buildingPermitId, bytes32 statementId);

  /********************/
  /** PUBLIC - WRITE **/
  /********************/
  constructor(address immutableDataStore) public {
    require(immutableDataStore != address(0), "null data store");
    dataStore = ImmutableEternalStorageInterface(immutableDataStore);
  }

  /* Only to be called by the Controller contract */
  function recordStatement(
    string calldata buildingPermitId,
    uint[] calldata statementDataLayout,
    bytes calldata statementData
  ) external onlyPrimaryOrOwner returns(bytes32) {
    bytes32 statementId = generateNewStatementId(buildingPermitId);

    assert(!statementExists(statementId));

    recordStatementKeyValues(statementId, statementDataLayout, statementData);

    dataStore.createBool(keccak256(abi.encodePacked(statementId)), true);
    updateStatementCountByBuildingPermit(buildingPermitId);

    emit NewStatementEvent(buildingPermitId, statementId);

    return statementId;
  }

  /*******************/
  /** PUBLIC - READ **/
  /*******************/
  function statementIdsByBuildingPermit(string calldata buildingPermitId) external view returns(bytes32[] memory) {
    uint nbStatements = statementCountByBuildingPermit(buildingPermitId);

    bytes32[] memory res = new bytes32[](nbStatements);

    while(nbStatements > 0) {
      nbStatements--;
      res[nbStatements] = computeStatementId(buildingPermitId,nbStatements);
    }

    return res;
  }

  function statementExists(bytes32 statementId) public view returns(bool) {
    return dataStore.boolExists(keccak256(abi.encodePacked(statementId)));
  }

  function getStatementString(bytes32 statementId, string memory key) public view returns(string memory) {
    return dataStore.getString(keccak256(abi.encodePacked(statementId, key)));
  }

  function getStatementPcId(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "pcId");
  }

  function getStatementAcquisitionDate(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "acquisitionDate");
  }

  function getStatementRecipient(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "recipient");
  }

  function getStatementArchitect(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "architect");
  }

  function getStatementCityHall(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "cityHall");
  }

  function getStatementMaximumHeight(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "maximumHeight");
  }

  function getStatementDestination(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "destination");
  }

  function getStatementSiteArea(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "siteArea");
  }

  function getStatementBuildingArea(bytes32 statementId) external view returns (string memory) {
    return getStatementString(statementId, "buildingArea");
  }

  function getStatementNearImage(bytes32 statementId) external view returns(string memory) {
    return getStatementString(statementId, "nearImage");
  }

  function getStatementFarImage(bytes32 statementId) external view returns(string memory) {
    return getStatementString(statementId, "farImage");
  }

  function getAllStatements() external view returns(bytes32[] memory) {
    uint nbStatements = 0;
    for(uint idx = 0; idx < buildingPermitIds.length; idx++) {
      nbStatements += statementCountByBuildingPermit(buildingPermitIds[idx]);
    }

    bytes32[] memory res = new bytes32[](nbStatements);

    uint statementIdx = 0;
    for(uint idx = 0; idx < buildingPermitIds.length; idx++) {
      nbStatements = statementCountByBuildingPermit(buildingPermitIds[idx]);
      while(nbStatements > 0){
        nbStatements--;
        res[statementIdx] = computeStatementId(buildingPermitIds[idx],nbStatements);
        statementIdx++;
      }
    }

    return res;
  }

  /**********************/
  /** INTERNAL - WRITE **/
  /**********************/
  function updateStatementCountByBuildingPermit(string memory buildingPermitId) internal {
    uint oldCount = statementCountByBuildingPermitHash[keccak256(abi.encodePacked(buildingPermitId))];

    if(oldCount == 0) { // first record for this building permit id
      buildingPermitIds.push(buildingPermitId);
    }

    uint newCount = oldCount + 1;
    assert(newCount > oldCount);
    statementCountByBuildingPermitHash[keccak256(abi.encodePacked(buildingPermitId))] = newCount;
  }

  function recordStatementKeyValues(
    bytes32 statementId,
    uint[] memory statementDataLayout,
    bytes memory statementData) internal {
    string[] memory infos = parseStatementStrings(statementDataLayout, statementData);

    require(infos.length == 11, "the statement key values array length is incorrect");

    /** enforce the rules given in the legal specifications **/
    // required infos
    require(!isEmpty(infos[0]) && !isEmpty(infos[1]), "acquisitionDate and pcId are required");
    require(!isEmpty(infos[9]) && !isEmpty(infos[10]), "missing image");

    // < 2 missing non required info
    uint nbMissingNRIs = (isEmpty(infos[2]) ? 1 : 0) + (isEmpty(infos[3]) ? 1 : 0) + (isEmpty(infos[4]) ? 1 : 0) + (isEmpty(infos[7]) ? 1 : 0);
    require(nbMissingNRIs <= 2, "> 2 missing non required info");

    // mo missing mandatory info or one missing mandatory info and 0 missing non required info
    uint nbMissingMIs = (isEmpty(infos[5]) ? 1 : 0) + (isEmpty(infos[6]) ? 1 : 0) + (isEmpty(infos[8]) ? 1 : 0);
    require(nbMissingMIs == 0 || (nbMissingMIs == 1 && nbMissingNRIs == 0), "missing mandatory info");

    recordStatementString(statementId, "pcId", infos[0]);
    recordStatementString(statementId, "acquisitionDate", infos[1]);
    if(!isEmpty(infos[2])) recordStatementString(statementId, "recipient", infos[2]);
    if(!isEmpty(infos[3])) recordStatementString(statementId, "architect", infos[3]);
    if(!isEmpty(infos[4])) recordStatementString(statementId, "cityHall", infos[4]);
    if(!isEmpty(infos[5])) recordStatementString(statementId, "maximumHeight", infos[5]);
    if(!isEmpty(infos[6])) recordStatementString(statementId, "destination", infos[6]);
    if(!isEmpty(infos[7])) recordStatementString(statementId, "siteArea", infos[7]);
    if(!isEmpty(infos[8])) recordStatementString(statementId, "buildingArea", infos[8]);
    recordStatementString(statementId, "nearImage", infos[9]);
    recordStatementString(statementId, "farImage", infos[10]);
  }

  function recordStatementString(bytes32 statementId, string memory key, string memory value) internal {
    require(!dataStore.stringExists(keccak256(abi.encodePacked(statementId, key))), "Trying to write an existing key-value string pair");

    dataStore.createString(keccak256(abi.encodePacked(statementId,key)), value);
  }

  /*********************/
  /** INTERNAL - READ **/
  /*********************/
  function generateNewStatementId(string memory buildingPermitId) internal view returns (bytes32) {
    uint nbStatements = statementCountByBuildingPermit(buildingPermitId);
    return computeStatementId(buildingPermitId,nbStatements);
  }

  function statementCountByBuildingPermit(string memory buildingPermitId) internal view returns (uint) {
    return statementCountByBuildingPermitHash[keccak256(abi.encodePacked(buildingPermitId))]; // mapping's default is 0
  }

  function computeStatementId(string memory buildingPermitId, uint statementNb) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(buildingPermitId,statementNb));
  }

  function parseStatementStrings(uint[] memory statementDataLayout, bytes memory statementData) internal pure returns(string[] memory) {
    string[] memory res = new string[](statementDataLayout.length);
    uint bytePos = 0;
    uint resLength = res.length;
    for(uint i = 0; i < resLength; i++) {
      bytes memory strBytes = new bytes(statementDataLayout[i]);
      uint strBytesLength = strBytes.length;
      for(uint j = 0; j < strBytesLength; j++) {
        strBytes[j] = statementData[bytePos];
        bytePos++;
      }
      res[i] = string(strBytes);
    }

    return res;
  }

  function isEmpty(string memory s) internal pure returns(bool) {
    return bytes(s).length == 0;
  }
}