//
//  FileDestination.swift
//  XCGLogger: https://github.com/DaveWoodCom/XCGLogger
//
//  Created by Dave Wood on 2014-06-06.
//  Copyright © 2014 Dave Wood, Cerebral Gardens.
//  Some rights reserved: https://github.com/DaveWoodCom/XCGLogger/blob/master/LICENSE.txt
//

import Foundation
import Dispatch

// MARK: - FileDestination
/// A standard destination that outputs log details to a file
open class FileDestination: BaseQueuedDestination {
    // MARK: - Properties
    /// Logger that owns the destination object
    open override var owner: XCGLogger? {
        didSet {
            if owner != nil {
                openFile()
            }
            else {
                closeFile()
            }
        }
    }

    /// FileURL of the file to log to
    open var writeToFileURL: URL? = nil {
        didSet {
            openFile()
        }
    }

    /// File handle for the log file
    internal var logFileHandle: FileHandle? = nil

    /// Option: whether or not to append to the log file if it already exists
    internal var shouldAppend: Bool

    /// Option: if appending to the log file, the string to output at the start to mark where the append took place
    internal var appendMarker: String?

    /// Option: Attributes to use when creating a new file
    internal var fileAttributes: [FileAttributeKey: Any]? = nil

    // MARK: - Life Cycle
    public init(owner: XCGLogger? = nil, writeToFile: Any, identifier: String = "", shouldAppend: Bool = false, appendMarker: String? = "-- ** ** ** --", attributes: [FileAttributeKey: Any]? = nil) {
        self.shouldAppend = shouldAppend
        self.appendMarker = appendMarker
        self.fileAttributes = attributes

        if writeToFile is NSString {
            writeToFileURL = URL(fileURLWithPath: writeToFile as! String)
        }
        else if let writeToFile = writeToFile as? URL, writeToFile.isFileURL {
            writeToFileURL = writeToFile
        }
        else {
            writeToFileURL = nil
        }

        super.init(owner: owner, identifier: identifier)

        if owner != nil {
            openFile()
        }
    }

    deinit {
        // close file stream if open
        closeFile()
    }

    // MARK: - File Handling Methods
    /// Open the log file for writing.
    ///
    /// - Parameters:   None
    ///
    /// - Returns:  Nothing
    ///
    private func openFile() {
        guard let owner = owner else { return }

        if logFileHandle != nil {
            closeFile()
        }

        guard let writeToFileURL = writeToFileURL else { return }

        let fileManager: FileManager = FileManager.default
        let fileExists: Bool = fileManager.fileExists(atPath: writeToFileURL.path)
        if !shouldAppend || !fileExists {
            fileManager.createFile(atPath: writeToFileURL.path, contents: nil, attributes: fileAttributes)
        }

        do {
            logFileHandle = try FileHandle(forWritingTo: writeToFileURL)
            if fileExists && shouldAppend {
                logFileHandle?.seekToEndOfFile()

                if let appendMarker = appendMarker,
                    let encodedData = "\(appendMarker)\n".data(using: String.Encoding.utf8) {

                    _try({
                        self.logFileHandle?.write(encodedData)
                    },
                    catch: { (exception: NSException) in
                        print("Objective-C Exception occurred: \(exception)")
                    })
                }
            }
        }
        catch let error as NSError {
            owner._logln("Attempt to open log file for \(fileExists && shouldAppend ? "appending" : "writing") failed: \(error.localizedDescription)", level: .error, source: self)
            logFileHandle = nil
            return
        }

//        owner.logAppDetails(selectedDestination: self)
//
//        let logDetails = LogDetails(level: .info, date: Date(), message: "XCGLogger " + (fileExists && shouldAppend ? "appending" : "writing") + " log to: " + writeToFileURL.absoluteString, functionName: "", fileName: "", lineNumber: 0, userInfo: XCGLogger.Constants.internalUserInfo)
//        owner._logln(logDetails.message, level: logDetails.level, source: self)
//        if owner.destination(withIdentifier: identifier) == nil {
//            processInternal(logDetails: logDetails)
//        }
    }

    /// Close the log file.
    ///
    /// - Parameters:   None
    ///
    /// - Returns:  Nothing
    ///
    private func closeFile() {
        logFileHandle?.synchronizeFile()
        logFileHandle?.closeFile()
        logFileHandle = nil
    }

    /// Force any buffered data to be written to the file.
    ///
    /// - Parameters:
    ///     - closure:  An optional closure to execute after the file has been rotated.
    ///
    /// - Returns:      Nothing.
    ///
    open func flush(closure: (() -> Void)? = nil) {
        if let logQueue = logQueue {
            logQueue.async {
                self.logFileHandle?.synchronizeFile()
                closure?()
            }
        }
        else {
            logFileHandle?.synchronizeFile()
            closure?()
        }
    }

    /// Rotate the log file, storing the existing log file in the specified location.
    ///
    /// - Parameters:
    ///     - archiveToFile:    FileURL or path (as String) to where the existing log file should be rotated to.
    ///     - closure:          An optional closure to execute after the file has been rotated.
    ///
    /// - Returns:
    ///     - true:     Log file rotated successfully.
    ///     - false:    Error rotating the log file.
    ///
    @discardableResult open func rotateFile(to archiveToFile: Any, closure: ((_ success: Bool) -> Void)? = nil) -> Bool {
        var archiveToFileURL: URL? = nil

        if archiveToFile is NSString {
            archiveToFileURL = URL(fileURLWithPath: archiveToFile as! String)
        }
        else if let archiveToFile = archiveToFile as? URL, archiveToFile.isFileURL {
            archiveToFileURL = archiveToFile
        }
        else {
            closure?(false)
            return false
        }

        if let archiveToFileURL = archiveToFileURL,
          let writeToFileURL = writeToFileURL {

            let fileManager: FileManager = FileManager.default
            guard !fileManager.fileExists(atPath: archiveToFileURL.path) else { closure?(false); return false }

            closeFile()
            haveLoggedAppDetails = false

            do {
                try fileManager.moveItem(atPath: writeToFileURL.path, toPath: archiveToFileURL.path)
            }
            catch let error as NSError {
                openFile()
                owner?._logln("Unable to rotate file \(writeToFileURL.path) to \(archiveToFileURL.path): \(error.localizedDescription)", level: .error, source: self)
                closure?(false)
                return false
            }

            do {
                if let identifierData: Data = identifier.data(using: .utf8) {
                    try archiveToFileURL.setExtendedAttribute(data: identifierData, forName: XCGLogger.Constants.extendedAttributeArchivedLogIdentifierKey)
                }
                if let timestampData: Data = "\(Date().timeIntervalSince1970)".data(using: .utf8) {
                    try archiveToFileURL.setExtendedAttribute(data: timestampData, forName: XCGLogger.Constants.extendedAttributeArchivedLogTimestampKey)
                }
            }
            catch let error as NSError {
                owner?._logln("Unable to set extended file attributes on file \(archiveToFileURL.path): \(error.localizedDescription)", level: .error, source: self)
            }

            owner?._logln("Rotated file \(writeToFileURL.path) to \(archiveToFileURL.path)", level: .info, source: self)
            openFile()
            closure?(true)
            return true
        }

        closure?(false)
        return false
    }

    // MARK: - Overridden Methods
    /// Write the log to the log file.
    ///
    /// - Parameters:
    ///     - message:   Formatted/processed message ready for output.
    ///
    /// - Returns:  Nothing
    ///
    open override func write(message: String) {
        if let encodedData = "\(message)\n".data(using: String.Encoding.utf8) {
            _try({
                self.logFileHandle?.write(encodedData)
            },
            catch: { (exception: NSException) in
                print("Objective-C Exception occurred: \(exception)")
            })
        }
    }
}
