//
//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//

import Foundation
import SignalMessaging

private class IntroducingCustomNotificationAudioExperienceUpgradeViewController: ExperienceUpgradeViewController {

    var buttonAction: ((UIButton) -> Void)?

    override func loadView() {
        self.view = UIView.container()

        /// Create Views

        // Title label
        let titleLabel = UILabel()
        view.addSubview(titleLabel)
        titleLabel.text = header
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
        titleLabel.textColor = UIColor.white
        titleLabel.minimumScaleFactor = 0.5
        titleLabel.adjustsFontSizeToFitWidth = true

        // Body label
        let bodyLabel = UILabel()
        self.bodyLabel = bodyLabel
        view.addSubview(bodyLabel)
        bodyLabel.text = body
        bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
        bodyLabel.textColor = UIColor.black
        bodyLabel.numberOfLines = 0
        bodyLabel.lineBreakMode = .byWordWrapping
        bodyLabel.textAlignment = .center

        // Image
        let imageView = UIImageView(image: image)
        view.addSubview(imageView)
        imageView.contentMode = .scaleAspectFit

        let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_SETTINGS_BUTTON", comment: "button label shown one time, after upgrade")
        let button = addButton(title: buttonTitle) { _ in
            // dismiss the modally presented view controller, then proceed.
            self.experienceUpgradesPageViewController.dismiss(animated: true) {
                guard let fromViewController = UIApplication.shared.frontmostViewController else {
                    owsFail("frontmostViewController was unexectedly nil")
                    return
                }

                // Construct the "settings" view & push the "notifications settings" view.
                let navigationController = AppSettingsViewController.inModalNavigationController()
                navigationController.pushViewController(NotificationSettingsViewController(), animated: false)

                fromViewController.present(navigationController, animated: true)
            }
        }

        let bottomSpacer = UIView()
        view.addSubview(bottomSpacer)

        /// Layout Views

        // Image layout
        imageView.autoAlignAxis(toSuperviewAxis: .vertical)
        imageView.autoPinToSquareAspectRatio()
        imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
        imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))

        // Title label layout
        titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
        titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
        titleLabel.autoPinTopToSuperviewMargin()

        // Body label layout
        bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
        bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
        bodyLabel.setContentHuggingVerticalHigh()

        // Button layout
        button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(16))
        button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))

        bottomSpacer.autoPinEdge(.top, to: .bottom, of: button, withOffset: ScaleFromIPhone5(16))
        bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom)
        bottomSpacer.autoPinWidthToSuperview()
    }

    // MARK: - Actions

    func addButton(title: String, action: @escaping (UIButton) -> Void) -> UIButton {
        self.buttonAction = action
        let button = MultiLineButton()
        view.addSubview(button)
        button.setTitle(title, for: .normal)
        button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
        button.isUserInteractionEnabled = true
        button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
        button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        button.titleLabel?.textAlignment = .center
        button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))
        return button
    }

    @objc func didTapButton(sender: UIButton) {
        Logger.debug("\(TAG) in \(#function)")

        guard let buttonAction = self.buttonAction else {
            owsFail("\(TAG) button action was nil")
            return
        }

        buttonAction(sender)
    }
}

private class IntroductingReadReceiptsExperienceUpgradeViewController: ExperienceUpgradeViewController {

    var buttonAction: ((UIButton) -> Void)?

    override func loadView() {
        self.view = UIView.container()

        /// Create Views

        // Title label
        let titleLabel = UILabel()
        view.addSubview(titleLabel)
        titleLabel.text = header
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
        titleLabel.textColor = UIColor.white
        titleLabel.minimumScaleFactor = 0.5
        titleLabel.adjustsFontSizeToFitWidth = true

        // Body label
        let bodyLabel = UILabel()
        self.bodyLabel = bodyLabel
        view.addSubview(bodyLabel)
        bodyLabel.text = body
        bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
        bodyLabel.textColor = UIColor.black
        bodyLabel.numberOfLines = 0
        bodyLabel.lineBreakMode = .byWordWrapping
        bodyLabel.textAlignment = .center

        // Image
        let imageView = UIImageView(image: image)
        view.addSubview(imageView)
        imageView.contentMode = .scaleAspectFit

        let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_READ_RECEIPTS_PRIVACY_SETTINGS", comment: "button label shown one time, after upgrade")
        let button = addButton(title: buttonTitle) { _ in
            // dismiss the modally presented view controller, then proceed.
            self.experienceUpgradesPageViewController.dismiss(animated: true) {
                guard let fromViewController = UIApplication.shared.frontmostViewController as? HomeViewController else {
                    owsFail("unexpected frontmostViewController: \(String(describing: UIApplication.shared.frontmostViewController))")
                    return
                }

                // Construct the "settings" view & push the "privacy settings" view.
                let navigationController = AppSettingsViewController.inModalNavigationController()
                navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false)

                fromViewController.present(navigationController, animated: true)
            }
        }

        let bottomSpacer = UIView()
        view.addSubview(bottomSpacer)

        /// Layout Views

        // Image layout
        imageView.autoAlignAxis(toSuperviewAxis: .vertical)
        imageView.autoPinToSquareAspectRatio()
        imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
        imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))

        // Title label layout
        titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
        titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
        titleLabel.autoPinTopToSuperviewMargin()

        // Body label layout
        bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
        bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
        bodyLabel.setContentHuggingVerticalHigh()

        // Button layout
        button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(16))
        button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))

        bottomSpacer.autoPinEdge(.top, to: .bottom, of: button, withOffset: ScaleFromIPhone5(16))
        bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom)
        bottomSpacer.autoPinWidthToSuperview()
    }

    // MARK: - Actions

    func addButton(title: String, action: @escaping (UIButton) -> Void) -> UIButton {
        self.buttonAction = action
        let button = MultiLineButton()
        view.addSubview(button)
        button.setTitle(title, for: .normal)
        button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
        button.isUserInteractionEnabled = true
        button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
        button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        button.titleLabel?.textAlignment = .center
        button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))
        return button
    }

    @objc func didTapButton(sender: UIButton) {
        Logger.debug("\(TAG) in \(#function)")

        guard let buttonAction = self.buttonAction else {
            owsFail("\(TAG) button action was nil")
            return
        }

        buttonAction(sender)
    }
}

/**
 * Allows multiple lines of button text, and ensures the buttons intrinsic content size reflects that of it's label.
 */
class MultiLineButton: UIButton {

    // MARK: - Init

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.commonInit()
    }

    required init() {
        super.init(frame: CGRect.zero)

        self.commonInit()
    }

    private func commonInit() {
        self.titleLabel?.numberOfLines = 0
        self.titleLabel?.lineBreakMode = .byWordWrapping
    }

    // MARK: - Overrides

    override var intrinsicContentSize: CGSize {
        guard let titleLabel = titleLabel else {
            return CGSize.zero
        }

        // be more forgiving with the tappable area
        let extraPadding: CGFloat = 20
        let labelSize = titleLabel.intrinsicContentSize
        return CGSize(width: labelSize.width + extraPadding, height: labelSize.height + extraPadding)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel?.preferredMaxLayoutWidth = titleLabel?.frame.size.width ?? 0
        super.layoutSubviews()
    }
}

private class IntroductingProfilesExperienceUpgradeViewController: ExperienceUpgradeViewController {

    override func loadView() {
        self.view = UIView()

        /// Create Views

        // Title label
        let titleLabel = UILabel()
        view.addSubview(titleLabel)
        titleLabel.text = header
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
        titleLabel.textColor = UIColor.white
        titleLabel.minimumScaleFactor = 0.5
        titleLabel.adjustsFontSizeToFitWidth = true

        // Body label
        let bodyLabel = UILabel()
        self.bodyLabel = bodyLabel
        view.addSubview(bodyLabel)
        bodyLabel.text = body
        bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
        bodyLabel.textColor = UIColor.black
        bodyLabel.numberOfLines = 0
        bodyLabel.lineBreakMode = .byWordWrapping
        bodyLabel.textAlignment = .center

        // Image
        let imageView = UIImageView(image: image)
        view.addSubview(imageView)
        imageView.contentMode = .scaleAspectFit

        // Button
        let button = UIButton()
        view.addSubview(button)
        let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_PROFILES_BUTTON", comment: "button label shown one time, after user upgrades app")
        button.setTitle(buttonTitle, for: .normal)
        button.setTitleColor(UIColor.white, for: .normal)
        button.backgroundColor = UIColor.ows_materialBlue

        button.isUserInteractionEnabled = true
        button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
        button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)

        button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))

        /// Layout Views

        // Image layout
        imageView.autoAlignAxis(toSuperviewAxis: .vertical)
        imageView.autoPinToSquareAspectRatio()
        imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
        imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))

        // Title label layout
        titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
        titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
        titleLabel.autoPinEdge(toSuperviewEdge: .top)

        // Body label layout
        bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
        bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
        bodyLabel.sizeToFit()

        // Button layout
        button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(18))
        button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))
        button.autoPinEdge(toSuperviewEdge: .bottom, withInset: ScaleFromIPhone5(16))
        button.autoSetDimension(.height, toSize: ScaleFromIPhone5(36))
    }

    // MARK: - Actions

    @objc func didTapButton(sender: UIButton) {
        Logger.debug("\(TAG) in \(#function)")

        // dismiss the modally presented view controller, then proceed.
        experienceUpgradesPageViewController.dismiss(animated: true) {
            guard let fromViewController = UIApplication.shared.frontmostViewController as? HomeViewController else {
                owsFail("unexpected frontmostViewController: \(String(describing: UIApplication.shared.frontmostViewController))")
                return
            }
            ProfileViewController.presentForUpgradeOrNag(from: fromViewController)
        }
    }
}

private class CallKitExperienceUpgradeViewController: ExperienceUpgradeViewController {

    override func loadView() {
        super.loadView()
        assert(view != nil)
        assert(bodyLabel != nil)

        // Privacy Settings Button
        let privacySettingsButton = UIButton()
        view.addSubview(privacySettingsButton)
        let privacyTitle = NSLocalizedString("UPGRADE_EXPERIENCE_CALLKIT_PRIVACY_SETTINGS_BUTTON", comment: "button label shown once when when user upgrades app, in context of call kit")
        privacySettingsButton.setTitle(privacyTitle, for: .normal)
        privacySettingsButton.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
        privacySettingsButton.isUserInteractionEnabled = true
        privacySettingsButton.addTarget(self, action: #selector(didTapPrivacySettingsButton), for: .touchUpInside)
        privacySettingsButton.titleLabel?.font = bodyLabel.font

        // Privacy Settings Button layout
        privacySettingsButton.autoPinWidthToSuperview(withMargin: bodyMargin)
        privacySettingsButton.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(12))
        privacySettingsButton.sizeToFit()
    }

    // MARK: - Actions

    @objc func didTapPrivacySettingsButton(sender: UIButton) {
        Logger.debug("\(TAG) in \(#function)")

        // dismiss the modally presented view controller, then proceed.
        experienceUpgradesPageViewController.dismiss(animated: true) {
            let fromViewController = UIApplication.shared.frontmostViewController
            assert(fromViewController != nil)

            // Construct the "settings" view & push the "privacy settings" view.
            let navigationController = AppSettingsViewController.inModalNavigationController()
            navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false)

            fromViewController?.present(navigationController, animated: true, completion: nil)
        }
    }
}

private class ExperienceUpgradeViewController: OWSViewController {
    let TAG = "[ExperienceUpgradeViewController]"

    let header: String
    let body: String
    let image: UIImage?
    let experienceUpgradesPageViewController: ExperienceUpgradesPageViewController

    var bodyLabel: UILabel!
    let bodyMargin = ScaleFromIPhone5To7Plus(12, 24)

    init(experienceUpgrade: ExperienceUpgrade, experienceUpgradesPageViewController: ExperienceUpgradesPageViewController) {
        header = experienceUpgrade.title
        body = experienceUpgrade.body
        image = experienceUpgrade.image
        self.experienceUpgradesPageViewController = experienceUpgradesPageViewController
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        self.view = UIView()

        /// Create Views

        // Title label
        let titleLabel = UILabel()
        view.addSubview(titleLabel)
        titleLabel.text = header
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
        titleLabel.textColor = UIColor.white
        titleLabel.minimumScaleFactor = 0.5
        titleLabel.adjustsFontSizeToFitWidth = true

        // Body label
        let bodyLabel = UILabel()
        self.bodyLabel = bodyLabel
        view.addSubview(bodyLabel)
        bodyLabel.text = body
        bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
        bodyLabel.textColor = UIColor.black
        bodyLabel.numberOfLines = 0
        bodyLabel.lineBreakMode = .byWordWrapping
        bodyLabel.textAlignment = .center

        // Image
        let imageView = UIImageView(image: image)
        view.addSubview(imageView)
        imageView.contentMode = .scaleAspectFit

        /// Layout Views

        // Image layout
        imageView.autoAlignAxis(toSuperviewAxis: .vertical)
        imageView.autoPinToSquareAspectRatio()
        imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 60))
        imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))

        // Title label layout
        titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
        titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
        titleLabel.autoPinEdge(toSuperviewEdge: .top)

        // Body label layout
        bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
        bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
        bodyLabel.sizeToFit()
        bodyLabel.autoPinEdge(toSuperviewEdge: .bottom, withInset: ScaleFromIPhone5(16))
    }
}

func setPageControlAppearance() {
    let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self])
    pageControl.pageIndicatorTintColor = UIColor.lightGray
    pageControl.currentPageIndicatorTintColor = UIColor.ows_materialBlue
}

@objc
public class ExperienceUpgradesPageViewController: OWSViewController, UIPageViewControllerDataSource {

    let TAG = "[ExperienceUpgradeViewController]"

    private let experienceUpgrades: [ExperienceUpgrade]
    private var allViewControllers = [UIViewController]()
    private var viewControllerIndexes = [UIViewController: Int]()

    let pageViewController: UIPageViewController

    let editingDBConnection: YapDatabaseConnection

    // MARK: - Initializers

    @objc
    public required init(experienceUpgrades: [ExperienceUpgrade]) {
        self.experienceUpgrades = experienceUpgrades

        setPageControlAppearance()
        self.editingDBConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
        self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
        super.init(nibName: nil, bundle: nil)
        self.pageViewController.dataSource = self

        experienceUpgrades.forEach { addViewController(experienceUpgrade: $0) }
    }

    @available(*, unavailable, message:"unavailable, use initWithExperienceUpgrade instead")
    @objc
    public required init?(coder aDecoder: NSCoder) {
        fatalError("unimplemented")
    }

    // MARK: - View lifecycle

    @objc public override func viewDidLoad() {
        guard let firstViewController = allViewControllers.first else {
            owsFail("\(TAG) no pages to show.")
            dismiss(animated: true)
            return
        }

        addDismissGesture()
        self.pageViewController.setViewControllers([ firstViewController ], direction: .forward, animated: false, completion: nil)
    }

    @objc public override func loadView() {
        self.view = UIView.container()
        view.backgroundColor = UIColor.white

        //// Create Views

        // Header Background
        let statusBarBackgroundView = UIView.container()
        view.addSubview(statusBarBackgroundView)
        statusBarBackgroundView.backgroundColor = UIColor.ows_materialBlue

        let headerBackgroundView = UIView.container()
        view.addSubview(headerBackgroundView)
        headerBackgroundView.backgroundColor = UIColor.ows_materialBlue

        // Dismiss button
        let dismissButton = UIButton()
        view.addSubview(dismissButton)
        dismissButton.setTitle(CommonStrings.dismissButton, for: .normal)
        dismissButton.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
        dismissButton.isUserInteractionEnabled = true
        dismissButton.addTarget(self, action: #selector(didTapDismissButton), for: .touchUpInside)
        dismissButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(16))
        let dismissInsetValue: CGFloat = ScaleFromIPhone5(10)
        dismissButton.contentEdgeInsets = UIEdgeInsets(top: dismissInsetValue, left: dismissInsetValue, bottom: dismissInsetValue, right: dismissInsetValue)

        guard let carouselView = self.pageViewController.view else {
            Logger.error("\(TAG) carousel view was unexpectedly nil")
            return
        }

        self.view.addSubview(carouselView)

        //// Layout Views

        // Status Bar Background layout
        //
        // We use a separate backgrounds for the "status bar" area (which has very
        // different height on iPhone X) and the "header" (which has consistent
        // height).
        statusBarBackgroundView.autoPinWidthToSuperview()
        statusBarBackgroundView.autoPinEdge(toSuperviewEdge: .top)
        statusBarBackgroundView.autoPinEdge(.bottom, to: .top, of: headerBackgroundView)

        // Header Background layout
        statusBarBackgroundView.autoPinWidthToSuperview()
        statusBarBackgroundView.autoPinEdge(toSuperviewEdge: .top)
        statusBarBackgroundView.autoPinEdge(.bottom, to: .top, of: headerBackgroundView)

        headerBackgroundView.autoPinWidthToSuperview()
        headerBackgroundView.autoPinTopToSuperviewMargin()
        headerBackgroundView.autoSetDimension(.height, toSize: ScaleFromIPhone5(60))

        // Dismiss button layout
        dismissButton.autoHCenterInSuperview()
        dismissButton.autoPinBottomToSuperviewMargin(withInset: ScaleFromIPhone5(10))

        // Carousel View layout
        carouselView.autoPinWidthToSuperview()
        // negative inset so as to overlay the header text in the carousel view with the header background which
        // lives outside of the carousel. We do this so that the user can't bounce past the page view controllers
        // width limits, exposing the edge of the header.
        carouselView.autoPinTopToSuperviewMargin(withInset: ScaleFromIPhone5To7Plus(14, 24))
        carouselView.autoPinEdge(.bottom, to: .top, of: dismissButton, withOffset: ScaleFromIPhone5(-10))
    }

    private func addDismissGesture() {
        let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleDismissGesture))
        swipeGesture.direction = .down
        view.addGestureRecognizer(swipeGesture)
    }

    // MARK: - UIPageViewControllerDataSource

    public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        Logger.debug("\(TAG) in \(#function)")
        guard let currentIndex = self.viewControllerIndexes[viewController] else {
            owsFail("\(TAG) unknown view controller: \(viewController)")
            return nil
        }

        if currentIndex + 1 == allViewControllers.count {
            // already at last view controller
            return nil
        }

        return allViewControllers[currentIndex + 1]
    }

    public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        Logger.debug("\(TAG) in \(#function)")
        guard let currentIndex = self.viewControllerIndexes[viewController] else {
            owsFail("\(TAG) unknown view controller: \(viewController)")
            return nil
        }

        if currentIndex <= 0 {
            // already at first view controller
            return nil
        }

        return allViewControllers[currentIndex - 1]
    }

    public func presentationCount(for pageViewController: UIPageViewController) -> Int {
        // don't show a page indicator if there's only one page.
        return allViewControllers.count == 1 ? 0 : allViewControllers.count
    }

    public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        guard let currentViewController = pageViewController.viewControllers?.first else {
            Logger.error("\(TAG) unexpectedly empty view controllers.")
            return 0
        }

        guard let currentIndex = self.viewControllerIndexes[currentViewController] else {
            Logger.error("\(TAG) unknown view controller: \(currentViewController)")
            return 0
        }

        return currentIndex
    }

    public func addViewController(experienceUpgrade: ExperienceUpgrade) {
        guard let uniqueId = experienceUpgrade.uniqueId else {
            Logger.error("\(self.TAG) experienceUpgrade is missing uniqueId.")
            return
        }
        guard let identifier = ExperienceUpgradeId(rawValue: uniqueId) else {
            owsFail("\(TAG) unknown experience upgrade. skipping")
            return
        }

        let viewController: ExperienceUpgradeViewController = {
            switch identifier {
            case .callKit:
                return CallKitExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
            case .introducingProfiles:
                return IntroductingProfilesExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
            case .introducingReadReceipts:
                return IntroductingReadReceiptsExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
            case .introducingCustomNotificationAudio:
                return IntroducingCustomNotificationAudioExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
            default:
                return ExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
            }
        }()

        let count = allViewControllers.count
        viewControllerIndexes[viewController] = count
        allViewControllers.append(viewController)
    }

    @objc public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        // Blocking write before dismiss, to be sure they're marked as complete
        // before HomeView.didAppear is re-fired.
        self.editingDBConnection.readWrite { transaction in
            Logger.info("\(self.logTag) marking all upgrades as seen.")
            ExperienceUpgradeFinder.shared.markAllAsSeen(transaction: transaction)
        }
        super.dismiss(animated: flag, completion: completion)
    }

    @objc func didTapDismissButton(sender: UIButton) {
        Logger.debug("\(TAG) in \(#function)")
        self.dismiss(animated: true)
    }

    @objc func handleDismissGesture(sender: AnyObject) {
        Logger.debug("\(TAG) in \(#function)")
        self.dismiss(animated: true)
    }
}
