/*
 * Copyright 2015 Collective, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package com.collective.celos.ci.testing.fixtures.compare;

import com.collective.celos.ci.mode.test.TestRun;
import com.collective.celos.ci.testing.fixtures.create.FixObjectCreator;
import com.collective.celos.ci.testing.structure.fixobject.FixTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang.StringUtils;

import java.util.*;

/**
 * Created by akonopko on 13.02.15.
 */
public class FixTableComparer implements FixtureComparer {

    private final FixObjectCreator<FixTable> expectedDataCreator;
    private final FixObjectCreator<FixTable> actualDataCreator;
    private final boolean columnNamesOrdered;
    private final boolean respectRowOrder;

    public FixTableComparer(FixObjectCreator<FixTable> expectedDataCreator, FixObjectCreator<FixTable> actualDataCreator, boolean columnNamesOrdered, boolean respectRowOrder) {
        this.expectedDataCreator = expectedDataCreator;
        this.actualDataCreator = actualDataCreator;
        this.columnNamesOrdered = columnNamesOrdered;
        this.respectRowOrder = respectRowOrder;
    }

    public FixObjectCreator<FixTable> getExpectedDataCreator() {
        return expectedDataCreator;
    }

    public FixObjectCreator<FixTable> getActualDataCreator() {
        return actualDataCreator;
    }

    public boolean isColumnNamesOrdered() {
        return columnNamesOrdered;
    }

    public boolean isRespectRowOrder() {
        return respectRowOrder;
    }

    @Override
    public FixObjectCompareResult check(TestRun testRun) throws Exception {
        FixTable expected = expectedDataCreator.create(testRun);
        FixTable actual = actualDataCreator.create(testRun);

        List<String> expectedColumns = expected.getColumnNames();
        List<String> actualColumns = actual.getColumnNames();
        boolean wrongColNames = columnNamesOrdered && !expectedColumns.equals(actualColumns);
        wrongColNames |= !columnNamesOrdered && !equalsIgnoreOrder(expectedColumns, actualColumns);

        if (wrongColNames) {
            String expectedColDesc = StringUtils.join(expectedColumns, ", ");
            String actualColDesc = StringUtils.join(actualColumns, ", ");
            return FixObjectCompareResult.failed(
                    "Column Names for expected and actual data set differed: expected [" +
                    expectedColDesc + "] but was [" + actualColDesc + "]"
            );
        }

        if (expected.getRows().size() != actual.getRows().size()) {
            return FixObjectCompareResult.failed(
                    "Data set size for expected and actual data set differed: expected has " +
                            expected.getRows().size() + " while actual data has " + actual.getRows().size() + " rows"
            );
        }

        if (respectRowOrder) {
            Map<String, FixObjectCompareResult> fails = compareRespectOrder(expected, actual);
            if (!fails.isEmpty()) {
                return FixObjectCompareResult.wrapFailed(fails, "");
            }
            return FixObjectCompareResult.SUCCESS;
        } else {
            Map<FixTable.FixRow, Integer> expectedRes = countEntries(expected);
            Map<FixTable.FixRow, Integer> actualRes = countEntries(actual);

            return CompareHelper.compareEntityNumber(testRun, actualDataCreator, expectedDataCreator, expectedRes, actualRes);
        }

    }

    private Map<FixTable.FixRow, Integer> countEntries(FixTable expected) {
        Map<FixTable.FixRow, Integer> expEntryCount = Maps.newHashMap();

        for (FixTable.FixRow row : expected.getRows()) {
            Integer count = expEntryCount.get(row);
            if (count == null) {
                count = 1;
            } else {
                count = count + 1;
            }
            expEntryCount.put(row, count);
        }
        return expEntryCount;
    }

    private Map<String, FixObjectCompareResult> compareRespectOrder(FixTable expected, FixTable actual) {
        Map<String, FixObjectCompareResult> fails = Maps.newLinkedHashMap();
        ListIterator<FixTable.FixRow> expIter = expected.getRows().listIterator();
        ListIterator<FixTable.FixRow> actIter = actual.getRows().listIterator();
        while (expIter.hasNext()) {
            FixTable.FixRow expRow = expIter.next();
            FixTable.FixRow actRow = actIter.next();

            FixObjectCompareResult result = compareFixRows(expected.getColumnNames(), expRow, actRow);
            if (result.getStatus() == FixObjectCompareResult.Status.FAIL) {
                fails.put("Row #" + expIter.previousIndex(), result);
            }
        }
        return fails;
    }

    private FixObjectCompareResult compareFixRows(List<String> columnNames, FixTable.FixRow expRow, FixTable.FixRow actRow) {
        if (expRow.equals(actRow)) {
            return FixObjectCompareResult.SUCCESS;
        }

        List<String> expDesc = Lists.newArrayList();
        List<String> actDesc = Lists.newArrayList();

        for (String key: columnNames) {
            String expValue = expRow.getCells().get(key);
            String actValue = actRow.getCells().get(key);
            if (!expValue.equals(actValue)) {
                expDesc.add(key + "=" + expValue);
                actDesc.add(key + "=" + actValue);
            }
        }

        String differenctDescription =
                "Cells in expected data set: [" + StringUtils.join(expDesc, ", ") + "], " +
                "cells in actual data set: [" + StringUtils.join(actDesc, ", ") + "]";

        return FixObjectCompareResult.failed(differenctDescription);
    }

    private boolean equalsIgnoreOrder(Collection col1, Collection col2) {
        return col1.containsAll(col2) && col2.containsAll(col1);
    }

}