/*
 * Copyright 2012 Henry Coles
 *
 * 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 org.pitest.coverage;

import org.pitest.classinfo.ClassInfo;
import org.pitest.classinfo.ClassName;
import org.pitest.classpath.CodeSource;
import org.pitest.functional.FCollection;
import org.pitest.testapi.Description;
import org.pitest.util.Log;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class CoverageData implements CoverageDatabase {

  private static final Logger LOG = Log.getLogger();

  // We calculate block coverage, but everything currently runs on line
  // coverage. Ugly mess of maps below should go when
  // api changed to work via blocks
  private final Map<InstructionLocation, Set<TestInfo>> instructionCoverage = new LinkedHashMap<>();
  private final LegacyClassCoverage legacyClassCoverage;

  private final CodeSource code;

  private final List<Description> failingTestDescriptions = new ArrayList<>();

  public CoverageData(final CodeSource code, final LineMap lm) {
    this.code = code;
    this.legacyClassCoverage = new LegacyClassCoverage(code, lm);
  }

  public void calculateClassCoverage(final CoverageResult cr) {

    checkForFailedTest(cr);
    final TestInfo ti = this.createTestInfo(cr.getTestUnitDescription(),
            cr.getExecutionTime(), cr.getNumberOfCoveredBlocks());

    legacyClassCoverage.addTestToClasses(ti,cr.getCoverage());

    for (final BlockLocation each : cr.getCoverage()) {
      for (int i = each.getFirstInsnInBlock();
           i <= each.getLastInsnInBlock(); i++) {
        addTestsToBlockMap(ti, new InstructionLocation(each, i));
      }
    }
  }


  // populates class with class level data only, without block level data
  public void loadBlockDataOnly(Collection<BlockLocation> coverageData) {
    legacyClassCoverage.loadBlockDataOnly(coverageData);
  }


  @Override
  public Collection<TestInfo> getTestsForInstructionLocation(InstructionLocation location) {
    return this.instructionCoverage.getOrDefault(location, Collections.emptySet());
  }

  @Override
  public Collection<TestInfo> getTestsForClassLine(final ClassLine classLine) {
    return legacyClassCoverage.getTestsForClassLine(classLine);
  }

  public boolean allTestsGreen() {
    return this.failingTestDescriptions.isEmpty();
  }

  public int getCountFailedTests() {
    return this.failingTestDescriptions.size();
  }

  public List<Description> getFailingTestDescriptions() {
    return failingTestDescriptions;
  }

  @Override
  public Collection<ClassInfo> getClassInfo(final Collection<ClassName> classes) {
    return this.code.getClassInfo(classes);
  }

  @Override
  public int getNumberOfCoveredLines(final Collection<ClassName> mutatedClass) {
    return legacyClassCoverage.getNumberOfCoveredLines(mutatedClass);
  }

  @Override
  public Collection<TestInfo> getTestsForClass(final ClassName clazz) {
    return legacyClassCoverage.getTestsForClass(clazz);
  }

  private void addTestsToBlockMap(final TestInfo ti, InstructionLocation each) {
    Set<TestInfo> tests = this.instructionCoverage.get(each);
    if (tests == null) {
      tests = new TreeSet<>(new TestInfoNameComparator());
      this.instructionCoverage.put(each, tests);
    }
    tests.add(ti);
  }

  @Override
  public BigInteger getCoverageIdForClass(final ClassName clazz) {
    final Collection<TestInfo> coverage = getTestsForClass(clazz);
    if (coverage.isEmpty()) {
      return BigInteger.ZERO;
    }

    return generateCoverageNumber(coverage);
  }

  public List<BlockCoverage> createCoverage() {
    return FCollection.map(this.instructionCoverage.entrySet(), toBlockCoverage());
  }

  private static Function<Entry<InstructionLocation, Set<TestInfo>>, BlockCoverage> toBlockCoverage() {
    return a -> new BlockCoverage(a.getKey().getBlockLocation(), FCollection.map(a.getValue(),
        TestInfo.toName()));
  }

  @Override
  public Collection<ClassInfo> getClassesForFile(final String sourceFile,
      String packageName) {
    return legacyClassCoverage.getClassesForFile(sourceFile, packageName);
  }

  @Override
  public CoverageSummary createSummary() {
    return new CoverageSummary(numberOfLines(), coveredLines());
  }

  private BigInteger generateCoverageNumber(Collection<TestInfo> coverage) {
    BigInteger coverageNumber = BigInteger.ZERO;
    Set<ClassName> testClasses = coverage.stream()
            .map(TestInfo.toDefiningClassName())
            .collect(Collectors.toSet());

    for (final ClassInfo each : this.code.getClassInfo(testClasses)) {
      coverageNumber = coverageNumber.add(each.getDeepHash());
    }

    return coverageNumber;
  }

  private Collection<ClassName> allClasses() {
    return this.code.getCodeUnderTestNames();
  }

  private int numberOfLines() {
    return FCollection.fold(numberLines(), 0,
        this.code.getClassInfo(allClasses()));
  }

  private int coveredLines() {
    return getNumberOfCoveredLines(allClasses());
  }

  private BiFunction<Integer, ClassInfo, Integer> numberLines() {
    return (a, clazz) -> a + clazz.getNumberOfCodeLines();
  }

  private void checkForFailedTest(final CoverageResult cr) {
    if (!cr.isGreenTest()) {
      recordTestFailure(cr.getTestUnitDescription());
      LOG.severe(cr.getTestUnitDescription()
          + " did not pass without mutation.");
    }
  }

  private TestInfo createTestInfo(final Description description,
      final int executionTime, final int linesCovered) {
    final Optional<ClassName> testee = this.code.findTestee(description
        .getFirstTestClass());
    return new TestInfo(description.getFirstTestClass(),
        description.getQualifiedName(), executionTime, testee, linesCovered);
  }

  private void recordTestFailure(final Description testDescription) {
    this.failingTestDescriptions.add(testDescription);
  }

}

@@ -1573,27 +1573,21 @@
 nal Map%3C
-Instruction
+Block
 Location
@@ -1603,27 +1603,21 @@
 tInfo%3E%3E 
-instruction
+block
 Coverage
@@ -2336,172 +2336,39 @@
     
-for (int i = each.getFirstInsnInBlock();%0A           i %3C= each.getLastInsnInBlock(); i++) %7B%0A        addTestsToBlockMap(ti, new InstructionLocation(each, i));%0A      %7D
+  addTestsToBlockMap(ti, each);
 %0A   
@@ -2375,17 +2375,16 @@
  %7D%0A  %7D%0A%0A
-%0A
   // pop
@@ -2703,35 +2703,29 @@
 return this.
-instruction
+block
 Coverage.get
@@ -2742,16 +2742,35 @@
 location
+.getBlockLocation()
 , Collec
@@ -3744,27 +3744,21 @@
 nfo ti, 
-Instruction
+Block
 Location
@@ -3793,35 +3793,29 @@
 ests = this.
-instruction
+block
 Coverage.get
@@ -3913,35 +3913,29 @@
 %0A      this.
-instruction
+block
 Coverage.put
@@ -4326,19 +4326,13 @@
 his.
-instruction
+block
 Cove
@@ -4405,27 +4405,21 @@
 n%3CEntry%3C
-Instruction
+Block
 Location
@@ -4515,35 +4515,16 @@
 getKey()
-.getBlockLocation()
 , FColle

store blocks instead of lines