//
//  LKFFileExecution.swift
//  lkfc
//
//  Created by Alexander Holzer on 11.06.18.
//  Copyright © 2018 Alexander Holzer. All rights reserved.
//

import Foundation

public protocol LKFBatchFileExecutionBackgroundProtocol {
    func finished (batchFileResult: LKFBatchFileExecutionResult)
}

public class LKFBatchFileExecution {
    
    var file: URL
    public var context: LKFContext?
    public var sourceContext: LKFContext?
    var caseStat : LKFCaseStat?
    
    var parameters : LKFBatchFileExecutionParameters
    
    public var packageSize : Int?
    
    let metaData: LKFMetaDataFilesContext?
    
    public init (file: URL, metaData: LKFMetaDataFilesContext? = nil, parameters: LKFBatchFileExecutionParameters) {
        self.file = file
        self.parameters = parameters
        self.metaData = metaData
    }
    
    public func execute (chunkSize: Int) throws -> LKFBatchFileExecutionResult {
        do {
            let context : LKFContext
            if let rootPath = parameters.rootPath {
            
                if parameters.statOnly {
                    context = try LKFNoMetaDataContext(year: 0, path: rootPath)
                }
                else if let metaData {
                    context = try LKFContext(metaDataContext: metaData)
                }
                else {
                    context = try LKFContext(model: 0, path: rootPath)
                }
            }
            else {
                context = LKFContext()
            }
            
            if parameters.keepContext {
                self.context = context
            }

            let statFile = LKFOutputStatFile(context:context)
            
            if !parameters.statOnly {
                context.add(file:LKFOutputDataFile(context:context))
                context.add(file:LKFOutputErrorFile(context:context))
            }
            
            context.add(file:statFile)
            
            do {
                try context.executePackaged(packageSize: chunkSize, file: file, enableComparing: parameters.compareMode != .disable)
            }
            catch {
                return LKFBatchFileExecutionResultFailed (execution:self, error: error)
            }
            
            for f in context.packageOutputFiles! {
                let fileUrl = parameters.outPath.appendingPathComponent(f.defaultfileName, isDirectory: false)
                try context.write(file: fileUrl, lines: f.output ?? [])
            }
            caseStat = context.CaseStat()
            if let comparer = context.compareCache {
            
                if let differences = comparer.compare() {
                    if parameters.compareMode == .print {
                        for s in differences {
                            print(s)
                        }
                    }
                    return LKFBatchFileExecutionResultFailed (execution:self, failureText: "\(file): \(differences.count) differences found")
                }
            }
            
            if let comparePath = parameters.comparePath {
                let statFileCompare = LKFTestFileCompare (file: statFile, comparePath: comparePath)
                if let statFileErrors = try statFileCompare.compare() {
                    return LKFBatchFileExecutionResultFailed (execution:self, failureText: "\(file): \(statFileErrors.count) differences in stat file found")
                }
            }
        }
        catch {
            return LKFBatchFileExecutionResultFailed (execution:self, error:error)
        }
        return LKFBatchFileExecutionResult(execution: self)
    }
    
    public func execute(iteration: Int? = nil) throws -> LKFBatchFileExecutionResult {
        
        if parameters.unloadStamm {
            LKFContext.unloadStamm()
        }
        let context : LKFContext
        if let metaData {
            context = try LKFContext(metaDataContext: metaData)
            if parameters.year > 0 {
                context.setContextParameters(year: Int32(parameters.year))
            }
        }
        else if let rootPath = parameters.rootPath {
            if parameters.statOnly {
                context = try LKFNoMetaDataContext(year: parameters.year, path: rootPath)
            }
            else {
                context = try LKFContext(model: parameters.model, path: rootPath)
                if parameters.year > 0 {
                    context.setContextParameters(year: Int32(parameters.year))
                }
            }
            if let contextFlags = parameters.contextFlags {
                context.flags = contextFlags
            }
        }
        else {
            context = LKFContext()
        }

        if parameters.keepContext {
            self.context = context
        }

        if let packageSize = packageSize {
            try context.executePackaged(packageSize: packageSize, file: file, enableComparing: parameters.compareMode != .disable)
        }
        else {
            if let iteration {
                print ("iteration #\(iteration+1): memory before load: \(LKFMemory.getDebugMemoryBlocks()), bytes:\(LKFMemory.getDebugMemoryBytes())")
            }
            try _ = context.load(file: file, enableComparing: parameters.compareMode != .disable)

            if let iteration {
                print ("iteration #\(iteration+1): loaded: \(LKFMemory.getDebugMemoryBlocks()), bytes:\(LKFMemory.getDebugMemoryBytes())")
            }
            
            try context.execute()
            
            if let iteration {
                print ("iteration #\(iteration+1): executed: \(LKFMemory.getDebugMemoryBlocks()), bytes:\(LKFMemory.getDebugMemoryBytes())")
            }
            
            if let anonymizationSettings = parameters.anonymization {
                let targetContext : LKFContext
                if let rootPath = parameters.rootPath {
                    targetContext = try LKFContext(model: context.model, path: rootPath)
                    if let addYears = anonymizationSettings.addYears {
                        targetContext.setContextParameters(year: Int32(context.year+addYears))
                    }
                    else {
                        targetContext.setContextParameters(year: Int32(context.year))
                    }
                }
                else {
                    targetContext = LKFContext()
                }
                let _ = LKFAnonymizationContext(settings: anonymizationSettings, targetContext: targetContext, aufenthalte: context.aufenthalte(), sourceContext: context)
                
                try targetContext.execute()
                                    
                if parameters.keepContext {
                    self.sourceContext = context
                    self.context = targetContext
                }
                
                let outputDataFile = LKFOutputDataFile(context:targetContext)
                let outputErrorFile = LKFOutputErrorFile(context: targetContext)
                let outputWarningFile = LKFOutputWarningFile(context: targetContext)
                let outputInfoFile = LKFOutputInfoFile(context: targetContext)

                outputDataFile.createOutputForTest = parameters.createTestOutput
                let dataFile = parameters.outPath.appendingPathComponent(outputDataFile.defaultfileName.replacingOccurrences(of: "j001", with: "jano"), isDirectory: false)
                let errorFile = parameters.outPath.appendingPathComponent(outputErrorFile.defaultfileName.replacingOccurrences(of: "j001", with: "jano"), isDirectory: false)
                let warningFile = parameters.outPath.appendingPathComponent(outputWarningFile.defaultfileName.replacingOccurrences(of: "j001", with: "jano"), isDirectory: false)
                let infoFile = parameters.outPath.appendingPathComponent(outputInfoFile.defaultfileName.replacingOccurrences(of: "j001", with: "jano"), isDirectory: false)
                
                if parameters.errorFiles == .write {
                    do {
                        try targetContext.write(file: dataFile, lines: outputDataFile.getOutput())
                    }
                    catch {
                        throw error
                    }
                }
                
                if parameters.errorFiles == .write {
                    do {
                        try targetContext.write(file: errorFile, lines: outputErrorFile.getOutput())
                    }
                    catch {
                        throw error
                    }
                    do {
                        try targetContext.write(file: warningFile, lines: outputWarningFile.getOutput())
                    }
                    catch {
                        throw error
                    }
                    do {
                        try targetContext.write(file: infoFile, lines: outputInfoFile.getOutput())
                    }
                    catch {
                        throw error
                    }
                }
            }
        }
        
        
        caseStat = context.CaseStat()
        
        if !parameters.statOnly {
            let outputDataFile = LKFOutputDataFile(context:context)

            outputDataFile.createOutputForTest = parameters.createTestOutput
            let dataFile = parameters.outPath.appendingPathComponent(outputDataFile.defaultfileName, isDirectory: false)
            
            if parameters.errorFiles == .write  {
                let outputErrorFile = LKFOutputErrorFile(context: context)
                let outputWarningFile = LKFOutputWarningFile(context: context)
                let outputInfoFile = LKFOutputInfoFile(context: context)

                let errorFile = parameters.outPath.appendingPathComponent(outputErrorFile.defaultfileName, isDirectory: false)
                let warningFile = parameters.outPath.appendingPathComponent(outputWarningFile.defaultfileName, isDirectory: false)
                let infoFile = parameters.outPath.appendingPathComponent(outputInfoFile.defaultfileName, isDirectory: false)

                try context.write(file: errorFile, lines: outputErrorFile.getOutput())
                try context.write(file: warningFile, lines: outputWarningFile.getOutput())
                try context.write(file: infoFile, lines: outputInfoFile.getOutput())
                
                if (context.flags & 2048) != 0 {
                    let npTraceFile = LKFOutputNPTraceFile(context: context)
                    let npSumFile = LKFOutputNPSumFile(context: context)
                    let npsumFilePath = parameters.outPath.appendingPathComponent(npSumFile.defaultfileName, isDirectory: false)
                    let nptraceFilePath = parameters.outPath.appendingPathComponent(npTraceFile.defaultfileName, isDirectory: false)
                    try context.write(file: npsumFilePath, lines: npSumFile.getOutput())
                    try context.write(file: nptraceFilePath, lines: npTraceFile.getOutput())
                }
                
                
                try context.write(file: dataFile, lines: outputDataFile.getOutput())
            }
            else if parameters.errorFiles == .disable {
                try context.write(file: dataFile, lines: outputDataFile.getOutput())
            }
            else {
                var lines = outputDataFile.getOutput()
                let outputErrorFile = LKFOutputErrorFile(context: context, withHeadline: false)
                outputErrorFile.setErrorFormat (fields: [.code, .aufnr, .aufid64, .ident, .feld_lang, .abtfc])
                let outputWarningFile = LKFOutputWarningFile(context: context, withHeadline: false)
                outputWarningFile.setErrorFormat (fields: [.code, .aufnr, .aufid64, .ident, .feld_lang, .abtfc])
                let outputInfoFile = LKFOutputInfoFile(context: context, withHeadline: false)
                outputInfoFile.setErrorFormat (fields: [.code, .aufnr, .aufid64, .ident, .feld_lang, .abtfc])
                
                
                var errorLines = outputErrorFile.getOutput(linePrefix: parameters.errorFilePrefix)
                errorLines.append(contentsOf: outputWarningFile.getOutput(linePrefix: parameters.warningFilePrefix))
                errorLines.append(contentsOf: outputInfoFile.getOutput(linePrefix: parameters.infoFilePrefix))
                
                if parameters.errorFiles == .writeToResult || parameters.errorFiles == .compareAndWriteToResult {
                    lines.append(contentsOf: errorLines)
                }
                if let compareCache = context.compareCache, parameters.errorFiles == .compare || parameters.errorFiles == .compareAndWriteToResult {
                    for l in errorLines {
                        compareCache.addTargetError(line: l)
                    }
                }
                try context.write(file: dataFile, lines: lines)
            }
        }

        let outputStatFile : LKFOutputStatFile?
        
        if parameters.includeStat || parameters.statOnly {
            outputStatFile = LKFOutputStatFile(context: context)
            let statFile = parameters.outPath.appendingPathComponent(outputStatFile!.defaultfileName, isDirectory: false)
            try context.write(file: statFile, lines: outputStatFile!.getOutput())
        } else {
            outputStatFile = nil
        }
        
        if parameters.statOnly == false, let comparer = context.compareCache {
            
            if let differences = comparer.compare(removeBM: parameters.removeBM, onlyPointDiff: false) {
                if parameters.compareMode == .print {
                    
                    let differenceMax = 100
                    
                    if differences.count > differenceMax {
                        print ("\(file): \(differences.count) differences - showing first \(differenceMax) ...")
                        for i in 0..<differenceMax {
                            print(differences[i])
                        }
                    }
                    else {
                        print ("\(file): \(differences.count) differences:")
                        for s in differences {
                            print(s)
                        }
                    }
                }
                
                if let errDiff = comparer.compareErrors() {
                    if parameters.compareMode == .print {
                        
                        if errDiff.count > 200 {
                            print ("\(file): error \(errDiff.count) differences - showing first 200 ...")
                            for i in 0..<200 {
                                print(errDiff[i])
                            }
                        }
                        else {
                            for s in errDiff {
                                print(s)
                            }
                        }
                    }
                }
                
                return LKFBatchFileExecutionResultFailed (execution:self, failureText: "\(differences.count) differences found")
            }
            
            if parameters.errorFiles == .compare || parameters.errorFiles == .compareAndWriteToResult {
                if let errDiff = comparer.compareErrors() {
                    if parameters.compareMode == .print {
                        
                        if errDiff.count > 200 {
                            print ("error \(errDiff.count) differences - showing first 200 ...")
                            for i in 0..<200 {
                                print(errDiff[i])
                            }
                        }
                        else {
                            for s in errDiff {
                                print(s)
                            }
                        }
                    }
                    return LKFBatchFileExecutionResultFailed (execution:self, failureText: "\(errDiff.count) error differences found")
                }
            }

            
            if outputStatFile != nil, let comparePath = parameters.comparePath {
                let statFileCompare = LKFTestFileCompare (file: outputStatFile!, comparePath: comparePath)
                if let statFileErrors = try statFileCompare.compare() {
                    return LKFBatchFileExecutionResultFailed (execution:self, failureText: "\(statFileErrors.count) differences in stat file found")
                }
            }
        }
        return LKFBatchFileExecutionResult(execution: self)
    }
    
    public func threadExecute (delegate: LKFBatchFileExecutionBackgroundProtocol) {
        DispatchQueue.global(qos:.background).async {
            print ("starting execution: \(self.file)")
            do {
                let result = try self.execute()
                print ("finished execution: \(self.file)")
                delegate.finished(batchFileResult: result)
            }
            catch {
                print ("ERROR: \(error.localizedDescription)")
                delegate.finished(batchFileResult: LKFBatchFileExecutionResultFailed(execution: self, error: error))
            }
        }
    }
    
    public func threadExecutePackaged (chunkSize: Int, delegate: LKFBatchFileExecutionBackgroundProtocol) {
        DispatchQueue.global(qos:.background).async {
            print ("starting execution: \(self.file)")
            do {
                let result = try self.execute(chunkSize:chunkSize)
                print ("finished execution: \(self.file)")
                delegate.finished(batchFileResult: result)
            }
            catch {
                print ("ERROR: \(error.localizedDescription)")
                delegate.finished(batchFileResult: LKFBatchFileExecutionResultFailed(execution: self, error: error))
            }
        }
    }
}



