//: Playground - noun: a place where people can play



import UIKit

import XCTest


protocol DataService {}

protocol APIService {}

protocol Cache {}

protocol HomeViewModel {}

protocol Action {}

protocol State {}

protocol Result {}


enum MVC {

// Code Sample for Conductor


final class HomeViewController: UIViewController {

    @IBOutlet var headerView: UIView!

    @IBOutlet var signInButton: UIButton!

    @IBOutlet var signOutButton: UIButton!

    // ...


    var dataService: DataService!

    var apiService: APIService!

    var cache: Cache!

    // ...


    @IBAction func signIn() {

        // ...

    }

    // ...

}


}


enum MVVM {

// Code Sample for Bridge


final class HomeViewController: UIViewController {

    @IBOutlet var headerView: UIView!

    @IBOutlet var signInButton: UIButton!

    @IBOutlet var signOutButton: UIButton!

    // ...


    var viewModel: HomeViewModel!

}


}



enum Flux {

    struct Subscriber {

        func newState(_ state: State) {}

    }





    struct State {

        var currentUser: User?

    }


    struct User {

        let name: String

    }


    enum Action {

        case signIn(User)

        case signOut

    }


    final class Store {

        var state: State

        var subscribers: [Subscriber] = []


        init(initialState: State) {

            state = initialState

        }


        func dispatch(action: Action) {

            // update state ...

            subscribers.forEach { $0.newState(state) }

        }


        func subscribe(view: Subscriber) {

            subscribers.append(view)

        }

    }






    static func reduce(_ state: State, _ action: Action) -> State {

        var state = state


        switch action {

        case .signOut:

            state.currentUser = nil

        case .signIn(let user):

            state.currentUser = user

        }


        return state

    }


    class ReduceTests: XCTestCase {

        func testSignIn() {

            let state = State(currentUser: nil)

            let user = User(name: "Bob")

            let expected = State(currentUser: user)

            let newState = Flux.reduce(state, Action.signIn(user))

            XCTAssertEqual(expected, newState)

        }


        func testSignOut() {

            let user = User(name: "Bob")

            let state = State(currentUser: user)

            let expected = State(currentUser: nil)

            let newState = Flux.reduce(state, Action.signOut)

            XCTAssertEqual(expected, newState)

        }

    }


}


extension Flux.State: Equatable {

    static func ==(lhs: Flux.State, rhs: Flux.State) -> Bool {

        return true

    }

}



var actionStream: [Action]


extension Sequence {

    func reduce<Result>(

        _ initialResult: Result,

        _ nextPartialResult: (Result, Self.Iterator.Element) -> Result

    ) -> Result

    {

        return initialResult

    }



    func reduce<Result>(

        _ initialResult: Result,

        _ nextPartialResult: (Result, Action) -> Result

        ) -> Result

    {

        return initialResult

    }


    typealias State = Result


    func reduce(

        _ initialResult: State,

        _ nextPartialResult: (State, Action) -> State

        ) -> State

    {

        return initialResult

    }

}


let a = "hi there"

let b = 1


#if DEBUG

#endif













/// RESWIFT SLIDES






protocol StateType {}

protocol Reducer {}


struct Store<T> {

    func subscribe(_ any: Any) {


    }

    func unsubscribe(_ any: Any) {


    }

    func dispatch(_ any: Any) {


    }

}


protocol StoreSubscriber {}


enum ReSwift {



    struct User {

        let name: String

    }


    struct AppState: StateType {

        var user: User?

    }


    static let store = Store<AppState>()


    enum AuthenticationAction: Action {

        case signIn(User)

        case signOut

    }


    struct AppReducer: Reducer {

        func handleAction(action: Action, state: AppState?) -> AppState {

            let state = state ?? AppState()

            return AppState(

                user: userReducer(action: action, state: state)

            )

        }


        func userReducer(action: Action, state: AppState) -> User? {

            guard let action = action as? AuthenticationAction else {

                return state.user

            }


            switch action {

            case .signIn(let user):

                return user

            case .signOut:

                return nil

            }

        }

    }




    final class HomeViewController: UIViewController, StoreSubscriber {

        var store: Store<AppState>!


        override func viewWillAppear(_ animated: Bool) {

            store.subscribe(self)

        }


        override func viewWillDisappear(_ animated: Bool) {

            store.unsubscribe(self)

        }


        func newState(state: AppState) {

            // render state to view

        }


        @IBAction func signOut(sender: AnyObject) {

            store.dispatch(AuthenticationAction.signOut)

        }

    }


}


    class ReduceTests: XCTestCase {

        let user = User(name: "Bob")

        let signedOut = State(currentUser: nil)

        let signedIn = State(currentUser: User(name: "Bob"))


        func testSignIn() {

            let newState = Flux.reduce(signedOut, Action.signIn(user))

            XCTAssertEqual(signedIn, newState)

        }


        func testSignOut() {

            let newState = Flux.reduce(signedIn, Action.signOut)

            XCTAssertEqual(signedOut, newState)

        }

    }


    static let store = Store<AppState>(reducer: AppReducer(), state: nil)





// FIND ME
func signIn(credential: Credential) {

    store.dispatch(SignInFormAction.request)

    api.signIn(with: credential) { [store] result in

        DispatchQueue.main.async {

            switch result {

            case .success(let user):

                store.dispatch(SignInFormAction.handleSuccess)

                store.dispatch(AuthenticationAction.signIn(user))

            case .error(let error):

                store.dispatch(SignInFormAction.handleError(error))

            }

        }

    }

}



// EXAMPLE SIGN IN SLIDES


enum SignInExample {


struct AppState: StateType {

    var user: User?

    var signInForm = SignInState()

}


struct User {

    let name: String

}


struct SignInState {

    var isSigningIn = false

    var serverError: String?


    var email: String?

    var emailEditedOnce = false


    var password: String?

    var passwordEditedOnce = false

}




}



extension SignInExample.SignInState {

    var isEmailValid: Bool {

        return Validation.isValid(email: email ?? "")

    }


    var isPasswordValid: Bool {

        return Validation.isValid(password: password ?? "")

    }


    var isFormValid: Bool {

        return isEmailValid && isPasswordValid

    }

}











class SignInViewController: UIViewController, StoreSubscriber {

    @IBOutlet var serverErrorView: UIView!

    @IBOutlet var serverErrorLabel: UILabel!


    @IBOutlet var emailField: UITextField!

    @IBOutlet var emailErrorLabel: UILabel!


    @IBOutlet var passwordField: UITextField!

    @IBOutlet var passwordErrorLabel: UILabel!


    @IBOutlet var signInButton: UIButton!

    @IBOutlet var activityIndicator: UIActivityIndicatorView!


    func newState(state: SignInExample.AppState) {

        let state = state.signInForm


        serverErrorLabel.text = state.serverError

        serverErrorView.isHidden = state.serverError == nil


        emailField.text = state.email

        emailField.isEnabled = !state.isSigningIn

        emailErrorLabel.isHidden = !state.emailEditedOnce || state.isEmailValid


        passwordField.text = state.password

        passwordField.isEnabled = !state.isSigningIn

        passwordErrorLabel.isHidden = !state.passwordEditedOnce || state.isPasswordValid


        signInButton.isEnabled = state.isFormValid

        signInButton.isHidden = state.isSigningIn

        activityIndicator.isHidden = !state.isSigningIn

    }

}





enum SignInFormAction: Action {

    case requested

    case success

    case error(Error)

    case reset


    case emailEdited

    case emailUpdated(String)


    case passwordEdited

    case passwordUpdated(String)

}






enum SignInFormAction: Action {

    case request

    case handleSuccess

    case handleError(Error)

    case reset


    case touchEmail

    case updateEmail(String)


    case touchPassword

    case updatePassword(String)

}





func signInReducer(action: Action, state: SignInState) -> SignInState {

    guard let action = action as? SignInFormAction else {

        return state

    }


    var state = state

    switch action {

    case .request:

        state.isSigningIn = true

    case .handleSuccess:

        state.isSigningIn = false

    case .handleError(let error):

        state.isSigningIn = false

        state.serverError = error.localizedDescription

    case .touchEmail:

        state.emailEditedOnce = true

    case .updateEmail(let email):

        state.email = email

    case .touchPassword:

        state.passwordEditedOnce = true

    case .updatePassword(let password):

        state.password = password

    }

    

    return state

}





func createActions() {

    store.dispatch(StoplightAction.turnGreen)

}


func createActionsAsync() {

    store.dispatch(StoplightAction.turnYellow)


    let when = DispatchTime.now() + 4

    DispatchQueue.main.asyncAfter(deadline: when) {

        self.store.dispatch(StoplightAction.turnRed)

    }

}






enum Managed {

    class Contact: NSManagedObject {

        var name: String!

        var phone: String!

    }

}


struct Contact {

    let id: URL

    let name: String

    let phone: String


    init(contact: Managed.Contact) {

        id = contact.objectID.uriRepresentation()

        name = contact.name

        phone = contact.phone

    }

}


struct AppState {}


enum ContactAction: Action {

    case load([Contact])

    case error(Error)

}


class CoreDataExample {

    var store: Store<AppState>!

    var context: NSManagedObjectContext!


    func loadContacts() {

        let fetchRequest = NSFetchRequest<Managed.Contact>(entityName: "Contact")

        do {

            let contacts = try fetchRequest.execute()

            store.dispatch(ContactAction.load(contacts.map(Contact.init)))

        } catch {

            store.dispatch(ContactAction.error(error))

        }

    }



}



            contacts.count

            // => 10_000_000





enum Loading<T> {

    case loading

    case loaded(T)

    case error(Error)

}


enum TypeSafeExample {

    struct SignInForm {}

    struct User {}


enum AppState {

    case unauthenticated(SignInForm)

    case authenticated(AuthenticatedState)

}


struct AuthenticatedState {

    let user: User

    let contacts: Loading<[Contact]>

}




    override func viewWillAppear(animated: Bool) {

        store.subscribe(self, selector: SignInViewModel.init)

    }


    override func viewWillDisappear(animated: Bool) {

        store.dispatch(SignInFormAction.reset)

        store.unsubscribe(self)

        signInRequest?.cancel()

    }


    override func viewDidLoad() {

        emailField.addTarget(self, action: .emailUpdated, forControlEvents: .EditingChanged)

        passwordField.addTarget(self, action: .passwordUpdated, forControlEvents: .EditingChanged)

    }


    struct SignInViewModel {

        let email: String

        let password: String

        let serverError: String?


        let isSigningIn: Bool

        let isFormValid: Bool


        let hideServerError: Bool

        let hideEmailError: Bool

        let hidePasswordError: Bool


        init?(state: AppState) {

            // ...

            fatalError()

        }

    }


    func newState(viewModel: SignInViewModel?) {

        guard let viewModel = viewModel else { return }


        serverErrorLabel.text = viewModel.serverError

        serverErrorView.hidden = viewModel.hideServerError


        emailField.text = viewModel.email

        emailField.enabled = !viewModel.isSigningIn

        emailErrorLabel.hidden = viewModel.hideEmailError


        passwordField.text = viewModel.password

        passwordField.enabled = !viewModel.isSigningIn

        passwordErrorLabel.hidden = viewModel.hidePasswordError


        signInButton.enabled = viewModel.isFormValid

        signInButton.hidden = viewModel.isSigningIn

        activityIndicator.hidden = !viewModel.isSigningIn

    }





UIView.animateWithDuration(0.3) {

    self.emailErrorLabel.hidden = hideEmailError

    self.passwordErrorLabel.hidden = hidePasswordError

}




extension UITableView {

    func iDontKnowHowToFixThis() {

        reloadData()

    }

}




func newState(state: AppState) {

    let (diff, updates) = lastState.rows.tableDiff(state.rows)

    lastState = state


    tableView.indexPathsForVisibleRows

    for indexPath in updates.intersection(Set(tableView.indexPathsForVisibleRows ?? [])) {

        let cell = tableView.cellForRow(at: indexPath) as! CustomCell

        cell.update(state.rows[indexPath.row])

    }


    // automatically insert, delete, and move:

    tableView.applyDiff(diff)

}




func newState(state: AppState) {

    let (diff, updates) = lastState.rows.tableDiff(state.rows)

    lastState = state


    let visible = Set(tableView.indexPathsForVisibleRows ?? [])

    for indexPath in updates.intersection(visible) {

        let cell = tableView.cellForRow(at: indexPath) as! CustomCell

        cell.update(state.rows[indexPath.row])

    }


    // automatically insert, delete, and move:

    tableView.applyDiff(diff)

}




@IBAction func linkTapped() {

    let vc = NextViewController()

    navigationController?.pushViewController(vc, animated: true)

}



@IBAction func linkTapped() {

    let vc = NextViewController()

    navigationController?.pushViewController(vc, animated: true)

}




enum RouteExample {

    struct AppState {

        var route: [String]

    }


    func asdf() {

        let state = AppState(route: ["auth", "signIn"])


        UIViewController.goTo(route: ["home", "movies", "kubo"])

    }


}





func application(_ app: UIApplication,

                 open url: URL,

                 options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool

{

    // build the whole stack!

    return true

}



func application(_ app: UIApplication, open url: URL,

    options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

    // build the whole stack!

    return true

}




protocol Routable {

    func changeRouteSegment(from: RouteElementIdentifier,

                            to: RouteElementIdentifier,

                            completionHandler: RoutingCompletionHandler) -> Routable


    func pushRouteSegment(routeElementIdentifier: RouteElementIdentifier,

                          completionHandler: RoutingCompletionHandler) -> Routable


    func popRouteSegment(routeElementIdentifier: RouteElementIdentifier,

                         completionHandler: RoutingCompletionHandler)

}



protocol Routable {

    func changeRouteSegment(from: ..., to: ..., completion: ...) -> Routable


    func pushRouteSegment(identifier: ..., completion: ... ) -> Routable


    func popRouteSegment(identifier: ... , completionI: ... )

}





    final class Store {

        var state: State

        var subscriber: Subscriber?


        init(initialState: State) {

            state = initialState

        }


        func dispatch(action: Action) {

            // state = update state ...

            subscriber?.newState(state)

        }


        func subscribe(view: Subscriber) {

            subscriber = view

        }

    }




    func testInvalidEmailAndPassword() {

        state.signInForm.email = "mark@facebook"

        state.signInForm.emailEditedOnce = true

        state.signInForm.password = "pas"

        state.signInForm.passwordEditedOnce = true

        controller.newState(state.signInForm)

        FBSnapshotVerifyView(window)

    }



    override func setUp() {

        super.setUp()

        recordMode = false

        state = AppState()

    }




    func testInvalidEmailAndPassword() {

        let state = AppState()

        state.signInForm.email = "mark@facebook"

        state.signInForm.emailEditedOnce = true

        state.signInForm.password = "pas"

        state.signInForm.passwordEditedOnce = true

        controller.newState(state.signInForm)

        FBSnapshotVerifyView(window)

    }



    func testInvalidEmailAndPassword() {

        let state = AppState()

        state.signInForm.email = "mark@facebook"

        state.signInForm.emailEditedOnce = true

        state.signInForm.password = "pas"

        state.signInForm.passwordEditedOnce = true

        controller.newState(state.signInForm)

        FBSnapshotVerifyView(window)

    }




protocol SignInHandler: class {

    func signIn(credential: Credential)

    func forgotPassword()

}



struct Credential {


}



enum Coordinator {




final class SignInViewController: UIViewController {

    weak var handler: SignInHandler!


    @IBAction func signInButtonPressed(sender: AnyObject) {

        let credential = Credential()

        handler.signIn(credential: credential)

    }


    @IBAction func forgotPasswordButtonPressed(sender: AnyObject) {

        handler.forgotPassword()

    }

}






func signIn(credential: Credential) {

    store.dispatch(SignInFormAction.request)

    api.signIn(with: credential) { [store] result in

        DispatchQueue.main.async {

            switch result {

            case .success(let user):

                store.dispatch(SignInFormAction.handleSuccess)

                store.dispatch(AuthenticationAction.signIn(user))

            case .error(let error):

                store.dispatch(SignInFormAction.handleError(error))

            }

        }

    }

}