//
//  Parser.swift
//  passKit
//
//  Created by Danny Moesch on 16.08.18.
//  Copyright © 2018 Bob Sun. All rights reserved.
//

class Parser {
    let firstLine: String
    let additionsSection: String
    let purgedAdditionalLines: [String]

    // The parsing process is expensive. This field makes sure it is only done once if actually needed.
    private(set) lazy var additionFields = getAdditionFields()

    init(plainText: String) {
        let splittedPlainText = plainText.splitByNewline()

        self.firstLine = splittedPlainText.first!
        self.additionsSection = splittedPlainText[1...].joined(separator: "\n")
        self.purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty }
    }

    private func getAdditionFields() -> [AdditionField] {
        var additions: [AdditionField] = []
        var unknownIndex: UInt = 0
        var lineNumber = purgedAdditionalLines.startIndex
        while lineNumber < purgedAdditionalLines.count {
            let line = purgedAdditionalLines[lineNumber]
            lineNumber += 1
            var (key, value) = Self.getKeyValuePair(from: line)
            if key == nil {
                unknownIndex += 1
                key = Constants.unknown(unknownIndex)
            } else if value == Constants.MULTILINE_WITH_LINE_BREAK_INDICATOR {
                value = gatherMultilineValue(startingAt: &lineNumber, removingLineBreaks: false)
            } else if value == Constants.MULTILINE_WITHOUT_LINE_BREAK_INDICATOR {
                value = gatherMultilineValue(startingAt: &lineNumber, removingLineBreaks: true)
            }
            additions.append(key! => value)
        }
        return additions
    }

    private func gatherMultilineValue(startingAt lineNumber: inout Int, removingLineBreaks: Bool) -> String {
        var result = ""
        guard lineNumber < purgedAdditionalLines.count else {
            return result
        }
        // swiftlint:disable:next unused_enumerated
        let numberInitialBlanks = purgedAdditionalLines[lineNumber].enumerated().first {
            $1 != Character(Constants.BLANK)
        }?.0 ?? purgedAdditionalLines[lineNumber].count
        guard numberInitialBlanks != 0 else {
            return result
        }
        let initialBlanks = String(repeating: Constants.BLANK, count: numberInitialBlanks)

        while lineNumber < purgedAdditionalLines.count, purgedAdditionalLines[lineNumber].starts(with: initialBlanks) {
            result.append(String(purgedAdditionalLines[lineNumber].dropFirst(numberInitialBlanks)))
            result.append(Constants.getSeparator(breakingLines: !removingLineBreaks))
            lineNumber += 1
        }
        return result.trimmed
    }

    /// Split line from password file in to a key-value pair separted by `: `.
    ///
    /// - Parameter line: Line from a password file
    /// - Returns: Pair of two `String`s of which the first one can be 'nil'
    static func getKeyValuePair(from line: String) -> (key: String?, value: String) {
        let items = line.components(separatedBy: ": ").map { String($0).trimmingCharacters(in: .whitespaces) }
        var key: String?
        var value = ""
        if items.count == 1 || (items[0].isEmpty && items[1].isEmpty) {
            // No ': ' found, or empty on both sides of ': '.
            value = line
            // "otpauth" special case
            if value.hasPrefix(Constants.OTPAUTH_URL_START) {
                key = Constants.OTPAUTH
            }
        } else {
            if !items[0].isEmpty {
                key = items[0]
            }
            value = items[1]
        }
        return (key, value)
    }
}
