"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Certificate = void 0;
const ethers_1 = require("ethers");
const events_1 = require("../utils/events");
const CertificateUtils_1 = require("./CertificateUtils");
const CertificationRequest_1 = require("./CertificationRequest");
const PreciseProofUtils_1 = require("../utils/PreciseProofUtils");
class Certificate {
    constructor(id, blockchainProperties) {
        this.id = id;
        this.blockchainProperties = blockchainProperties;
        this.initialized = false;
    }
    static create(to, value, generationStartTime, generationEndTime, deviceId, blockchainProperties, metadata) {
        return __awaiter(this, void 0, void 0, function* () {
            if (value.gt(CertificationRequest_1.MAX_ENERGY_PER_CERTIFICATE)) {
                throw new Error(`Too much energy requested. Requested: ${value}, Max: ${CertificationRequest_1.MAX_ENERGY_PER_CERTIFICATE}`);
            }
            const { issuer } = blockchainProperties;
            const issuerWithSigner = issuer.connect(blockchainProperties.activeUser);
            const data = (0, CertificateUtils_1.encodeData)({
                generationStartTime,
                generationEndTime,
                deviceId,
                metadata: metadata !== null && metadata !== void 0 ? metadata : ''
            });
            const properChecksumToAddress = ethers_1.ethers.utils.getAddress(to);
            return yield issuerWithSigner.issue(properChecksumToAddress, value, data);
        });
    }
    static fromTxHash(txHash, blockchainProperties) {
        return __awaiter(this, void 0, void 0, function* () {
            const tx = yield blockchainProperties.web3.getTransactionReceipt(txHash);
            if (!tx || !tx.blockNumber) {
                throw new Error(`No certificate was issued in transaction ${txHash}`);
            }
            let result;
            for (const log of tx.logs) {
                try {
                    result = blockchainProperties.issuer.interface.decodeEventLog('CertificationRequestApproved', log.data, log.topics);
                }
                catch (e) {
                    continue;
                }
                try {
                    result = blockchainProperties.issuer.interface.decodeEventLog('PrivateCertificationRequestApproved', log.data, log.topics);
                }
                catch (e) {
                    continue;
                }
            }
            const newCertificate = new Certificate(result._id.toNumber(), blockchainProperties);
            newCertificate.creationTransactionHash = txHash;
            return newCertificate.sync();
        });
    }
    static createPrivate(to, value, generationStartTime, generationEndTime, deviceId, blockchainProperties, metadata) {
        return __awaiter(this, void 0, void 0, function* () {
            if (value.gt(CertificationRequest_1.MAX_ENERGY_PER_CERTIFICATE)) {
                throw new Error(`Too much energy requested. Requested: ${value}, Max: ${CertificationRequest_1.MAX_ENERGY_PER_CERTIFICATE}`);
            }
            const { privateIssuer } = blockchainProperties;
            const privateIssuerWithSigner = privateIssuer.connect(blockchainProperties.activeUser);
            const data = (0, CertificateUtils_1.encodeData)({
                generationStartTime,
                generationEndTime,
                deviceId,
                metadata: metadata !== null && metadata !== void 0 ? metadata : ''
            });
            const properChecksumToAddress = ethers_1.ethers.utils.getAddress(to);
            const ownershipCommitment = {
                [properChecksumToAddress]: value.toString()
            };
            const commitmentProof = PreciseProofUtils_1.PreciseProofUtils.generateProofs(ownershipCommitment);
            const tx = yield privateIssuerWithSigner.issuePrivate(properChecksumToAddress, commitmentProof.rootHash, data);
            return {
                proof: commitmentProof,
                tx
            };
        });
    }
    sync() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.id === null) {
                return this;
            }
            const { registry } = this.blockchainProperties;
            const issuanceTransaction = this.creationTransactionHash
                ? yield registry.provider.getTransactionReceipt(this.creationTransactionHash)
                : yield this.getIssuanceTransaction();
            const certOnChain = yield registry.getCertificate(this.id);
            const decodedData = (0, CertificateUtils_1.decodeData)(certOnChain.data);
            this.generationStartTime = decodedData.generationStartTime;
            this.generationEndTime = decodedData.generationEndTime;
            this.deviceId = decodedData.deviceId;
            this.metadata = decodedData.metadata;
            this.issuer = certOnChain.issuer;
            const creationBlock = yield registry.provider.getBlock(issuanceTransaction.blockNumber);
            this.creationTime = Number(creationBlock.timestamp);
            this.creationTransactionHash = issuanceTransaction.transactionHash;
            this.owners = yield (0, CertificateUtils_1.calculateOwnership)(this.id, this.blockchainProperties);
            this.claimers = yield (0, CertificateUtils_1.calculateClaims)(this.id, this.blockchainProperties);
            this.initialized = true;
            return this;
        });
    }
    claim(claimData, amount, to, from) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            const { activeUser, registry } = this.blockchainProperties;
            const registryWithSigner = registry.connect(activeUser);
            const activeUserAddress = yield activeUser.getAddress();
            const claimAddress = to !== null && to !== void 0 ? to : activeUserAddress;
            const ownedVolume = ethers_1.BigNumber.from((_a = this.owners[claimAddress]) !== null && _a !== void 0 ? _a : 0);
            if (ownedVolume.eq(0) && !amount) {
                throw new Error(`claim(): ${claimAddress} does not own a share in the certificate.`);
            }
            const fromAddress = from !== null && from !== void 0 ? from : activeUserAddress;
            const encodedClaimData = (0, CertificateUtils_1.encodeClaimData)(claimData);
            return registryWithSigner.safeTransferAndClaimFrom(fromAddress, claimAddress, this.id, amount !== null && amount !== void 0 ? amount : ownedVolume, encodedClaimData, // TO-DO: Check the difference between data and claimData
            encodedClaimData);
        });
    }
    transfer(to, amount, from) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if (yield this.isRevoked()) {
                throw new Error(`Unable to transfer Certificate #${this.id}. It has been revoked.`);
            }
            const { activeUser, registry } = this.blockchainProperties;
            const fromAddress = from !== null && from !== void 0 ? from : (yield activeUser.getAddress());
            const toAddress = ethers_1.ethers.utils.getAddress(to);
            const ownedVolume = ethers_1.BigNumber.from((_a = this.owners[toAddress]) !== null && _a !== void 0 ? _a : 0);
            const registryWithSigner = registry.connect(activeUser);
            return registryWithSigner.safeTransferFrom(fromAddress, toAddress, this.id, amount !== null && amount !== void 0 ? amount : ownedVolume, ethers_1.utils.defaultAbiCoder.encode([], []) // TO-DO: Store more meaningful transfer data?
            );
        });
    }
    mint(to, volume) {
        return __awaiter(this, void 0, void 0, function* () {
            const toAddress = ethers_1.ethers.utils.getAddress(to);
            const { issuer } = this.blockchainProperties;
            const issuerWithSigner = issuer.connect(this.blockchainProperties.activeUser);
            return issuerWithSigner.mint(toAddress, this.id, volume);
        });
    }
    revoke() {
        return __awaiter(this, void 0, void 0, function* () {
            const { issuer } = this.blockchainProperties;
            const issuerWithSigner = issuer.connect(this.blockchainProperties.activeUser);
            return issuerWithSigner.revokeCertificate(this.id);
        });
    }
    isRevoked() {
        return __awaiter(this, void 0, void 0, function* () {
            const { issuer } = this.blockchainProperties;
            const revokedEvents = yield (0, events_1.getEventsFromContract)(issuer, issuer.filters.CertificateRevoked(this.id));
            return revokedEvents.length > 0;
        });
    }
    getClaimedData() {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            const { registry } = this.blockchainProperties;
            const claims = [];
            const claimSingleEvents = yield (0, events_1.getEventsFromContract)(registry, registry.filters.ClaimSingle(null, null, null, null, null, null));
            for (const claimEvent of claimSingleEvents) {
                if (claimEvent._id.toNumber() === this.id) {
                    const { _claimData, _id, _claimIssuer, _claimSubject, _topic, _value } = claimEvent;
                    const claimData = (0, CertificateUtils_1.decodeClaimData)(_claimData);
                    claims.push({
                        id: _id.toNumber(),
                        from: _claimIssuer,
                        to: _claimSubject,
                        topic: _topic.toString(),
                        value: _value.toString(),
                        claimData
                    });
                }
            }
            const claimBatchEvents = yield (0, events_1.getEventsFromContract)(registry, registry.filters.ClaimBatch(null, null, null, null, null, null));
            for (const claimBatchEvent of claimBatchEvents) {
                if (claimBatchEvent._ids.map((idAsBN) => idAsBN.toNumber()).includes(this.id)) {
                    const { _ids, _claimData, _claimIssuer, _claimSubject, _topics, _values } = claimBatchEvent;
                    const claimIds = _ids.map((idAsBN) => idAsBN.toNumber());
                    const index = claimIds.indexOf(this.id);
                    const claimData = (0, CertificateUtils_1.decodeClaimData)(_claimData[index]);
                    claims.push({
                        id: _ids[index].toNumber(),
                        from: _claimIssuer,
                        to: _claimSubject,
                        topic: (_a = _topics[index]) === null || _a === void 0 ? void 0 : _a.toString(),
                        value: _values[index].toString(),
                        claimData
                    });
                }
            }
            const claimBatchMultipleEvents = yield (0, events_1.getEventsFromContract)(registry, registry.filters.ClaimBatchMultiple(null, null, null, null, null, null));
            for (const claimBatchMultipleEvent of claimBatchMultipleEvents) {
                if (claimBatchMultipleEvent._ids
                    .map((idAsBN) => idAsBN.toNumber())
                    .includes(this.id)) {
                    const { _ids, _claimData, _claimIssuer, _claimSubject, _topics, _values } = claimBatchMultipleEvent;
                    const claimIds = _ids.map((idAsBN) => idAsBN.toNumber());
                    const index = claimIds.indexOf(this.id);
                    claims.push({
                        id: _ids[index].toNumber(),
                        from: _claimIssuer[index],
                        to: _claimSubject[index],
                        topic: (_b = _topics[index]) === null || _b === void 0 ? void 0 : _b.toString(),
                        value: _values[index].toString(),
                        claimData: (0, CertificateUtils_1.decodeClaimData)(_claimData[index])
                    });
                }
            }
            return claims;
        });
    }
    getIssuanceTransaction() {
        return __awaiter(this, void 0, void 0, function* () {
            const { registry } = this.blockchainProperties;
            const allIssuanceLogs = yield (0, events_1.getEventsFromContract)(registry, registry.filters.IssuanceSingle(null, null, null));
            let issuanceLog = allIssuanceLogs.find((event) => event._id.toString() === this.id.toString());
            if (!issuanceLog) {
                const allBatchIssuanceLogs = yield (0, events_1.getEventsFromContract)(registry, registry.filters.IssuanceBatch(null, null, null, null));
                issuanceLog = allBatchIssuanceLogs.find((event) => event._ids.map((id) => id.toString()).includes(this.id.toString()));
                if (!issuanceLog) {
                    throw new Error(`Unable to find the issuance event for Certificate ${this.id}`);
                }
            }
            return registry.provider.getTransactionReceipt(issuanceLog.transactionHash);
        });
    }
}
exports.Certificate = Certificate;
//# sourceMappingURL=Certificate.js.map