//
//  LKFTestFileCompare.swift
//  lkfc
//
//  Created by Alexander Holzer on 04.07.18.
//  Copyright © 2018 Alexander Holzer. All rights reserved.
//

import Foundation

public class LKFTestFileCompare {
    
    let file: LKFOutputFile
    let comparePath: URL
    var ignoreFirstLines = 0
    var ignoreLastLines = 0
    var sort = false
    var ignoreEmptyLines = true
    var encoding : String.Encoding = .utf8
    
    public init (file: LKFOutputFile, comparePath: URL) {
        self.file = file
        self.comparePath = comparePath
    }
    
    public func compare () throws -> [LKFTestFileError]? {
        let rawSourceArray : [String]
        let fileUrl = comparePath.appendingPathComponent(file.defaultfileName, isDirectory: false)
        let fileContents = try String(contentsOf: fileUrl, encoding: encoding)
        rawSourceArray = fileContents.components(separatedBy: .newlines)
        
        
        let rawTargetArray = try file.getOutput()
        
        var sourceArray = [String]()
        var targetArray = [String]()
        
        if ignoreEmptyLines {
            for l in rawSourceArray {
                if l.isEmpty && ignoreEmptyLines {
                    continue
                }
                sourceArray.append(l)
            }
            for l in rawTargetArray {
                if l.isEmpty && ignoreEmptyLines {
                    continue
                }
                targetArray.append(l)
            }
        }
        else {
            sourceArray.append(contentsOf: rawSourceArray)
            targetArray.append(contentsOf: rawTargetArray)
        }

        if ignoreFirstLines > 0 {
            sourceArray.removeFirst(ignoreFirstLines)
            targetArray.removeFirst(ignoreFirstLines)
        }
        if ignoreLastLines > 0 {
            sourceArray.removeLast(ignoreLastLines)
            targetArray.removeLast(ignoreLastLines)
        }
        if sort {
            sourceArray.sort()
            targetArray.sort()
        }
        
        var errors = [LKFTestFileError]()
        var sourceDict = [String:[Int]]()
        var skippedSourceDict = [String:Int]()
        
        for i in 0..<sourceArray.count {
            
            let s = sourceArray[i]
            if let indexArray = sourceDict[s] {
                var newIndexArray = [Int]()
                newIndexArray.append(contentsOf: indexArray)
                newIndexArray.append(i)
                sourceDict[s] = newIndexArray
            }
            else {
                sourceDict[s] = [i]
            }
        }
        
        var sourcePos = 0
        let sourceCount = sourceArray.count
        
        var openTarget = -1
        
        for i in 0..<targetArray.count {
            
            if sourcePos == sourceCount {
                openTarget = i
                break
            }
            
            let sourceString = sourceArray[sourcePos]
            let targetString = targetArray[i]
            
            if sourceString != targetString {
                
                var foundSource = -1
                
                if let skippedStringCount = skippedSourceDict[targetString] {
                    if skippedStringCount == 1 {
                        skippedSourceDict.removeValue(forKey: targetString)
                    }
                    else {
                        skippedSourceDict[targetString] = skippedStringCount - 1
                    }
                    continue
                }
                
                if let sourceStringIndices = sourceDict[targetString] {
                    for i in sourceStringIndices {
                        if i > sourcePos {
                            foundSource = i
                            break
                        }
                    }
                }
                
                if foundSource >= 0 {
                    errors.append(LKFTestFileErrorDifferentPosition(targetString, targetRowNumber:i, sourceRowNumber:foundSource))
                    for i in sourcePos..<foundSource {
                        let s = sourceArray[i]
                        if let count = skippedSourceDict[s] {
                            skippedSourceDict[s] = count + 1
                        }
                        else {
                            skippedSourceDict[s] = 1
                        }
                    }
                    sourcePos = foundSource + 1
                }
                else {
                    errors.append(LKFTestFileErrorOnlyInTarget(targetArray[i], rowNumber:i))
                }
            } else {
                sourcePos += 1
            }
            
            
        }
        
        if openTarget >= 0 {
            for i in openTarget..<targetArray.count {
                errors.append(LKFTestFileErrorOnlyInTarget(targetArray[i], rowNumber:i))
            }
        }
        
        if skippedSourceDict.count > 0 {
            for kv in skippedSourceDict {
                errors.append(LKFTestFileErrorOnlyInSource(kv.key, rowNumber:kv.value))
            }
        }
        
        if sourcePos < sourceCount {
            for i in sourcePos..<sourceCount {
                errors.append(LKFTestFileErrorOnlyInSource(sourceArray[i], rowNumber:i))
            }
        }
        
        if errors.count > 0 {
            return errors
        }
        return nil
    }
}

public class LKFTestFileError {
    var message : String {
        return "unsupported"
    }
}

public class LKFTestFileErrorGeneral : LKFTestFileError {
    let _message : String
    override var message : String {
        return _message
    }
    
    init (message: String) {
        _message = message
    }
}

public class LKFTestFileErrorOnlyInSource : LKFTestFileError {
    
    let rowNumber : Int
    let s: String
    
    init (_ s: String, rowNumber: Int) {
        self.rowNumber = rowNumber
        self.s = s
    }
    
    override var message: String {
        return "only in source (\(rowNumber)): \(s)"
    }
}

public class LKFTestFileErrorOnlyInTarget : LKFTestFileError {
    
    let rowNumber : Int
    let s: String
    
    init (_ s: String, rowNumber: Int) {
        self.rowNumber = rowNumber
        self.s = s
    }
    
    override var message: String {
        return "only in target (\(rowNumber)): \(s)"
    }
}

public class LKFTestFileErrorDifferentPosition : LKFTestFileError {
    
    let targetRowNumber : Int
    let sourceRowNumber : Int
    let s: String
    
    init (_ s: String, targetRowNumber: Int, sourceRowNumber: Int) {
        self.sourceRowNumber = sourceRowNumber
        self.targetRowNumber = targetRowNumber
        self.s = s
    }
    
    override var message: String {
        return "position (source=\(sourceRowNumber)/target=\(targetRowNumber): \(s)"
    }
}
