<commit_before>//
//  FeedbackViewController.swift
//  PinpointKit
//
//  Created by <NAME> on 2/5/16.
//  Copyright © 2016 Lickability. All rights reserved.
//

import UIKit

/// A `UITableViewController` that conforms to `FeedbackCollector` in order to display an interface that allows the user to see, change, and send feedback.
public final class FeedbackViewController: UITableViewController {
    
    // MARK: - InterfaceCustomizable
    
    public var interfaceCustomization: InterfaceCustomization? {
        didSet {
            guard isViewLoaded() else { return }
            
            updateInterfaceCustomization()
        }
    }
    
    // MARK: - LogSupporting
    
    public var logViewer: LogViewer?
    public var logCollector: LogCollector?
    public var editor: Editor?
    
    // MARK: - FeedbackCollector
    
    public weak var feedbackDelegate: FeedbackCollectorDelegate?
    public var feedbackConfiguration: FeedbackConfiguration?
    
    // MARK: - FeedbackViewController
    
    /// The screenshot the feedback describes.
    public var screenshot: UIImage? {
        didSet {
            guard isViewLoaded() else { return }
            updateTableHeaderView()
        }
    }
    
    /// The annotated screenshot the feedback describes.
    var annotatedScreenshot: UIImage? {
        didSet {
            guard isViewLoaded() else { return }
            updateTableHeaderView()
        }
    }
    
    private var dataSource: FeedbackTableViewDataSource? {
        didSet {
            guard isViewLoaded() else { return }
            tableView.dataSource = dataSource
        }
    }
    
    private var userEnabledLogCollection = true {
        didSet {
            updateDataSource()
        }
    }
    
    public required init() {
        super.init(style: .Grouped)
    }
    
    @available(*, unavailable)
    override init(style: UITableViewStyle) {
        super.init(style: .Grouped)
    }
    
    @available(*, unavailable)
    public required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - UIViewController
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        editor?.delegate = self
        updateInterfaceCustomization()
    }
    
    public override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        // Since this view controller could be reused in another orientation, update the table header view on every appearance to reflect the current orientation sizing.
        updateTableHeaderView()
    }
    
    public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        
        coordinator.animateAlongsideTransition({ context in
            // Layout and adjust the height of the table header view by setting the property once more to alert the table view of a layout change.
            self.tableView.tableHeaderView?.layoutIfNeeded()
            self.tableView.tableHeaderView = self.tableView.tableHeaderView
        }, completion: nil)
    }
    
    // MARK: - FeedbackViewController
    
    private func updateDataSource() {
        guard let interfaceCustomization = interfaceCustomization else { assertionFailure(); return }
        
        dataSource = FeedbackTableViewDataSource(interfaceCustomization: interfaceCustomization, logSupporting: self, userEnabledLogCollection: userEnabledLogCollection)
    }
    
    private func updateTableHeaderView() {
        guard let screenshot = screenshot, editor = editor else { return }
        let screenshotToDisplay = annotatedScreenshot ?? screenshot
        
        // We must set the screenshot before showing the view controller.
        editor.setScreenshot(screenshot)
        let header = ScreenshotHeaderView()

        header.viewModel = ScreenshotHeaderView.ViewModel(screenshot: screenshotToDisplay, hintText: interfaceCustomization?.interfaceText.feedbackEditHint, hintFont: interfaceCustomization?.appearance.feedbackEditHintFont)
        header.screenshotButtonTapHandler = { [weak self] button in
            let editImageViewController = NavigationController(rootViewController: editor.viewController)
            editImageViewController.view.tintColor = self?.interfaceCustomization?.appearance.tintColor
            self?.presentViewController(editImageViewController, animated: true, completion: nil)
        }
        
        tableView.tableHeaderView = header
        tableView.enableTableHeaderViewDynamicHeight()
    }
    
    private func updateInterfaceCustomization() {
        guard let interfaceCustomization = interfaceCustomization else { assertionFailure(); return }
        let interfaceText = interfaceCustomization.interfaceText
        let appearance = interfaceCustomization.appearance

        title = interfaceText.feedbackCollectorTitle
        navigationController?.navigationBar.titleTextAttributes = [NSFontAttributeName: appearance.navigationTitleFont]
        
        let sendBarButtonItem = UIBarButtonItem(title: interfaceText.feedbackSendButtonTitle, style: .Done, target: self, action: #selector(FeedbackViewController.sendButtonTapped))
        sendBarButtonItem.setTitleTextAttributes([NSFontAttributeName: appearance.feedbackSendButtonFont], forState: .Normal)
        navigationItem.rightBarButtonItem = sendBarButtonItem
        
        let backBarButtonItem = UIBarButtonItem(title: interfaceText.feedbackBackButtonTitle, style: .Plain, target: nil, action: nil)
        backBarButtonItem.setTitleTextAttributes([NSFontAttributeName: appearance.feedbackBackButtonFont], forState: .Normal)
        navigationItem.backBarButtonItem = backBarButtonItem
        
        let cancelBarButtonItem: UIBarButtonItem
        let cancelAction = #selector(FeedbackViewController.cancelButtonTapped)
        if let cancelButtonTitle = interfaceText.feedbackCancelButtonTitle {
            cancelBarButtonItem = UIBarButtonItem(title: cancelButtonTitle, style: .Plain, target: self, action: cancelAction)
        } else {
            cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: cancelAction)
        }
        
        cancelBarButtonItem.setTitleTextAttributes([NSFontAttributeName: appearance.feedbackCancelButtonFont], forState: .Normal)
        navigationItem.leftBarButtonItem = cancelBarButtonItem
        
        view.tintColor = appearance.tintColor
        updateTableHeaderView()
        updateDataSource()
    }
    
    @objc private func sendButtonTapped() {
        
        let logs = userEnabledLogCollection ? logCollector?.retrieveLogs() : nil
        
        let feedback: Feedback?
        
        if let screenshot = annotatedScreenshot {
            feedback = Feedback(screenshot: .Annotated(image: screenshot), logs: logs, configuration: feedbackConfiguration)
        } else if let screenshot = screenshot {
            feedback = Feedback(screenshot: .Original(image: screenshot), logs: logs, configuration: feedbackConfiguration)
        } else {
            feedback = nil
        }
        
        guard let feedbackToSend = feedback else { return assertionFailure("We must have either a screenshot or an edited screenshot!") }
        
        feedbackDelegate?.feedbackCollector(self, didCollectFeedback: feedbackToSend)
    }
    
    @objc private func cancelButtonTapped() {        
        dismissViewControllerAnimated(true, completion: nil)
    }
}

// MARK: - FeedbackCollector

extension FeedbackViewController: FeedbackCollector {
    public func collectFeedbackWithScreenshot(screenshot: UIImage, fromViewController viewController: UIViewController) {
        self.screenshot = screenshot
        annotatedScreenshot = nil
        viewController.showDetailViewController(self, sender: viewController)
    }
}

// MARK: - EditorDelegate

extension FeedbackViewController: EditorDelegate {
    public func editorWillDismiss(editor: Editor, screenshot: UIImage) {
        annotatedScreenshot = screenshot
        updateTableHeaderView()
    }
}

// MARK: - UITableViewDelegate

extension FeedbackViewController {
    public override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
        guard let logCollector = logCollector else {
            assertionFailure("No log collector exists.")
            return<commit_msg>Doesn’t assume `FeedbackViewController` is presented modally<commit_after>//
//  FeedbackViewController.swift
//  PinpointKit
//
//  Created by <NAME> on 2/5/16.
//  Copyright © 2016 Lickability. All rights reserved.
//

import UIKit

/// A `UITableViewController` that conforms to `FeedbackCollector` in order to display an interface that allows the user to see, change, and send feedback.
public final class FeedbackViewController: UITableViewController {
    
    // MARK: - InterfaceCustomizable
    
    public var interfaceCustomization: InterfaceCustomization? {
        didSet {
            guard isViewLoaded() else { return }
            
            updateInterfaceCustomization()
        }
    }
    
    // MARK: - LogSupporting
    
    public var logViewer: LogViewer?
    public var logCollector: LogCollector?
    public var editor: Editor?
    
    // MARK: - FeedbackCollector
    
    public weak var feedbackDelegate: FeedbackCollectorDelegate?
    public var feedbackConfiguration: FeedbackConfiguration?
    
    // MARK: - FeedbackViewController
    
    /// The screenshot the feedback describes.
    public var screenshot: UIImage? {
        didSet {
            guard isViewLoaded() else { return }
            updateTableHeaderView()
        }
    }
    
    /// The annotated screenshot the feedback describes.
    var annotatedScreenshot: UIImage? {
        didSet {
            guard isViewLoaded() else { return }
            updateTableHeaderView()
        }
    }
    
    private var dataSource: FeedbackTableViewDataSource? {
        didSet {
            guard isViewLoaded() else { return }
            tableView.dataSource = dataSource
        }
    }
    
    private var userEnabledLogCollection = true {
        didSet {
            updateDataSource()
        }
    }
    
    public required init() {
        super.init(style: .Grouped)
    }
    
    @available(*, unavailable)
    override init(style: UITableViewStyle) {
        super.init(style: .Grouped)
    }
    
    @available(*, unavailable)
    public required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - UIViewController
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        editor?.delegate = self
    }
    
    public override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        // Since this view controller could be reused in another orientation, update the table header view on every appearance to reflect the current orientation sizing.
        updateTableHeaderView()
        updateInterfaceCustomization()
    }
    
    public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        
        coordinator.animateAlongsideTransition({ context in
            // Layout and adjust the height of the table header view by setting the property once more to alert the table view of a layout change.
            self.tableView.tableHeaderView?.layoutIfNeeded()
            self.tableView.tableHeaderView = self.tableView.tableHeaderView
        }, completion: nil)
    }
    
    // MARK: - FeedbackViewController
    
    private func updateDataSource() {
        guard let interfaceCustomization = interfaceCustomization else { assertionFailure(); return }
        
        dataSource = FeedbackTableViewDataSource(interfaceCustomization: interfaceCustomization, logSupporting: self, userEnabledLogCollection: userEnabledLogCollection)
    }
    
    private func updateTableHeaderView() {
        guard let screenshot = screenshot, editor = editor else { return }
        let screenshotToDisplay = annotatedScreenshot ?? screenshot
        
        // We must set the screenshot before showing the view controller.
        editor.setScreenshot(screenshot)
        let header = ScreenshotHeaderView()

        header.viewModel = ScreenshotHeaderView.ViewModel(screenshot: screenshotToDisplay, hintText: interfaceCustomization?.interfaceText.feedbackEditHint, hintFont: interfaceCustomization?.appearance.feedbackEditHintFont)
        header.screenshotButtonTapHandler = { [weak self] button in
            let editImageViewController = NavigationController(rootViewController: editor.viewController)
            editImageViewController.view.tintColor = self?.interfaceCustomization?.appearance.tintColor
            self?.presentViewController(editImageViewController, animated: true, completion: nil)
        }
        
        tableView.tableHeaderView = header
        tableView.enableTableHeaderViewDynamicHeight()
    }
    
    private func updateInterfaceCustomization() {
        guard let interfaceCustomization = interfaceCustomization else { assertionFailure(); return }
        let interfaceText = interfaceCustomization.interfaceText
        let appearance = interfaceCustomization.appearance

        title = interfaceText.feedbackCollectorTitle
        navigationController?.navigationBar.titleTextAttributes = [NSFontAttributeName: appearance.navigationTitleFont]
        
        let sendBarButtonItem = UIBarButtonItem(title: interfaceText.feedbackSendButtonTitle, style: .Done, target: self, action: #selector(FeedbackViewController.sendButtonTapped))
        sendBarButtonItem.setTitleTextAttributes([NSFontAttributeName: appearance.feedbackSendButtonFont], forState: .Normal)
        navigationItem.rightBarButtonItem = sendBarButtonItem
        
        let backBarButtonItem = UIBarButtonItem(title: interfaceText.feedbackBackButtonTitle, style: .Plain, target: nil, action: nil)
        backBarButtonItem.setTitleTextAttributes([NSFontAttributeName: appearance.feedbackBackButtonFont], forState: .Normal)
        navigationItem.backBarButtonItem = backBarButtonItem
        
        let cancelBarButtonItem: UIBarButtonItem
        let cancelAction = #selector(FeedbackViewController.cancelButtonTapped)
        if let cancelButtonTitle = interfaceText.feedbackCancelButtonTitle {
            cancelBarButtonItem = UIBarButtonItem(title: cancelButtonTitle, style: .Plain, target: self, action: cancelAction)
        } else {
            cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: cancelAction)
        }
        
        cancelBarButtonItem.setTitleTextAttributes([NSFontAttributeName: appearance.feedbackCancelButtonFont], forState: .Normal)
        
        if viewController.presentingViewController != nil {
            navigationItem.leftBarButtonItem = cancelBarButtonItem
        } else {
            navigationItem.leftBarButtonItem = nil
        }
        
        view.tintColor = appearance.tintColor
        updateTableHeaderView()
        updateDataSource()
    }
    
    @objc private func sendButtonTapped() {
        
        let logs = userEnabledLogCollection ? logCollector?.retrieveLogs() : nil
        
        let feedback: Feedback?
        
        if let screenshot = annotatedScreenshot {
            feedback = Feedback(screenshot: .Annotated(image: screenshot), logs: logs, configuration: feedbackConfiguration)
        } else if let screenshot = screenshot {
            feedback = Feedback(screenshot: .Original(image: screenshot), logs: logs, configuration: feedbackConfiguration)
        } else {
            feedback = nil
        }
        
        guard let feedbackToSend = feedback else { return assertionFailure("We must have either a screenshot or an edited screenshot!") }
        
        feedbackDelegate?.feedbackCollector(self, didCollectFeedback: feedbackToSend)
    }
    
    @objc private func cancelButtonTapped() {
        if presentingViewController != nil {
            dismissViewControllerAnimated(true, completion: nil)
        } else if let navigationController = navigationController where navigationController.topViewController == self && navigationController.viewControllers.count > 1 {
            navigationController.popViewControllerAnimated(true)
        } else {
            assertionFailure("Attempting to dismiss `FeedbackViewController` in unexpected presentation context.")
        }
    }
}

// MARK: - FeedbackCollector

extension FeedbackViewController: FeedbackCollector {
    public func collectFeedbackWithScreenshot(screenshot: UIImage, fromViewController viewController: UIViewController) {
        self.screenshot = screenshot
        annotatedScreenshot = nil
        viewController.showDetailViewController(self, sender: viewController)
    }
}

// MARK: - EditorDelegate

extension FeedbackViewController: EditorDelegate {
    public func editorWillDismiss(editor: Editor, screenshot: UIImage) {
        annotatedScreenshot = screenshot
        updateTableHeaderView()
    }
}

// MARK: - UITableViewDelegate

extension FeedbackViewController {
    public override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
        guard let logCollector = logCollector else {
            assertionFailure("No log collector exists.")
            return
{'fraction_non_alphanumeric': 0.06747907294573202, 'fraction_numerical': 0.000968054211035818, 'mean_word_length': 2.975101856043459, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '255184', 'n_tokens_mistral': 4419, 'n_tokens_neox': 4163, 'n_words': 1075}