<commit_before>//
//  PgoApi.swift
//  pgoapi
//
//  Created by <NAME> on 2016/07/28.
//  Copyright © 2016 MC. All rights reserved.
//

import Foundation
import BoltsSwift
import ProtocolBuffers

public class PgoApi
{
    public enum ApiError: ErrorType
    {
        case NotLoggedIn
    }
    
    final public class Builder
    {
        private let api: PgoApi
        private let authToken: AuthToken
        private var messages: [RequestMessage] = []
        
        private var responseConverterBuilder = ApiResponseDataConverter.Builder()
        
        private let location: Location?
        
        private init(api: PgoApi, authToken: AuthToken, location: Location?)
        {
            self.api = api
            self.authToken = authToken
            self.location = location
        }
    }
    
    let network: Network
    let authToken: AuthToken
    
    private var apiEndpoint: String?
    private(set) var loggedIn = false
    
    public init(network: Network, authToken: AuthToken)
    {
        self.network = network
        self.authToken = authToken
    }
    
    public func builder(location: Location? = nil) -> Builder
    {
        return Builder(api: self, authToken: authToken, location: location)
    }
    
    public func login() -> Task<(PgoApi, ApiResponse)>
    {
        precondition(NSThread.isMainThread(), "\(#function) should only be called from the main thread")
        
        // Capture self strongly, so that the instance stays alive until the login call finishes
        return executeInternal(builder: getLoginBuilder())
        .continueOnSuccessWith(.MainThread)
        {
            response in
            self.apiEndpoint = "https://\(response.response.apiUrl)/rpc"
            self.loggedIn = true
            return (self, response)
        }
    }
    
    public func execute(builder: Builder) -> Task<ApiResponse>
    {
        precondition(NSThread.isMainThread(), "\(#function) should only be called from the main thread")
        
        guard loggedIn else
        {
            return Task(error: ApiError.NotLoggedIn)
        }
        // Explicityly force unwrap apiEndpoint. If we don't have one after being logged in, the app is in an inconsistent state
        return executeInternal(apiEndpoint!, builder: builder)
    }
    
    private func executeInternal(endPoint: String = EndPoint.Rpc, builder: Builder) -> Task<ApiResponse>
    {
        let request = builder.build()
        return request.request.execute(endPoint, decoder: request.decoder)
    }
    
    private func getLoginBuilder() -> Builder
    {
        return builder()
              .getPlayer()
              .getHatchedEggs()
              .getInventory()
              .checkAwardedBadges()
              .downloadSettings()
    }
}

private extension PgoApi.Builder
{
    func build() -> (request: RpcRequest, decoder: Decoder<ApiResponse, ApiResponseDataConverter>)
    {
        let params = RpcParams(authToken: authToken, location: location)
        let request = RpcRequest(network: api.network, params: params, messages: messages)
        let decoder = Decoder(converter: responseConverterBuilder.build())
        
        return (request: request, decoder: decoder)
    }
}

// This extensions adds all the methods to build up the response
public extension PgoApi.Builder
{
    private typealias RequestType = Pogoprotos.Networking.Requests.RequestType
    
    /// Force unwrap all building. It should be a runtime error for a builder not to build
    
    public func getPlayer() -> PgoApi.Builder
    {
        let messageBuilder = Pogoprotos.Networking.Requests.Messages.GetPlayerMessage.Builder()
        let responseType = Pogoprotos.Networking.Responses.GetPlayerResponse.self
        return addMessage(try! messageBuilder.build(), type: .GetPlayer, responseType: responseType)
    }

    func getHatchedEggs() -> PgoApi.Builder<commit_msg>Remove the requirement for executing an API request from the main thread<commit_after>//
//  PgoApi.swift
//  pgoapi
//
//  Created by <NAME> on 2016/07/28.
//  Copyright © 2016 MC. All rights reserved.
//

import Foundation
import BoltsSwift
import ProtocolBuffers

public class PgoApi
{
    public enum ApiError: ErrorType
    {
        case NotLoggedIn
    }
    
    final public class Builder
    {
        private let api: PgoApi
        private let authToken: AuthToken
        private var messages: [RequestMessage] = []
        
        private var responseConverterBuilder = ApiResponseDataConverter.Builder()
        
        private let location: Location?
        
        private init(api: PgoApi, authToken: AuthToken, location: Location?)
        {
            self.api = api
            self.authToken = authToken
            self.location = location
        }
    }
    
    let network: Network
    let authToken: AuthToken
    
    private var apiEndpoint: String?
    private(set) var loggedIn = false
    private var lock: OSSpinLock = OS_SPINLOCK_INIT
    
    public init(network: Network, authToken: AuthToken)
    {
        self.network = network
        self.authToken = authToken
    }
    
    public func builder(location: Location? = nil) -> Builder
    {
        return Builder(api: self, authToken: authToken, location: location)
    }
    
    public func login() -> Task<(PgoApi, ApiResponse)>
    {
        // Capture self strongly, so that the instance stays alive until the login call finishes
        return executeInternal(builder: getLoginBuilder())
        .continueOnSuccessWith(network.processingExecutor)
        {
            response in
            self.sync
            {
                self.apiEndpoint = "https://\(response.response.apiUrl)/rpc"
                self.loggedIn = true
            }
            return (self, response)
        }
    }
    
    public func execute(builder: Builder) -> Task<ApiResponse>
    {
        let (isLoggedIn, endPoint) = sync { return (loggedIn, apiEndpoint) }
        
        guard isLoggedIn else
        {
            return Task(error: ApiError.NotLoggedIn)
        }
        // Explicityly force unwrap apiEndpoint. If we don't have one after being logged in, the app is in an inconsistent state
        return executeInternal(endPoint!, builder: builder)
    }
    
    private func executeInternal(endPoint: String = EndPoint.Rpc, builder: Builder) -> Task<ApiResponse>
    {
        let request = builder.build()
        return request.request.execute(endPoint, decoder: request.decoder)
    }
    
    private func getLoginBuilder() -> Builder
    {
        return builder()
              .getPlayer()
              .getHatchedEggs()
              .getInventory()
              .checkAwardedBadges()
              .downloadSettings()
    }
    
    private func sync(@noescape closure: () -> ())
    {
        OSSpinLockLock(&lock)
        defer { OSSpinLockUnlock(&lock) }
        closure()
    }
    
    private func sync<T>(@noescape closure: () -> T) -> T
    {
        OSSpinLockLock(&lock)
        defer { OSSpinLockUnlock(&lock) }
        return closure()
    }
}

private extension PgoApi.Builder
{
    func build() -> (request: RpcRequest, decoder: Decoder<ApiResponse, ApiResponseDataConverter>)
    {
        let params = RpcParams(authToken: authToken, location: location)
        let request = RpcRequest(network: api.network, params: params, messages: messages)
        let decoder = Decoder(converter: responseConverterBuilder.build())
        
        return (request: request, decoder: decoder)
    }
}

// This extensions adds all the methods to build up the response
public extension PgoApi.Builder
{
    private typealias RequestType = Pogoprotos.Networking.Requests.RequestType
    
    /// Force unwrap all building. It should be a runtime error for a builder not to build
    
    public func getPlayer() -> PgoApi.Builder
    {
        let messageBuilder = Pogoprotos.Networking.Requests.Messages.GetPlayerMessage.Builder()
        let responseType = Pogoprotos.Networking.Responses.GetPlayerResponse.self
        return addMessage(try! messageBuilder.build(), type: .GetPlayer, responseType: responseType)
    }

    func getHatchedEggs() -> PgoApi.Builder
{'fraction_non_alphanumeric': 0.08446234590504088, 'fraction_numerical': 0.0029293299157817647, 'mean_word_length': 2.294732609569763, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 33, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1772388', 'n_tokens_mistral': 2236, 'n_tokens_neox': 2187, 'n_words': 628}