package com.capgemini.reports.definition.r26report;

import com.google.common.base.Joiner;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Ranges {

  private final List<Range> ranges;

  Ranges(List<Range> ranges) {
    this.ranges = ranges;
  }

  public String toString(final String revId) {
    List<Range> newRanges = new LinkedList<>();
    for (int index = 0; index < ranges.size(); index++) {
      Range currentRange = ranges.get(index);
      if (index == 0) {
        newRanges.add(currentRange);
      }
      else {
        int borneInfIndex = indexForIncludedBorne(newRanges, currentRange.borneInf());
        int borneSupIndex = indexForIncludedBorne(newRanges, currentRange.borneSup());

        if (borneInfIndex == -1 && borneSupIndex == -1) {
          int excludedBorneInfIndex = indexForExcludedBorne(newRanges, currentRange.borneInf());
          int excludedBorneSupIndex = indexForExcludedBorne(newRanges, currentRange.borneSup());

          if (excludedBorneInfIndex == excludedBorneSupIndex) {
            newRanges.add(excludedBorneInfIndex, new Range(currentRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
          }
          else {
            newRanges.set(excludedBorneInfIndex, new Range(currentRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
          }
        }
        else if (borneInfIndex <= newRanges.size() && borneSupIndex == -1) {
          Range previousRange = newRanges.get(borneInfIndex);
          newRanges.set(borneInfIndex, new Range(previousRange.getRevision(), previousRange.borneInf(), currentRange.borneInf() - 1));
          newRanges.add(new Range(currentRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
        }
        else if (borneInfIndex == borneSupIndex) {
          Range previousRange = newRanges.get(borneInfIndex);
          if (currentRange.borneInf() == previousRange.borneInf() && currentRange.borneSup() == previousRange.borneSup()) {
            newRanges.set(borneInfIndex, new Range(currentRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
          }
          else if (currentRange.borneInf() == previousRange.borneInf()) {
            newRanges.set(borneInfIndex, new Range(previousRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
            newRanges.add(borneInfIndex + 1, new Range(previousRange.getRevision(), currentRange.borneSup() + 1, previousRange.borneSup()));
          }
          else if (currentRange.borneSup() == previousRange.borneSup()) {
            newRanges.set(borneInfIndex, new Range(previousRange.getRevision(), previousRange.borneInf(), currentRange.borneInf() - 1));
            newRanges.add(borneInfIndex + 1, new Range(currentRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
          }
          else {
            newRanges.set(borneInfIndex, new Range(previousRange.getRevision(), previousRange.borneInf(), currentRange.borneInf() - 1));
            newRanges.add(borneInfIndex + 1, new Range(currentRange.getRevision(), currentRange.borneInf(), currentRange.borneSup()));
            newRanges.add(borneInfIndex + 2, new Range(previousRange.getRevision(), currentRange.borneSup() + 1, previousRange.borneSup()));
          }
        }
        else {
          for (int i = borneInfIndex; i <= borneSupIndex; i = i + 2) {
            Range nextRange = newRanges.get(i);
            if (i == borneInfIndex) {
              if (currentRange.borneInf() == nextRange.borneInf()) {
                newRanges.set(i, new Range(nextRange.getRevision(), nextRange.borneInf(), currentRange.borneInf()));
                newRanges.add(i + 1, new Range(currentRange.getRevision(), currentRange.borneInf() + 1, nextRange.borneSup()));
              }
              else {
                newRanges.set(i, new Range(nextRange.getRevision(), nextRange.borneInf(), currentRange.borneInf() - 1));
                newRanges.add(i + 1, new Range(currentRange.getRevision(), currentRange.borneInf(), nextRange.borneSup()));
              }
              borneSupIndex = borneSupIndex + 1;
            }
            else {
              if (!currentRange.isUp()) {
                newRanges.set(i, new Range(nextRange.getRevision(), nextRange.borneInf(), currentRange.borneSup()));
                newRanges.add(i + 1, new Range(currentRange.getRevision(), currentRange.borneSup() + 1, nextRange.borneSup()));
                borneSupIndex = borneSupIndex + 1;
              }
              else {
                newRanges.set(i, new Range(currentRange.getRevision(), nextRange.borneInf(), currentRange.borneSup()));
              }
            }
          }
        }
      }
    }

    List<Range> validRevs = filterValidRevs(revId, newRanges);
    return Joiner.on(";").join(consolidateRevs(validRevs)).replace("2147483646", "UP");
  }

  private List<Range> filterValidRevs(String revId, List<Range> newRanges) {
    List<Range> validRevs = new ArrayList<>();
    for (Range range : newRanges) {
      if (revId.equals(range.getRevision())) {
        validRevs.add(range);
      }
    }
    return validRevs;
  }

  private List<Range> consolidateRevs(List<Range> validRevs) {
    List<Range> consolidatedRanges = new ArrayList<>();
    if (validRevs.size() == 1) {
      consolidatedRanges.add(validRevs.get(0));
    }
    else if (validRevs.size() == 2) {
      Range previousRange = validRevs.get(0);
      Range currentRange = validRevs.get(1);
      if (previousRange.borneSup() + 1 == currentRange.borneInf()) {
        Range consolidatedRange = new Range(currentRange.getRevision(), previousRange.borneInf(), currentRange.borneSup());
        consolidatedRanges.add(consolidatedRange);
      }
      else {
        consolidatedRanges.addAll(validRevs);
      }
    }
    else {
      for (int i = 0; i < validRevs.size(); i++) {
        int indexToConsolidate = indexToConsolidate(validRevs, i);
        if (indexToConsolidate != -1) {
          Range previousRange = validRevs.get(i);
          Range nextRange = validRevs.get(indexToConsolidate);
          Range consolidatedRange = new Range(nextRange.getRevision(), previousRange.borneInf(), nextRange.borneSup());
          consolidatedRanges.add(consolidatedRange);
          i = indexToConsolidate;
        }
        else {
          consolidatedRanges.add(validRevs.get(i));
        }
      }
    }
    return consolidatedRanges;
  }

  private int indexForExcludedBorne(List<Range> newRanges, int borne) {
    if (borne < newRanges.get(0).borneInf()) {
      return 0;
    }
    if (borne > newRanges.get(newRanges.size() - 1).borneSup()) {
      return newRanges.size();
    }

    for (int i = 1; i < newRanges.size(); i++) {
      Range previousRange = newRanges.get(i - 1);
      Range range = newRanges.get(i);
      if (borne >= previousRange.borneSup() && borne <= range.borneInf()) {
        return i;
      }
    }
    return -1;
  }

  private int indexForIncludedBorne(List<Range> newRanges, int borne) {
    for (int i = 0; i < newRanges.size(); i++) {
      Range range = newRanges.get(i);
      if (borne >= range.borneInf() && borne <= range.borneSup()) {
        return i;
      }
    }
    return -1;
  }

  private int indexToConsolidate(List<Range> newRanges, int startIndex) {
    for (int i = startIndex; i < newRanges.size(); i++) {
      Range currentRange = newRanges.get(i);
      if (i == 0 && i < newRanges.size() - 1) {
        Range nextRange = newRanges.get(i + 1);
        if (currentRange.borneSup() + 1 != nextRange.borneInf()) {
          return -1;
        }
      }
      else if (i > 0 && i < newRanges.size() - 1) {
        Range previousRange = newRanges.get(i - 1);
        Range nextRange = newRanges.get(i + 1);
        if (previousRange.borneSup() + 1 == currentRange.borneInf() && currentRange.borneSup() + 1 != nextRange.borneInf()) {
          return i;
        }
        else if (currentRange.borneSup() + 1 == nextRange.borneInf() && nextRange.isUp()) {
          return i + 1;
        }
      }
    }
    return -1; // not found
  }
}
