import Foundation

/// Simple wrapper around `Foundation.XMLParser`.
internal class AEXMLParser: NSObject, XMLParserDelegate {

    // MARK: - Properties

    let document: AEXMLDocument
    let data: Data

    var currentParent: AEXMLElement?
    var currentElement: AEXMLElement?
    var currentValue = String()

    var parseError: Error?

    private lazy var trimWhitespace: Bool = {
        let trim = self.document.options.parserSettings.shouldTrimWhitespace
        return trim
    }()

    // MARK: - Lifecycle

    init(document: AEXMLDocument, data: Data) {
        self.document = document
        self.data = data
        currentParent = document

        super.init()
    }

    // MARK: - API

    func parse() throws {
        let parser = XMLParser(data: data)
        parser.delegate = self

        parser.shouldProcessNamespaces = document.options.parserSettings.shouldProcessNamespaces
        parser.shouldReportNamespacePrefixes = document.options.parserSettings.shouldReportNamespacePrefixes
        parser.shouldResolveExternalEntities = document.options.parserSettings.shouldResolveExternalEntities

        let success = parser.parse()

        if !success {
            guard let error = parseError else { throw AEXMLError.parsingFailed }
            throw error
        }
    }

    // MARK: - XMLParserDelegate

    func parser(_ parser: XMLParser,
                      didStartElement elementName: String,
                      namespaceURI: String?,
                      qualifiedName qName: String?,
                      attributes attributeDict: [String : String]) {
        currentValue = String()
        currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
        currentParent = currentElement
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        currentValue.append(trimWhitespace ? string.trimmingCharacters(in: .whitespacesAndNewlines) : string)
        currentElement?.value = currentValue.isEmpty ? nil : currentValue
    }

    func parser(_ parser: XMLParser,
                      didEndElement elementName: String,
                      namespaceURI: String?,
                      qualifiedName qName: String?) {
        currentParent = currentParent?.parent
        currentElement = nil
    }

    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
        self.parseError = parseError
    }

}
