//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import CompletionScoring
import Foundation
import SwiftExtensions
@_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions

package struct RepeatableRandomNumberGenerator: RandomNumberGenerator {
  private let seed: [UInt64]
  private static let startIndex = (0, 1, 2, 3)
  private var nextIndex = Self.startIndex

  package init() {
    self.seed = try! PropertyListDecoder().decode(
      [UInt64].self,
      from: loadTestResource(name: "RandomSeed", withExtension: "plist")
    )
  }

  @discardableResult
  func increment(value: inout Int, range: Range<Int>) -> Bool {
    value += 1
    let wrapped = (value == range.upperBound)
    if wrapped {
      value = range.lowerBound
    }
    return wrapped
  }

  mutating func advance() {
    // This iterates through "K choose N" or "seed.count choose 4" unique combinations of 4 seed indexes. Given 1024 values in seed, that produces 45,545,029,376 unique combination of the values.
    if increment(value: &nextIndex.3, range: (nextIndex.2 + 1)..<(seed.count - 0)) {
      if increment(value: &nextIndex.2, range: (nextIndex.1 + 1)..<(seed.count - 1)) {
        if increment(value: &nextIndex.1, range: (nextIndex.0 + 1)..<(seed.count - 2)) {
          if increment(value: &nextIndex.0, range: 0..<(seed.count - 3)) {
            nextIndex = Self.startIndex
            return
          }
          nextIndex.1 = (nextIndex.0 + 1)
        }
        nextIndex.2 = (nextIndex.1 + 1)
      }
      nextIndex.3 = (nextIndex.2 + 1)
    }
  }

  package mutating func next() -> UInt64 {
    let result = seed[nextIndex.0] ^ seed[nextIndex.1] ^ seed[nextIndex.2] ^ seed[nextIndex.3]
    advance()
    return result
  }

  static func generateSeed() {
    let numbers: [UInt64] = (0..<1024).map { _ in
      let lo = UInt64.random(in: 0...UInt64.max)
      let hi = UInt64.random(in: 0...UInt64.max)
      return (hi << 32) | lo
    }
    let header =
      """
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      <plist version="1.0">
      <array>
      """
    let body = numbers.map { number in "    <integer>\(number)</integer>" }
    let footer =
      """
      </array>
      </plist>
      """

    print(([header] + body + [footer]).joined(separator: "\n"))
  }

  package mutating func randomLowercaseASCIIString(lengthRange: ClosedRange<Int>) -> String {
    let length = lengthRange.randomElement(using: &self)
    let utf8Bytes = (0..<length).map { _ in
      UTF8Byte.lowercaseAZ.randomElement(using: &self)
    }
    return String(bytes: utf8Bytes, encoding: .utf8).unwrap(orFail: "ASCII strings are always valid UTF8 sequences")
  }

  package mutating func randomLowercaseASCIIStrings(
    countRange: ClosedRange<Int>,
    lengthRange: ClosedRange<Int>
  ) -> [String] {
    let count = countRange.randomElement(using: &self)
    var strings: [String] = []
    for _ in (0..<count) {
      strings.append(randomLowercaseASCIIString(lengthRange: lengthRange))
    }
    return strings
  }
}

/// The path to the INPUTS directory of shared test projects.
private let skTestSupportInputsDirectory: URL = {
  guard let resourceURL = Bundle.module.resourceURL else {
    fatalError("could not determine resource URL for bundle: \(Bundle.module)")
  }
  guard FileManager.default.fileExists(at: resourceURL) else {
    fatalError("missing resources \(resourceURL)")
  }
  return resourceURL.appending(components: "INPUTS", directoryHint: .isDirectory).standardizedFileURL
}()

func loadTestResource(name: String, withExtension ext: String) throws -> Data {
  let file =
    skTestSupportInputsDirectory
    .appending(component: "\(name).\(ext)")
  return try Data(contentsOf: file)
}

extension ClosedRange {
  func randomElement<Generator: RandomNumberGenerator>(using randomness: inout Generator) -> Element {
    return randomElement(using: &randomness)!  // Closed ranges always have a value
  }
}
