package gov.cms.grouper.snf.component.r2.logic.nursing;

import gov.cms.grouper.snf.SnfContext;
import gov.cms.grouper.snf.lego.SnfCache;
import gov.cms.grouper.snf.lego.SnfCache.SupplierCache;
import gov.cms.grouper.snf.lego.SnfComparator;
import gov.cms.grouper.snf.lego.SnfUtils;
import gov.cms.grouper.snf.lego.SnfDataVersionImpl;
import gov.cms.grouper.snf.model.Assessment;
import gov.cms.grouper.snf.model.enums.NursingCmg;
import gov.cms.grouper.snf.model.reader.Rai300;
import gov.cms.grouper.snf.util.ClaimInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

/**
 * <a href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=701" class=
 * "req">CATEGORY:BEHAVIORAL SYMPTOMS AND COGNITIVE PERFORMANCE</a>
 */
public class BscpLogic extends SnfDataVersionImpl<NursingCmg> {

  public static final List<Rai300> behavioralSymptomsRaiList =
      Arrays.asList(Rai300.E0200A, Rai300.E0200B, Rai300.E0200C, Rai300.E0800, Rai300.E0900);

  public static final List<Rai300> Services = Arrays.asList(Rai300.O0500C, Rai300.O0500E,
      Rai300.O0500G, Rai300.O0500H, Rai300.O0500I, Rai300.O0500J);


  private final ClaimInfo claim;
  private final Supplier<ReducedPhysicalFunctionLogic> physicalSupplier;
  private final Supplier<Integer> cacheC0500;
  private final Supplier<Integer> cacheC0100;
  private final Supplier<Boolean> isBehavioralSymptomsPresentCache;
  private final Supplier<NursingCmg> physicalCmg;

  public BscpLogic(ClaimInfo claim, Supplier<ReducedPhysicalFunctionLogic> physicalSupplier) {
    super(claim.getDataVersion());
    this.claim = claim;
    this.physicalSupplier = physicalSupplier;
    this.cacheC0500 = SnfCache.of(() -> claim.getAssessmentValue(Rai300.C0500));
    this.cacheC0100 = SnfCache.of(() -> claim.getAssessmentValue(Rai300.C0100));
    this.isBehavioralSymptomsPresentCache = getBehavioralSymptomsPresentCache();
    this.physicalCmg = SnfCache.of(() -> physicalSupplier.get().exec());
  }

  public List<Assessment> getBehavioralSymptomsAssessmentList(ClaimInfo claim) {
    List<Assessment> result = new ArrayList<>();
    for (Rai300 rai : behavioralSymptomsRaiList) {
      Assessment ast = claim.getAssessment(rai);
      if (ast != null) {
        result.add(ast);
      }
    }
    return Collections.unmodifiableList(result);

  }


  public boolean isClassifiedBehavioralSymptomsCognitivePerformance() {
    Supplier<Boolean> comatoseSupplier = SnfCache
        .of(() -> claim.isComaAndNoActivities(() -> claim.getAssessmentValue(Rai300.B0100)));
    boolean result = this.claim.isClassifiedBehavioralSymptomsCognitivePerformance(
        () -> this.claim.getAssessmentValue(Rai300.B0700),
        () -> this.claim.getAssessmentValue(Rai300.C0700),
        () -> this.claim.getAssessmentValue(Rai300.C1000), comatoseSupplier);

    return result;
  }

  public SupplierCache<Boolean> getBehavioralSymptomsPresentCache() {
    SupplierCache<Boolean> cache = SnfCache.of(() -> {
      boolean e0100AChecked = this.claim.isCheckedAndNotNull(Rai300.E0100A);
      boolean e0100BChecked = this.claim.isCheckedAndNotNull(Rai300.E0100B);
      List<Assessment> behavioralSymptomsAssessmentList =
          getBehavioralSymptomsAssessmentList(this.claim);
      boolean hasSymptoms = hasSymptoms(behavioralSymptomsAssessmentList);
      return isBehavioralSymptomsPresent(e0100AChecked, e0100BChecked, hasSymptoms);
    });

    return cache;
  }

  public boolean isBehavioralSymptomsPresent(boolean e0100AChecked, boolean e0100BChecked,
      boolean hasSymptoms) {

    boolean result = SnfUtils.any(hasSymptoms, e0100BChecked, e0100AChecked);

    return result;
  }

  public boolean hasSymptoms(List<Assessment> behavioralSymptomsAssessmentList) {
    boolean hasSymptoms = false;
    for (Assessment ast : behavioralSymptomsAssessmentList) {
      // since we already did a null check when loading in the list, no need for 2nd one
      hasSymptoms = ast.getValueInt() >= 2;
      // ClaimInfo.isTrue(ast, (item) -> item.getValueInt() >= 2);
      if (hasSymptoms) {
        break;
      }
    }
    return hasSymptoms;
  }

  /**
   * Determine whether the resident presents with one of the following behavioral symptoms.
   * <a href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=702" class="req">Step4</a>
   */
  protected NursingCmg step4(boolean isBehavioralSymptomsPresent) {
    NursingCmg result;

    if (isBehavioralSymptomsPresent) {
      result = step6();
    } else {
      result = this.physicalSupplier.get().exec();
    }

    return SnfContext.trace(result);
  }

  /**
   * Determine Restorative Nursing Count: Count the number of the following services provided for 15
   * or more minutes a day for 6 or more of the last 7 days
   * <a href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=703" class= "req">Step5</a>
   */
  public int step5() {
    int restorativeNursingCount = 0;
    final int h0200C = claim.getAssessmentValue(Rai300.H0200C);
    final int h0500 = claim.getAssessmentValue(Rai300.H0500);
    final int o0500A = claim.getAssessmentValue(Rai300.O0500A);
    final int o0500B = claim.getAssessmentValue(Rai300.O0500B);
    final int o0500D = claim.getAssessmentValue(Rai300.O0500D);
    final int o0500F = claim.getAssessmentValue(Rai300.O0500F);

    if (h0200C == Assessment.CHECKED || h0500 == Assessment.CHECKED) {
      restorativeNursingCount += 1;
    }
    if (o0500A >= Const.nursingCountNumDaysInLast7Days
        || o0500B >= Const.nursingCountNumDaysInLast7Days) {
      restorativeNursingCount += 1;
    }
    if (o0500D >= Const.nursingCountNumDaysInLast7Days
        || o0500F >= Const.nursingCountNumDaysInLast7Days) {
      restorativeNursingCount += 1;
    }

    for (Rai300 service : BscpLogic.Services) {
      if (this.claim.getAssessmentValue(service) >= Const.nursingCountNumDaysInLast7Days) {
        restorativeNursingCount += 1;
      }
    }

    return SnfContext.trace(restorativeNursingCount);
  }

  /**
   * Select the final PDPM Classification by using the total PDPM Nursing Function Score and the
   * Restorative Nursing Count
   * <a href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=703" class="req">Step6</a>
   */
  protected NursingCmg step6() {
    int nursingFunctionScore = claim.getFunctionScore();

    NursingCmg result = null;
    if (SnfComparator.betweenInclusive(Const.functionScoreLimit, nursingFunctionScore, 16)) {
      int step5RestorativeNursingCount = step5();
      if (step5RestorativeNursingCount >= 2) {
        result = NursingCmg.BAB2;
      } else {
        result = NursingCmg.BAB1;
      }
    }

    return SnfContext.trace(result);
  }


  public NursingCmg eval(boolean isClassifiedBehavioralSymptomsCognitivePerformance,
      Supplier<Integer> cacheC0500, Supplier<Integer> cacheC0100,
      Supplier<NursingCmg> physicalSupplier) {
    NursingCmg result = null;
    // step #1
    int nursingFunctionScore = claim.getFunctionScore();
    if (nursingFunctionScore < Const.functionScoreLimit) {
      result = physicalSupplier.get();
    } else if (cacheC0100.get() != 0 && cacheC0500.get() <= 9) {
      // step #2
      // step #5/6
      result = step6();
    } else if (cacheC0100.get() != 0 && cacheC0500.get() > 9 && cacheC0500.get() != 99) {
      // step #4
      result = step4(this.isBehavioralSymptomsPresentCache.get());
    } else if (isClassifiedBehavioralSymptomsCognitivePerformance) {
      // step #5/6
      result = step6();
    } else {
      // step #4
      result = step4(this.isBehavioralSymptomsPresentCache.get());
    }

    return SnfContext.trace(result);

  }

  @Override
  public NursingCmg exec() {

    boolean isClassifiedBehavioralSymptomsCognitivePerformance =
        isClassifiedBehavioralSymptomsCognitivePerformance();

    NursingCmg result = eval(isClassifiedBehavioralSymptomsCognitivePerformance, cacheC0500,
        cacheC0100, physicalCmg);

    return SnfContext.trace(result);
  }

  public static List<Rai300> getBehavioralsymptomsrailist() {
    return behavioralSymptomsRaiList;
  }

  public ClaimInfo getClaim() {
    return claim;
  }

  public Supplier<ReducedPhysicalFunctionLogic> getPhysicalSupplier() {
    return physicalSupplier;
  }

  public Supplier<Integer> getCacheC0500() {
    return cacheC0500;
  }

  public Supplier<Integer> getCacheC0100() {
    return cacheC0100;
  }

}
