import Foundation
import SourceKittenFramework
import SwiftLintCore

private extension SwiftLintFile {
    func violatingRanges(for pattern: String) -> [NSRange] {
        match(pattern: pattern, excludingSyntaxKinds: SyntaxKind.commentAndStringKinds)
    }
}

@DisabledWithoutSourceKit
struct VerticalWhitespaceOpeningBracesRule: Rule {
    var configuration = SeverityConfiguration<Self>(.warning)

    private static let nonTriggeringExamples = #examples([
        "[1, 2].map { $0 }.foo()",
        "[1, 2].map { $0 }.filter { num in true }",
        "// [1, 2].map { $0 }.filter { num in true }",
        """
        /*
            class X {

                let x = 5

            }
        */
        """,
    ])

    private static let violatingToValidExamples: [Example: Example] = #corrections([
        """
        if x == 5 {
        ↓
          print("x is 5")
        }
        """: """
            if x == 5 {
              print("x is 5")
            }
            """,
        """
        if x == 5 {
        ↓

          print("x is 5")
        }
        """: """
            if x == 5 {
              print("x is 5")
            }
            """,
        """
        struct MyStruct {
        ↓
          let x = 5
        }
        """: """
            struct MyStruct {
              let x = 5
            }
            """,
        """
        class X {
          struct Y {
        ↓
            class Z {
            }
          }
        }
        """: """
            class X {
              struct Y {
                class Z {
                }
              }
            }
            """,
        """
        [
        ↓
        1,
        2,
        3
        ]
        """: """
            [
            1,
            2,
            3
            ]
            """,
        """
        foo(
        ↓
          x: 5,
          y:6
        )
        """: """
            foo(
              x: 5,
              y:6
            )
            """,
        """
        func foo() {
        ↓
          run(5) { x in
            print(x)
          }
        }
        """: """
            func foo() {
              run(5) { x in
                print(x)
              }
            }
            """,
        """
        KingfisherManager.shared.retrieveImage(with: url, options: nil, progressBlock: nil) { image, _, _, _ in
        ↓
            guard let img = image else { return }
        }
        """: """
            KingfisherManager.shared.retrieveImage(with: url, options: nil, progressBlock: nil) { image, _, _, _ in
                guard let img = image else { return }
            }
            """,
        """
        foo({ }) { _ in
        ↓
          self.dismiss(animated: false, completion: {
          })
        }
        """: """
            foo({ }) { _ in
              self.dismiss(animated: false, completion: {
              })
            }
            """,
    ])

    private let pattern = "([{(\\[][ \\t]*(?:[^\\n{]+ in[ \\t]*$)?)((?:\\n[ \\t]*)+)(\\n)"
}

extension VerticalWhitespaceOpeningBracesRule: OptInRule {
    static let description = RuleDescription(
        identifier: "vertical_whitespace_opening_braces",
        name: "Vertical Whitespace after Opening Braces",
        description: "Don't include vertical whitespace (empty line) after opening braces",
        kind: .style,
        nonTriggeringExamples: (violatingToValidExamples.values + nonTriggeringExamples).sorted(),
        triggeringExamples: Array(violatingToValidExamples.keys).sorted(),
        corrections: violatingToValidExamples.removingViolationMarkers()
    )

    func validate(file: SwiftLintFile) -> [StyleViolation] {
        let patternRegex: NSRegularExpression = regex(pattern)

        return file.violatingRanges(for: pattern).map { violationRange in
            let substring = file.contents.substring(from: violationRange.location, length: violationRange.length)
            let substringRange = NSRange(location: 0, length: substring.count)
            let matchResult = patternRegex.firstMatch(in: substring, options: [], range: substringRange)!
            let violatingSubrange = matchResult.range(at: 2)
            let characterOffset = violationRange.location + violatingSubrange.location + 1

            return StyleViolation(
                ruleDescription: Self.description,
                severity: configuration.severity,
                location: Location(file: file, characterOffset: characterOffset)
            )
        }
    }
}

extension VerticalWhitespaceOpeningBracesRule: CorrectableRule {
    func correct(file: SwiftLintFile) -> Int {
        let violatingRanges = file.ruleEnabled(violatingRanges: file.violatingRanges(for: pattern), for: self)
        guard violatingRanges.isNotEmpty else {
            return 0
        }
        let patternRegex = regex(pattern)
        var fileContents = file.contents
        for violationRange in violatingRanges.reversed() {
            fileContents = patternRegex.stringByReplacingMatches(
                in: fileContents,
                options: [],
                range: violationRange,
                withTemplate: "$1$3"
            )
        }
        file.write(fileContents)
        return violatingRanges.count
    }
}
