"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cloudwatch = require("@aws-cdk/aws-cloudwatch");
const s3n = require("@aws-cdk/aws-s3-notifications");
const cdk = require("@aws-cdk/cdk");
const policy_1 = require("./policy");
const subscription_1 = require("./subscription");
/**
 * ARN of a Topic
 */
class TopicArn extends cdk.Arn {
}
exports.TopicArn = TopicArn;
/**
 * Either a new or imported Topic
 */
class TopicRef extends cdk.Construct {
    constructor() {
        super(...arguments);
        /** Buckets permitted to send notifications to this topic */
        this.notifyingBuckets = new Set();
        /**
         * Indicates if the resource policy that allows CloudWatch events to publish
         * notifications to this topic have been added.
         */
        this.eventRuleTargetPolicyAdded = false;
    }
    /**
     * Import a Topic defined elsewhere
     */
    static import(parent, name, props) {
        return new ImportedTopic(parent, name, props);
    }
    /**
     * Export this Topic
     */
    export() {
        return {
            topicArn: new cdk.Output(this, 'TopicArn', { value: this.topicArn }).makeImportValue(),
            topicName: new cdk.Output(this, 'TopicName', { value: this.topicName }).makeImportValue(),
        };
    }
    /**
     * Subscribe some endpoint to this topic
     */
    subscribe(name, endpoint, protocol) {
        new subscription_1.Subscription(this, name, {
            topic: this,
            endpoint,
            protocol
        });
    }
    /**
     * Defines a subscription from this SNS topic to an SQS queue.
     *
     * The queue resource policy will be updated to allow this SNS topic to send
     * messages to the queue.
     *
     * TODO: change to QueueRef.
     *
     * @param name The subscription name
     * @param queue The target queue
     */
    subscribeQueue(queue) {
        const subscriptionName = queue.id + 'Subscription';
        if (this.tryFindChild(subscriptionName)) {
            throw new Error(`A subscription between the topic ${this.id} and the queue ${queue.id} already exists`);
        }
        // we use the queue name as the subscription's. there's no meaning to subscribing
        // the same queue twice on the same topic.
        const sub = new subscription_1.Subscription(this, subscriptionName, {
            topic: this,
            endpoint: queue.queueArn,
            protocol: subscription_1.SubscriptionProtocol.Sqs
        });
        // add a statement to the queue resource policy which allows this topic
        // to send messages to the queue.
        queue.addToResourcePolicy(new cdk.PolicyStatement()
            .addResource(queue.queueArn)
            .addAction('sqs:SendMessage')
            .addServicePrincipal('sns.amazonaws.com')
            .setCondition('ArnEquals', { 'aws:SourceArn': this.topicArn }));
        return sub;
    }
    /**
     * Defines a subscription from this SNS Topic to a Lambda function.
     *
     * The Lambda's resource policy will be updated to allow this topic to
     * invoke the function.
     *
     * @param name A name for the subscription
     * @param lambdaFunction The Lambda function to invoke
     */
    subscribeLambda(lambdaFunction) {
        const subscriptionName = lambdaFunction.id + 'Subscription';
        if (this.tryFindChild(subscriptionName)) {
            throw new Error(`A subscription between the topic ${this.id} and the lambda ${lambdaFunction.id} already exists`);
        }
        const sub = new subscription_1.Subscription(this, subscriptionName, {
            topic: this,
            endpoint: lambdaFunction.functionArn,
            protocol: subscription_1.SubscriptionProtocol.Lambda
        });
        lambdaFunction.addPermission(this.id, {
            sourceArn: this.topicArn,
            principal: new cdk.ServicePrincipal('sns.amazonaws.com'),
        });
        return sub;
    }
    /**
     * Defines a subscription from this SNS topic to an email address.
     *
     * @param name A name for the subscription
     * @param emailAddress The email address to use.
     * @param jsonFormat True if the email content should be in JSON format (default is false).
     */
    subscribeEmail(name, emailAddress, options) {
        const protocol = (options && options.json ? subscription_1.SubscriptionProtocol.EmailJson : subscription_1.SubscriptionProtocol.Email);
        return new subscription_1.Subscription(this, name, {
            topic: this,
            endpoint: emailAddress,
            protocol
        });
    }
    /**
     * Defines a subscription from this SNS topic to an http:// or https:// URL.
     *
     * @param name A name for the subscription
     * @param url The URL to invoke
     */
    subscribeUrl(name, url) {
        if (!url.startsWith('http://') && !url.startsWith('https://')) {
            throw new Error('URL must start with either http:// or https://');
        }
        const protocol = url.startsWith('https:') ? subscription_1.SubscriptionProtocol.Https : subscription_1.SubscriptionProtocol.Http;
        return new subscription_1.Subscription(this, name, {
            topic: this,
            endpoint: url,
            protocol
        });
    }
    /**
     * Adds a statement to the IAM resource policy associated with this topic.
     *
     * If this topic was created in this stack (`new Topic`), a topic policy
     * will be automatically created upon the first call to `addToPolicy`. If
     * the topic is improted (`Topic.import`), then this is a no-op.
     */
    addToResourcePolicy(statement) {
        if (!this.policy && this.autoCreatePolicy) {
            this.policy = new policy_1.TopicPolicy(this, 'Policy', { topics: [this] });
        }
        if (this.policy) {
            // statements must be unique, so we use the statement index.
            // potantially SIDs can change as a result of order change, but this should
            // not have an impact on the policy evaluation.
            // https://docs.aws.amazon.com/sns/latest/dg/AccessPolicyLanguage_SpecialInfo.html
            statement.describe(this.policy.document.statementCount.toString());
            this.policy.document.addStatement(statement);
        }
    }
    /**
     * Grant topic publishing permissions to the given identity
     */
    grantPublish(identity) {
        if (!identity) {
            return;
        }
        identity.addToPolicy(new cdk.PolicyStatement()
            .addResource(this.topicArn)
            .addActions('sns:Publish'));
    }
    /**
     * Returns a RuleTarget that can be used to trigger this SNS topic as a
     * result from a CloudWatch event.
     *
     * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/resource-based-policies-cwe.html#sns-permissions
     */
    asEventRuleTarget(_ruleArn, _ruleId) {
        if (!this.eventRuleTargetPolicyAdded) {
            this.addToResourcePolicy(new cdk.PolicyStatement()
                .addAction('sns:Publish')
                .addPrincipal(new cdk.ServicePrincipal('events.amazonaws.com'))
                .addResource(this.topicArn));
            this.eventRuleTargetPolicyAdded = true;
        }
        return {
            id: this.id,
            arn: this.topicArn,
        };
    }
    get alarmActionArn() {
        return this.topicArn;
    }
    /**
     * Construct a Metric object for the current topic for the given metric
     */
    metric(metricName, props) {
        return new cloudwatch.Metric(Object.assign({ namespace: 'AWS/SNS', dimensions: { TopicName: this.topicName }, metricName }, props));
    }
    /**
     * Metric for the size of messages published through this topic
     *
     * @default average over 5 minutes
     */
    metricPublishSize(props) {
        return this.metric('PublishSize', props);
    }
    /**
     * Metric for the number of messages published through this topic
     *
     * @default sum over 5 minutes
     */
    metricNumberOfMessagesPublished(props) {
        return this.metric('NumberOfMessagesPublished', Object.assign({ statistic: 'sum' }, props));
    }
    /**
     * Metric for the number of messages that failed to publish through this topic
     *
     * @default sum over 5 minutes
     */
    metricNumberOfMessagesFailed(props) {
        return this.metric('NumberOfMessagesFailed', Object.assign({ statistic: 'sum' }, props));
    }
    /**
     * Metric for the number of messages that were successfully delivered through this topic
     *
     * @default sum over 5 minutes
     */
    metricNumberOfMessagesDelivered(props) {
        return this.metric('NumberOfMessagesDelivered', Object.assign({ statistic: 'sum' }, props));
    }
    /**
     * Implements the IBucketNotificationDestination interface, allowing topics to be used
     * as bucket notification destinations.
     *
     * @param bucketArn The ARN of the bucket sending the notifications
     * @param bucketId A unique ID of the bucket
     */
    asBucketNotificationDestination(bucketArn, bucketId) {
        // allow this bucket to sns:publish to this topic (if it doesn't already have a permission)
        if (!this.notifyingBuckets.has(bucketId)) {
            this.addToResourcePolicy(new cdk.PolicyStatement()
                .addServicePrincipal('s3.amazonaws.com')
                .addAction('sns:Publish')
                .addResource(this.topicArn)
                .addCondition('ArnLike', { "aws:SourceArn": bucketArn }));
            this.notifyingBuckets.add(bucketId);
        }
        return {
            arn: this.topicArn,
            type: s3n.BucketNotificationDestinationType.Topic
        };
    }
}
exports.TopicRef = TopicRef;
/**
 * An imported topic
 */
class ImportedTopic extends TopicRef {
    constructor(parent, name, props) {
        super(parent, name);
        this.autoCreatePolicy = false;
        this.topicArn = props.topicArn;
        this.topicName = props.topicName;
    }
}
//# sourceMappingURL=data:application/json;base64, 