#!/usr/bin/swift sh
import func Darwin.fputs
import var Darwin.stderr
import PMKFoundation  // PromiseKit/Foundation ~> 3.3
import LegibleError   // @mxcl ~> 1.0
import Foundation
import PromiseKit     // @mxcl ~> 6.8
import Path           // mxcl/Path.swift ~> 0.15

let env = ProcessInfo.processInfo.environment
let token = env["GITHUB_TOKEN"] ?? env["GITHUB_ACCESS_TOKEN"]!
let slug = env["TRAVIS_REPO_SLUG"]!
let tag = env["TRAVIS_TAG"]!

func fatal(message: String) -> Never {
    fputs("error: \(message)\n", stderr)
    exit(1)
}
func fatal(error: Error) -> Never {
    fatal(message: "\(error.legibleLocalizedDescription)\n\n\(error.legibleDescription)")
}

struct Repo: Decodable {
    let description: String
    let license: License
    struct License: Decodable {
        let spdx_id: String
    }
}

struct Package: Decodable {
    let swiftLanguageVersions: [String]
    let targets: [Target]
    struct Target: Decodable {
        let path: String?
        let type: Kind
        enum Kind: String, Decodable {
            case regular
            case test
        }
    }
}

extension URLRequest {
    init(github path: String) {
        let url = URL(string: "https://api.github.com\(path)")!
        self.init(url: url)
        setValue("token \(token)", forHTTPHeaderField: "Authorization")
        setValue("application/json", forHTTPHeaderField: "Content-Type")
        setValue("application/json", forHTTPHeaderField: "Accept")
    }
}

func description() -> Promise<Repo> {
    let rq = URLRequest(github: "/repos/\(slug)")
    return firstly {
        URLSession.shared.dataTask(.promise, with: rq).validate()
    }.map { data, _ in
        try JSONDecoder().decode(Repo.self, from: data)
    }
}

struct User: Decodable {
    let name: String
    let email: String
}

func email() -> Promise<User> {
    let rq = URLRequest(github: "/user")
    return firstly {
        URLSession.shared.dataTask(.promise, with: rq).validate()
    }.map { data, _ in
        try JSONDecoder().decode(User.self, from: data)
    }
}

func dumpPackage() -> Promise<Package> {
    let task = Process()
    task.launchPath = "/usr/bin/swift"
    task.arguments = ["package", "dump-package"]
    return firstly {
        task.launch(.promise)
    }.map { out, _ in
        out.fileHandleForReading.readDataToEndOfFile()
    }.map { data in
        try JSONDecoder().decode(Package.self, from: data)
    }
}

var defaultSwiftVersion: String {
    let task = Process()
    task.launchPath = "/usr/bin/swift"
    task.arguments = ["--version"]

    func extract(input: String) -> String {
        let range = input.range(of: #"Apple Swift version \d+\.\d+"#, options: .regularExpression)!
        return String(input[range].split(separator: " ").last!)
    }

    return try! firstly {
        task.launch(.promise)
    }.compactMap { out, _ in
        String(data: out.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)
    }.map { out in
        extract(input: out)
    }.wait()
}

func podspec(repo: Repo, user: User, pkg: Package) -> (Substring, String) {
    let (owner, name) = { ($0[0], $0[1]) }(slug.split(separator: "/"))
    let swiftVersion = pkg.swiftLanguageVersions.min() ?? defaultSwiftVersion
    let targets = pkg.targets.filter{ $0.type == .regular }
    guard targets.count == 1 else { fatal(message: "Too many targets for this script!") }
    guard let sources = targets[0].path else { fatal(message: "Target has no path!") }
    return (name, """
    Pod::Spec.new do |s|
      s.name = '\(name)'
      s.author = { '\(user.name)': '\(user.email)' }
      s.source = { git: "https://github.com/\(slug).git", tag: '\(tag)' }
      s.version = '\(tag)'
      s.summary = '\(repo.description)'
      s.license = '\(repo.license.spdx_id)'
      s.homepage = "https://github.com/\(slug)"
      s.social_media_url = 'https://twitter.com/\(owner)'
      s.osx.deployment_target = '10.10'
      s.ios.deployment_target = '8.0'
      s.tvos.deployment_target = '9.0'
      s.watchos.deployment_target = '2.0'
      s.source_files = '\(sources)/*.swift'
      s.swift_version = '\(swiftVersion)'
    end
    """)
}

func publishRelease() throws -> Promise<Void> {
    struct Input: Encodable {
        let tag_name = tag
        let name = tag
        let body = ""
    }

    var rq = URLRequest(github: "/repos/\(slug)/releases")
    rq.httpMethod = "POST"
    rq.httpBody = try JSONEncoder().encode(Input())
    return URLSession.shared.dataTask(.promise, with: rq).validate().asVoid()
}

switch CommandLine.arguments[1] {
case "generate-podspec":
    firstly {
        when(fulfilled: description(), email(), dumpPackage())
    }.map(podspec).done { name, podspec in
        try podspec.write(toFile: "\(name).podspec", atomically: false, encoding: .utf8)
        exit(0)
    }.catch {
        fatal(error: $0)
    }
case "publish-release":
    try publishRelease().done {
        exit(0)
    }.catch {
        fatal(error: $0)
    }
default:
    fatal(message: "invalid usage")
}

RunLoop.main.run()
