package no.steria.skuldsku.testrunner.dbrunner.dbchange;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import no.steria.skuldsku.recorder.logging.RecorderLog;
import no.steria.skuldsku.utils.ParsedString;

/**
 * Represents a single database INSERT, UPDATE or DELETE operation.
 */
public class DatabaseChange {
    private static final String TABLE_NAME = "TABLE_NAME";
    private static final String ACTION = "ACTION";
    private static final String CLIENT_IDENTIFIER = "CLIENT_IDENTIFIER";
    private static final String START_TIME = "START_TIME";
    
    private final String row;
    private final Map<String, String> data;
    private final int lineNumber;


    /**
     * Creates a <code>DatabaseChange</code>.
     * 
     * @param row The <code>String</code>, representing a database change, having the format
     *          <code>KEY=VALUE[;KEY=VALUE]*</code>.
     * @param lineNumber The line the string was taken from. This may be used by
     *          a <code>DatabaseChangeVerifier</code>.
     */
    public DatabaseChange(String row, int lineNumber) {
        if (row == null) {
            throw new NullPointerException("The String 'row' cannot be null.");
        }
        if (lineNumber <= 0) {
            throw new IllegalArgumentException("The int 'lineNumber' must be greater than zero.");
        }
        this.row = row;
        this.data = extractData(row);
        this.lineNumber = lineNumber;
    }


    static Map<String, String> extractData(String row) {
        final Map<String, String> data = new LinkedHashMap<>();
        
        final Iterator<String> it = new ParsedString(row).iterator();
        while (it.hasNext()) {
            final String key = it.next();
            final String value = it.next();
            data.put(key, value);
        }
        return Collections.unmodifiableMap(data);
    }
    
    public int getLineNumber() {
        return lineNumber;
    }

    public Map<String, String> getData() {
        return Collections.unmodifiableMap(data);
    }
    
    public String getValue(String key) {
        return data.get(key);
    }
    
    public String getTableName() {
        return data.get(TABLE_NAME);
    }
    
    public String getAction() {
        return data.get(ACTION);
    }
    
    public String getClientIdentifier() {
        return data.get(CLIENT_IDENTIFIER);
    }
    
    /**
     * Gets the time the DB change happened.
     * @return The point in time using the format <code>yyyy-MM-dd'T'HH:mm:ss.SSSXXX</code>.
     */
    public String getStartTime() {
        return data.get(START_TIME);
    }
    
    List<Entry<String, String>> getFields(String start) {
        return data.entrySet().stream().filter(entry -> entry.getKey().startsWith(start)).collect(Collectors.toList());
    }
    
    public int fieldsMatched(DatabaseChange databaseChange, Set<String> skipFields) {
        int result = 0;
        for (String key : data.keySet()) {
            if (skipFields.contains(key)) {
                continue;
            }
            final String v1 = data.get(key);
            final String v2 = databaseChange.data.get(key);
            if ((v1 != null && v2!= null && v1.equals(v2)) || (v1 == null && v2 == null)) {
                result++;
            }
        }
        return result;
    }
    
    public boolean equals(DatabaseChange databaseChange, Set<String> skipFields) {
        return determineDifferences(databaseChange, skipFields).isEmpty();
    }
    
    public List<FieldDifference> determineDifferences(DatabaseChange databaseChange, Set<String> skipFields) {
        final List<FieldDifference> fieldDifferences = new ArrayList<FieldDifference>();
        for (String key : data.keySet()) {
            if (skipFields.contains(key)) {
                continue;
            }
            if (key.startsWith("OLDROW")) {
                continue;
            }
            final String v1 = data.get(key);
            final String v2 = databaseChange.data.get(key);
            if ((v1 != null && v2!= null && !v1.equals(v2)) || (v1 == null ^ v2 == null)) {
                fieldDifferences.add(new FieldDifference(key, v1, v2));
            }
        }
        return fieldDifferences;
    }

    public static List<DatabaseChange> readDatabaseChanges(File f) {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
            return readDatabaseChanges(in);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    RecorderLog.error("Could not close stream.", e);
                }
            }
        }
    }

    public static List<DatabaseChange> readDatabaseChanges(BufferedReader in) {
        try {
            final List<DatabaseChange> result = new ArrayList<>();
            String line;
            int lineNumber = 1;
            while ((line = in.readLine()) != null) {
                result.add(new DatabaseChange(line, lineNumber));
                lineNumber++;
            }
            return result;
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }
    
    public static List<DatabaseChange> toDatabaseChangeList(String[] rowArray) {
        final List<DatabaseChange> result = new ArrayList<>(rowArray.length);
        for (int index = 0; index<rowArray.length; index++) {
            result.add(new DatabaseChange(rowArray[index], index + 1));
        }
        return result;
    }
    
    @Override
    public String toString() {
        return row;
    }
}

@@ -5436,32 +5436,160 @@
 e()) != null) %7B%0A
+                if (line.trim().equals(%22%22)) %7B%0A                    lineNumber++;%0A                    continue;%0A                %7D%0A
                 

Allow blank lines.