package org.testng.reporters.jq;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.testng.IResultMap;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.collections.ListMultiMap;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.testng.collections.SetMultiMap;
import org.testng.internal.Utils;

public class Model {
  private final ListMultiMap<ISuite, ITestResult> m_model = Maps.newListMultiMap();
  private final List<ISuite> m_suites;
  private final Map<String, String> m_testTags = Maps.newHashMap();
  private final Map<ITestResult, String> m_testResultMap = Maps.newHashMap();
  private final Map<ISuite, ResultsByClass> m_failedResultsByClass = Maps.newHashMap();
  private final Map<ISuite, ResultsByClass> m_skippedResultsByClass = Maps.newHashMap();
  private final Map<ISuite, ResultsByClass> m_passedResultsByClass = Maps.newHashMap();
  private final List<ITestResult> m_allFailedResults = Lists.newArrayList();
  // Each suite is mapped to failed.png, skipped.png or nothing (which means passed.png)
  private final Map<String, String> m_statusBySuiteName = Maps.newHashMap();
  private final SetMultiMap<String, String> m_groupsBySuiteName = Maps.newSetMultiMap();
  private final SetMultiMap<String, String> m_methodsByGroup = Maps.newSetMultiMap();

  public Model(List<ISuite> suites) {
    m_suites = suites;
    init();
  }

  public List<ISuite> getSuites() {
    return m_suites;
  }

  private void init() {
    int testCounter = 0;
    for (ISuite suite : m_suites) {
      List<ITestResult> passed = Lists.newArrayList();
      List<ITestResult> failed = Lists.newArrayList();
      List<ITestResult> skipped = Lists.newArrayList();
      Map<String, ISuiteResult> suiteResults = suite.getResults();
      for (ISuiteResult sr : suiteResults.values()) {
        ITestContext context = sr.getTestContext();
        m_testTags.put(context.getName(), "test-" + testCounter++);
        failed.addAll(context.getFailedTests().getAllResults());
        skipped.addAll(context.getSkippedTests().getAllResults());
        passed.addAll(context.getPassedTests().getAllResults());
        IResultMap[] map =
            new IResultMap[] {
              context.getFailedTests(), context.getSkippedTests(), context.getPassedTests()
            };
        for (IResultMap m : map) {
          for (ITestResult tr : m.getAllResults()) {
            m_testResultMap.put(tr, getTestResultName(tr));
          }
        }
      }

      // Process them in the order passed, skipped and failed, so that the failed
      // icon overrides all the others and the skipped icon overrides passed.

      // Passed
      {
        ResultsByClass rbc = new ResultsByClass();
        for (ITestResult tr : passed) {
          rbc.addResult(tr.getTestClass().getRealClass(), tr);
          updateGroups(suite, tr);
        }
        m_passedResultsByClass.put(suite, rbc);
      }

      // Skipped
      {
        ResultsByClass rbc = new ResultsByClass();
        for (ITestResult tr : skipped) {
          m_statusBySuiteName.put(suite.getName(), "skipped");
          rbc.addResult(tr.getTestClass().getRealClass(), tr);
          updateGroups(suite, tr);
        }
        m_skippedResultsByClass.put(suite, rbc);
      }

      // Failed
      {
        ResultsByClass rbc = new ResultsByClass();
        for (ITestResult tr : failed) {
          m_statusBySuiteName.put(suite.getName(), "failed");
          rbc.addResult(tr.getTestClass().getRealClass(), tr);
          m_allFailedResults.add(tr);
          updateGroups(suite, tr);
        }
        m_failedResultsByClass.put(suite, rbc);
      }

      m_model.putAll(suite, failed);
      m_model.putAll(suite, skipped);
      m_model.putAll(suite, passed);
    }
  }

  private void updateGroups(ISuite suite, ITestResult tr) {
    String[] groups = tr.getMethod().getGroups();
    m_groupsBySuiteName.putAll(suite.getName(), Arrays.asList(groups));
    for (String group : groups) {
      m_methodsByGroup.put(group, tr.getMethod().getMethodName());
    }
  }

  public ResultsByClass getFailedResultsByClass(ISuite suite) {
    return m_failedResultsByClass.get(suite);
  }

  public ResultsByClass getSkippedResultsByClass(ISuite suite) {
    return m_skippedResultsByClass.get(suite);
  }

  public ResultsByClass getPassedResultsByClass(ISuite suite) {
    return m_passedResultsByClass.get(suite);
  }

  public String getTag(ITestResult tr) {
    return m_testResultMap.get(tr);
  }

  public List<ITestResult> getTestResults(ISuite suite) {
    return m_model.get(suite);
  }

  private static String getMethodName(String name) {
    if (name == null) {
      return "";
    }
    return name;
  }

  public static String getTestResultName(ITestResult tr) {
    StringBuilder result = new StringBuilder(getMethodName(tr.getMethod().getMethodName()));
    Object[] parameters = tr.getParameters();
    if (parameters.length > 0) {
      result.append("(");
      StringBuilder p = new StringBuilder();
      for (int i = 0; i < parameters.length; i++) {
        if (i > 0) p.append(", ");
        p.append(Utils.toString(parameters[i]));
      }
      if (p.length() > 100) {
        String s = p.substring(0, 100);
        s = s + "...";
        result.append(s);
      } else {
        result.append(p);
      }
      result.append(")");
    }

    return result.toString();
  }

  public List<ITestResult> getAllFailedResults() {
    return m_allFailedResults;
  }

  public static String getImage(String tagClass) {
    return tagClass + ".png";
  }

  public String getStatusForSuite(String suiteName) {
    String result = m_statusBySuiteName.get(suiteName);
    return result != null ? result : "passed";
  }

  public List<String> getGroups(String name) {
    List<String> result = Lists.newArrayList(m_groupsBySuiteName.get(name));
    Collections.sort(result);
    return result;
  }

  public List<String> getMethodsInGroup(String groupName) {
    List<String> result = Lists.newArrayList(m_methodsByGroup.get(groupName));
    Collections.sort(result);
    return result;
  }

  public List<ITestResult> getAllTestResults(ISuite suite) {
    return getAllTestResults(suite, true /* tests only */);
  }

  public List<ITestResult> getAllTestResults(ISuite suite, boolean testsOnly) {
    List<ITestResult> result = Lists.newArrayList();
    for (ISuiteResult sr : suite.getResults().values()) {
      result.addAll(sr.getTestContext().getPassedTests().getAllResults());
      result.addAll(sr.getTestContext().getFailedTests().getAllResults());
      result.addAll(sr.getTestContext().getSkippedTests().getAllResults());
      if (!testsOnly) {
        result.addAll(sr.getTestContext().getPassedConfigurations().getAllResults());
        result.addAll(sr.getTestContext().getFailedConfigurations().getAllResults());
        result.addAll(sr.getTestContext().getSkippedConfigurations().getAllResults());
      }
    }
    return result;
  }
}
