//
//  BaseTestDownloaderDelegate.swift
//  KeymanEngineTests
//
//  Created by Joshua Horton on 5/28/20.
//  Copyright © 2020 SIL International. All rights reserved.
//

import Foundation
import XCTest
@testable import KeymanEngine

extension TestUtils.Downloading {
  class ExpectationDownloaderDelegate: HTTPDownloadDelegate {
    enum Method: String {
      case RequestStarted
      case RequestFinished
      case RequestFailed
      case QueueFinished
      case QueueCancelled
    }

    typealias ExpectationMetBlock = (ExpectationQueueEntry) -> Bool

    struct ExpectationQueueEntry {
      let method: Method
      let request: HTTPDownloadRequest?
      let queue: HTTPDownloader?
      let completion: ExpectationMetBlock?
      let expectation: XCTestExpectation

      init(method: Method, request: HTTPDownloadRequest, completion: ExpectationMetBlock? = nil) {
        self.init(method: method, request: request, queue: nil, completion: completion)
      }

      init(method: Method, queue: HTTPDownloader, completion: ExpectationMetBlock? = nil) {
        self.init(method: method, request: nil, queue: queue, completion: completion)
      }

      private init(method: Method, request: HTTPDownloadRequest?, queue: HTTPDownloader?, completion: ExpectationMetBlock? = nil) {
        self.method = method
        self.request = request
        self.queue = queue
        self.completion = completion

        // Now to dynamically build the description test for the Expectation, then the Expectation itself
        var description: String
        switch method {
          case .RequestStarted, .RequestFinished, .RequestFailed:
              description = "\(method.rawValue) for \(request!.typeCode.rawValue) from \(request!.url)"
          case .QueueFinished, .QueueCancelled:
              description = "\(method.rawValue)"
        }
        self.expectation = XCTestExpectation(description: description)
      }
    }

    private var expectationQueue: [ExpectationQueueEntry] = []
    private var expectedAbsences: [ExpectationQueueEntry] = []

    private func doExpectationMatch(method: Method, request: HTTPDownloadRequest? = nil, queue: HTTPDownloader? = nil) {

      if expectationQueue.count == 0 {
        return
      } else {
        let head = expectationQueue[0]
        if head.method == method && head.request == request && head.queue == queue {
          expectationQueue.removeFirst()

          // Now to look to fulfill the expectation.
          if head.completion != nil {
            if head.completion!(head) {
              head.expectation.fulfill()
            }
          } else {
            head.expectation.fulfill()
          }
        }
      }
    }

    func downloadRequestStarted(_ request: HTTPDownloadRequest) {
      doExpectationMatch(method: .RequestStarted, request: request)
    }

    func downloadRequestFinished(_ request: HTTPDownloadRequest) {
      doExpectationMatch(method: .RequestFinished, request: request)
    }

    func downloadRequestFailed(_ request: HTTPDownloadRequest, with error: Error?) {
      doExpectationMatch(method: .RequestFailed, request: request)
    }

    func downloadQueueFinished(_ queue: HTTPDownloader) {
      doExpectationMatch(method: .QueueFinished, queue: queue)
    }

    func downloadQueueCancelled(_ queue: HTTPDownloader) {
      doExpectationMatch(method: .QueueCancelled, queue: queue)
    }

    func expect(method: Method, request: HTTPDownloadRequest, completion: ExpectationMetBlock? = nil) {
      expectationQueue.append(ExpectationQueueEntry(method: method, request: request, completion: completion))
    }

    func expect(method: Method, queue: HTTPDownloader, completion: ExpectationMetBlock? = nil) {
      expectationQueue.append(ExpectationQueueEntry(method: method, queue: queue, completion: completion))
    }

    func expectAbsence(method: Method, request: HTTPDownloadRequest, completion: ExpectationMetBlock? = nil) {
      let entry = ExpectationQueueEntry(method: method, request: request, completion: completion)
      entry.expectation.isInverted = true
      expectedAbsences.append(entry)
    }

    func expectAbsence(method: Method, queue: HTTPDownloader, completion: ExpectationMetBlock? = nil) {
      let entry = ExpectationQueueEntry(method: method, queue: queue, completion: completion)
      entry.expectation.isInverted = true
      expectedAbsences.append(entry)
    }

    func sequentialWait(testCase: XCTestCase, timeout: TimeInterval = 10) {
//      while expectationQueue.count > 0 {
//        testCase.wait(for: [expectationQueue[0].expectation], timeout: timeout)
//      }
      var expectationList: [XCTestExpectation] = expectationQueue.map { entry in
        return entry.expectation
      }

      let absenceList: [XCTestExpectation] = expectedAbsences.map { entry in
        return entry.expectation
      }

      expectationList.append(contentsOf: absenceList)
      testCase.wait(for: expectationList, timeout: timeout, enforceOrder: true)
    }
  }
}
