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

import gov.cms.grouper.snf.SnfContext;
import gov.cms.grouper.snf.r2.logic.SnfDataVersionImpl;
import gov.cms.grouper.snf.model.enums.NursingCmg;
import gov.cms.grouper.snf.model.enums.Rai300;
import gov.cms.grouper.snf.util.ClaimInfo;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * <a href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=693" class="req">CATEGORY:
 * SPECIAL CARE HIGH/SPECIAL CARE LOW/CLINICALLY COMPLEX</a>
 */
public class SpecialCare extends SnfDataVersionImpl<NursingCmg> {

  public static final List<Rai300> SKIN_TREATMENTS = Arrays.asList(Rai300.M1200C, Rai300.M1200D,
      Rai300.M1200E, Rai300.M1200G, Rai300.M1200H);
  public static final List<Rai300> SKIN_TREATMENTS_2 = Arrays.asList(Rai300.M1200A, Rai300.M1200B);
  private final ClaimInfo claim;

  public SpecialCare(ClaimInfo claim) {
    super(claim.getDataVersion());
    this.claim = claim;
  }


  /**
   * Evaluate the resident's special care high nursing cmg. If the resident IS NOT qualified for
   * special care high category, the resident will be evaluated for special care low. If the
   * resident IS qualified for special care high category, but the resident function score is 15 or
   * 16, then the resident will be evaluated for clinically complex category. <a
   * href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=693" class=
   * "req">CATEGORY:SPECIAL CARE HIGH</a>
   *
   * @return the Nursing Cmg based on the different evaluations.
   */
  protected NursingCmg evaluateSpecialCareHigh() {
    int functionScore = claim.getFunctionScore();
    // Step #2
    // TODO look up in table NURSING_CMG (still need to be setup)
    if (0 <= functionScore && functionScore <= 5) {
      return isDepressed() ? NursingCmg.HDE2 : NursingCmg.HDE1;
    } else if (6 <= functionScore && functionScore <= 14) {
      return isDepressed() ? NursingCmg.HBC2 : NursingCmg.HBC1;
    } else if (functionScore >= 15) {
      return evaluateClinicallyComplex();
    }

    return null;
  }

  /**
   * Evaluate whether the resident is classified into special care high category.
   *
   * @return true if the resident is classified into special care high, false otherwise.
   */
  protected boolean isSpecialCareHighApplicable() {
    Predicate<Object> eval = (o) -> false;
    eval = eval
        .or((o) -> isComatose())
        .or((o) -> isSepticemia())
        .or((o) -> isDiabetes())
        .or((o) -> isQuadriplegia())
        .or((o) -> isTroubleBreathing())
        .or((o) -> isParenteral())
        .or((o) -> isSick())
        .or((o) -> isRespiratory());
    boolean result = eval.test(null);
    return result;
  }

  /**
   * Evaluate the resident's special care low nursing cmg. If the resident is not qualified for
   * special care low, then clinically complex will be evaluated. <a href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=696"
   * class= "req">CATEGORY:SPECIAL CARE LOW</a>
   *
   * @return the Nursing Cmg related to special care low if qualifies or Clinically Complex Cmg.
   */
  protected NursingCmg evaluateSpecialCareLow() {
    int functionScore = claim.getFunctionScore();

    // Step #2
    // TODO look up in table NURSING_CMG (still need to be setup)
    if (0 <= functionScore && functionScore <= 5) {
      return isDepressed() ? NursingCmg.LDE2 : NursingCmg.LDE1;
    } else if (6 <= functionScore && functionScore <= 14) {
      return isDepressed() ? NursingCmg.LBC2 : NursingCmg.LBC1;
    } else if (15 <= functionScore) {
      return evaluateClinicallyComplex();
    }

    return null;
  }

  /**
   * Check whether the resident qualifies for special care low.
   *
   * @return true if the resident qualifies for special care low, false otherwise.
   */
  @SuppressWarnings("OverlyComplexMethod")
  public boolean isSpecialCareLowApplicable() {
    Predicate<Object> eval = (o) -> false;
    eval = eval
        .or((o) ->isCerebralPalsy())
        .or((o) ->isMultiSclerosis())
        .or((o) ->isParkinson())
        .or((o) ->isRespiratoryOxyTherapy())
        .or((o) ->isStage2Ulcer())
        .or((o) ->isStage3Or4Ulcer())
        .or((o) ->isTwoOrMoreVenousArterialUlcers())
        .or((o) ->isStage2UlcerAndVenousArterialUlcer())
        .or((o) ->isFoot())
        .or((o) ->claim.isCheckedAndNotNull(Rai300.O0100B2))
        .or((o) ->claim.isCheckedAndNotNull(Rai300.O0100J2))
        .or((o) ->isClassifiedTubeFeeding());
    boolean result = eval.test(null);
    return result;
  }

  /**
   * Evaluate the resident's clinically complex nursing cmg, which is calculated if the resident is
   * not qualified for special care low nursing cmg. If the resident is not qualified for clinically
   * complex nursing cmg, behavioral symptoms and cognitive performance will be checked. <a
   * href="doc-files/mds-3.0-rai-manual-v1.17.1_october_2019.pdf#page=699" class=
   * "req">CATEGORY:CLINICALLY COMPLEX</a>
   *
   * @return the Nursing Cmg related to the Clinically Complex Cmg or null if not applicable
   */
  public NursingCmg evaluateClinicallyComplex() {

    int functionScore = claim.getFunctionScore();

      // TODO look up in table NURSING_CMG (still need to be setup)
    if (0 <= functionScore && functionScore <= 5) {
      return isDepressed() ? NursingCmg.CDE2 : NursingCmg.CDE1;
    } else if (6 <= functionScore && functionScore <= 14) {
      return isDepressed() ? NursingCmg.CBC2 : NursingCmg.CBC1;
    } else if (15 <= functionScore && functionScore <= 16) {
      return isDepressed() ? NursingCmg.CA2 : NursingCmg.CA1;
    }

    return null;
  }

  /**
   * Determine whether the claim is coded with appropriate function score boundaries for Clinically
   * Complex CMG
   *
   * @return true if the resident qualifies for clinically complex, false otherwise.
   */
  public boolean isClinicallyComplexApplicable() {
    // TODO refactor out list assessment supplier
    Predicate<Object> eval = (o) -> false;
    eval = eval
        .or((o) ->claim.isCheckedAndNotNull(Rai300.I2000))
        .or((o) ->isHemiplegiaOrHemiparesis())
        .or((o) ->isOpenLesions())
        .or((o) ->claim.isCheckedAndNotNull(Rai300.M1040F))
        .or((o) ->claim.isCheckedAndNotNull(Rai300.O0100A2))
        .or((o) ->claim.isCheckedAndNotNull(Rai300.O0100C2))
        .or((o) ->claim.isCheckedAndNotNull(Rai300.O0100H2))
        .or((o) ->claim.isCheckedAndNotNull(Rai300.O0100I2));
    boolean result = eval.test(null);
    return result;
  }

  @SuppressWarnings("OverlyComplexBooleanExpression")
  private boolean isOpenLesions() {
    return (claim.isCheckedAndNotNull(Rai300.M1040D) || claim.isCheckedAndNotNull(Rai300.M1040E))
        && (claim.isCheckedAndNotNull(Rai300.M1200F) || claim.isCheckedAndNotNull(Rai300.M1200G)
        || claim.isCheckedAndNotNull(Rai300.M1200H));
  }

  private boolean isHemiplegiaOrHemiparesis() {
    return claim.isCheckedAndNotNull(Rai300.I4900)
        && claim.getFunctionScore() <= BscpLogic.FUNCTION_SCORE_LIMIT;
  }

  /**
   * Check the resident's Total Severity Scores (D0300, D0600) to determine whether the resident is
   * depressed.
   *
   * @return a boolean indicating whether the resident is depressed
   */
  public boolean isDepressed() {

    int d0300SeverityScore = claim.getAssessmentValue(Rai300.D0300);

    boolean result = (d0300SeverityScore >= 10 && d0300SeverityScore != 99)
        || (claim.getAssessmentValue(Rai300.D0600) >= 10);

    return SnfContext.trace(result);
  }

  @Override
  public NursingCmg exec() {
    if(isSpecialCareHighApplicable()) {
      return evaluateSpecialCareHigh();
    } else if (isSpecialCareLowApplicable()) {
      return evaluateSpecialCareLow();
    } else if(isClinicallyComplexApplicable()) {
      return evaluateClinicallyComplex();
    } else {
      return null;
    }
   }

  public boolean isFoot() {
    return claim.isAnyAssessmentValuesPresent(EnumSet.of(Rai300.M1040A, Rai300.M1040B, Rai300.M1040C))
        && claim.isCheckedAndNotNull(Rai300.M1200I);
  }

  public boolean isStage2UlcerAndVenousArterialUlcer() {
    return getSkinTreatmentsCount() >= 2
        && claim.isCheckedAndNotNull(Rai300.M1030)
        && claim.isCheckedAndNotNull(Rai300.M0300B1);
  }

  public boolean isTwoOrMoreVenousArterialUlcers() {
    return getSkinTreatmentsCount() >= 2
        && claim.isAnyAssessmentValuesGreaterThanN(EnumSet.of(Rai300.M1030), 1);
  }

  public boolean isStage3Or4Ulcer() {
    return getSkinTreatmentsCount() >= 2
        && claim.isAnyAssessmentValuesGreaterThanN(EnumSet.of(Rai300.M0300C1, Rai300.M0300D1, Rai300.M0300F1), 0);
  }

  public boolean isStage2Ulcer() {
    return getSkinTreatmentsCount() >= 2
        && claim.isAnyAssessmentValuesGreaterThanN(EnumSet.of(Rai300.M0300B1), 1);

  }

  protected int getSkinTreatmentsCount() {
    int skinTreatmentsCount = claim.countAssessmentPresent(SKIN_TREATMENTS);
    int overlapCount = claim.countAssessmentPresent(SKIN_TREATMENTS_2);
    if (overlapCount >= 1) {
      // Count Pressure relieving as one treatment even if both provided.
      skinTreatmentsCount++;
    }
    return skinTreatmentsCount;
  }

  public boolean isFeedingTube() {
    return claim.isAnyAssessmentValuesPresent(EnumSet.of(Rai300.K0510B1, Rai300.K0510B2))
        && isClassifiedTubeFeeding();
  }


  public boolean isRespiratoryOxyTherapy() {
    return (boolean) claim.isCheckedAndNotNull(Rai300.I6300)
        && claim.isCheckedAndNotNull(Rai300.O0100C2);
  }


  public boolean isParkinson() {
    return claim.isCheckedAndNotNull(Rai300.I5300)
        && claim.getFunctionScore() <= BscpLogic.FUNCTION_SCORE_LIMIT;
  }


  public boolean isMultiSclerosis() {
    return claim.isCheckedAndNotNull(Rai300.I5200)
        && claim.getFunctionScore() <= BscpLogic.FUNCTION_SCORE_LIMIT;
  }


  public boolean isCerebralPalsy() {
    return claim.isCheckedAndNotNull(Rai300.I4400)
        && claim.getFunctionScore() <= BscpLogic.FUNCTION_SCORE_LIMIT;
  }


  public boolean isRespiratory() {
    return claim.hasAssessmentOf(Rai300.O0400D2, item -> item.getValueInt() == 7);
  }


  @SuppressWarnings("OverlyComplexBooleanExpression")
  public boolean isSick() {
    Supplier<Boolean> fever = () -> claim.isCheckedAndNotNull(Rai300.J1550A);
    Supplier<Boolean> weightLo = () -> claim.hasAssessmentOf(Rai300.K0300, item -> 1 <= item.getValueInt() && item.getValueInt() <= 2);
    Supplier<Boolean> pneumoniaOrVomitting = () -> claim.isAnyAssessmentValuesPresent(EnumSet.of(Rai300.I2000, Rai300.J1550B));
    Supplier<Boolean> feedingTube = this::isFeedingTube;

    return fever.get() && (weightLo.get() || pneumoniaOrVomitting.get() || feedingTube.get());
  }

  public boolean isParenteral() {
    return claim.isAnyAssessmentValuesPresent(EnumSet.of(Rai300.K0510A1, Rai300.K0510A2));
  }


  protected boolean isClassifiedTubeFeeding() {
    Supplier<Boolean> k0710a3Equals3 = () -> claim.hasAssessmentOf(Rai300.K0710A3, item -> item.getValueInt() == 3);
    Supplier<Boolean> k0710a3Equals2 = () -> claim.hasAssessmentOf(Rai300.K0710A3, item -> item.getValueInt() == 2);
    Supplier<Boolean> k0710b3 = () -> claim.hasAssessmentOf(Rai300.K0710B3, item -> item.getValueInt() == 2);

    return k0710a3Equals3.get() || (k0710a3Equals2.get() && k0710b3.get());
  }


  public boolean isTroubleBreathing() {
    return claim.isCheckedAndNotNull(Rai300.I6200) && claim.isCheckedAndNotNull(Rai300.J1100C);
  }

  public boolean isComatose() { return claim.isComaAndNoActivities(); }

  public boolean isSepticemia() {
    return claim.isCheckedAndNotNull(Rai300.I2100);
  }

  public boolean isDiabetes() {
    return claim.isCheckedAndNotNull(Rai300.I2900)
        && claim.hasAssessmentOf(Rai300.N0350A, (item) -> item.getValueInt() == 7)
        && claim.hasAssessmentOf(Rai300.N0350B, (item) -> item.getValueInt() >= 2);
  }

  public boolean isQuadriplegia() {
    return claim.isCheckedAndNotNull(Rai300.I5100)
        && claim.getFunctionScore() <= BscpLogic.FUNCTION_SCORE_LIMIT;
  }

  public ClaimInfo getClaim() {
    return claim;
  }
}
