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

import gov.cms.grouper.snf.SnfContext;
import gov.cms.grouper.snf.component.r2.logic.NursingLogic;
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.enums.NursingCmg;
import gov.cms.grouper.snf.model.reader.Rai300;
import gov.cms.grouper.snf.util.ClaimInfo;

/**
 * <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> {

  private final ClaimInfo claim;

  private final boolean classifiedTubeFeeding;
  private final int finalSkinTreatmentsCount;
  private final boolean radiation;
  private final boolean dialysis;
  private final boolean depressed;

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

    this.finalSkinTreatmentsCount = this.getSkinTreatmentsCount();
    this.radiation = claim.isCheckedAndNotNull(Rai300.O0100B2);
    this.dialysis = claim.isCheckedAndNotNull(Rai300.O0100J2);
    this.classifiedTubeFeeding = isClassifiedTubeFeeding();
    this.depressed = isDepressed();
  }


  /**
   * 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 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() {
    boolean isSpecialCareHighApplicable = isSpecialCareHighApplicable();
    int functionScore = this.claim.getFunctionScore();

    NursingCmg result = null;
    if (isSpecialCareHighApplicable) {
      // Step #2
      if (functionScore >= 15) {
        result = evaluateClinicallyComplex(true);
      }
      // TODO look up in table NURSING_CMG (still need to be setup)
      if (SnfComparator.betweenInclusive(0, functionScore, 5)) {
        if (this.depressed) {
          result = NursingCmg.HDE2;
        } else {
          result = NursingCmg.HDE1;
        }
      } else if (functionScore >= 6 && functionScore <= 14) {
        if (this.depressed) {
          result = NursingCmg.HBC2;
        } else {
          result = NursingCmg.HBC1;
        }
      }
    } else {
      result = evaluateSpecialCareLow();
    }

    return SnfContext.trace(result);
  }

  /**
   * 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() {

    boolean comatose = isComatose();
    boolean septicemia = isSepticemia();
    boolean diabetes = isDiabetes();
    boolean quadriplegia = isQuadriplegia();
    boolean breathing = isBreathing();
    boolean parenteral = isParenteral();
    boolean sick = isSick();
    boolean respiratory = isRespiratory();

    boolean result = SnfUtils.any(comatose, septicemia, diabetes, quadriplegia, breathing,
        parenteral, sick, respiratory);

    return SnfContext.trace(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() {
    boolean isSpecialCareLowApplicable = this.isSpecialCareLowApplicable();
    int functionScore = claim.getFunctionScore();

    NursingCmg result = null;

    if (isSpecialCareLowApplicable) {
      // Step #2
      if (functionScore >= 15) {
        result = this.evaluateClinicallyComplex(true);
      }
      if (functionScore >= 0 && functionScore <= 5) {
        if (this.depressed) {
          result = NursingCmg.LDE2;
        } else {
          result = NursingCmg.LDE1;
        }
      } else if (functionScore >= 6 && functionScore <= 14) {
        if (this.depressed) {
          result = NursingCmg.LBC2;
        } else {
          result = NursingCmg.LBC1;
        }
      }
    } else {
      result = this.evaluateClinicallyComplex(false);
    }

    return SnfContext.trace(result);
  }

  /**
   * Check whether the resident qualifies for special care low.
   *
   * @return true if the resident qualifies for special care low, false otherwise.
   */
  public boolean isSpecialCareLowApplicable() {

    boolean cerebralPalsy = isCerebralPalsy();
    boolean multiSclerosis = isMultiSclerosis();
    boolean parkinson = isParkinson();
    boolean respiratoryOxyTherapy = isRespiratoryOxyTherapy();
    boolean stage2Ulcer = isStage2Ulcer();
    boolean stage3Or4Ulcer = isStage3Or4Ulcer();
    boolean twoOrMoreVenousArterialUlcers = isTwoOrMoreVenousArterialUlcers();
    boolean stage2UlcerAndVenousArterialUlcer = isStage2UlcerAndVenousArterialUlcer();
    boolean foot = this.isFoot();

    boolean result =
        SnfUtils.any(cerebralPalsy, multiSclerosis, parkinson, respiratoryOxyTherapy, stage2Ulcer,
            stage3Or4Ulcer, twoOrMoreVenousArterialUlcers, stage2UlcerAndVenousArterialUlcer, foot,
            this.radiation, this.dialysis, this.classifiedTubeFeeding);

    return SnfContext.trace(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(boolean forceClinicallyComplexApplicable) {

    int functionScore = this.claim.getFunctionScore();
    boolean isClinicallyComplexApplicable =
        forceClinicallyComplexApplicable || this.isClinicallyComplexApplicable();
    NursingCmg result = null;

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

    return SnfContext.trace(result);
  }

  /**
   * 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() {
    int functionScore = this.claim.getFunctionScore();

    // TODO refactor out list assessment supplier
    Boolean isPneumonia = claim.isCheckedAndNotNull(Rai300.I2000);
    Boolean isHemiplegiaOrHemiparesis =
        claim.isCheckedAndNotNull(Rai300.I4900) && functionScore <= Const.functionScoreLimit;
    Boolean isOpenLesions =
        (claim.isCheckedAndNotNull(Rai300.M1040D) || claim.isCheckedAndNotNull(Rai300.M1040E))
            && (claim.isCheckedAndNotNull(Rai300.M1200F) || claim.isCheckedAndNotNull(Rai300.M1200G)
                || claim.isCheckedAndNotNull(Rai300.M1200H));
    Boolean isBurnsSecondOrThirdDegree = claim.isCheckedAndNotNull(Rai300.M1040F);
    Boolean isChemotherapyWhileResident = claim.isCheckedAndNotNull(Rai300.O0100A2);
    Boolean isOxygenTherapyWhileResident = claim.isCheckedAndNotNull(Rai300.O0100C2);
    Boolean isIVMedicationsWhileResident = claim.isCheckedAndNotNull(Rai300.O0100H2);
    Boolean isTransfusionsWhileResident = claim.isCheckedAndNotNull(Rai300.O0100I2);

    boolean result = isPneumonia || isHemiplegiaOrHemiparesis || isOpenLesions
        || isBurnsSecondOrThirdDegree || isChemotherapyWhileResident || isOxygenTherapyWhileResident
        || isIVMedicationsWhileResident || isTransfusionsWhileResident;

    return SnfContext.trace(result);
  }

  /**
   * 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 d0300TotalSeverityScore = claim.getAssessmentValue(Rai300.D0300);
    int d0600TotalSeverityScore = claim.getAssessmentValue(Rai300.D0600);

    boolean result = (d0300TotalSeverityScore >= 10 && d0300TotalSeverityScore != 99)
        || (d0600TotalSeverityScore >= 10);

    return SnfContext.trace(result);
  }

  @Override
  public NursingCmg exec() {

    NursingCmg result = evaluateSpecialCareHigh();

    return SnfContext.trace(result);
  }


  public boolean isFoot() {
    boolean valuesPresent = claim
        .isAnyAssessmentValuesPresent(SnfUtils.toSet(Rai300.M1040A, Rai300.M1040B, Rai300.M1040C));
    boolean m1200i = claim.isCheckedAndNotNull(Rai300.M1200I);
    boolean result = valuesPresent && m1200i;
    return result;
  }


  public boolean isStage2UlcerAndVenousArterialUlcer() {
    boolean m1030 = claim.isCheckedAndNotNull(Rai300.M1030);
    boolean m0300b1 = claim.isCheckedAndNotNull(Rai300.M0300B1);
    boolean result = this.finalSkinTreatmentsCount >= 2 && m1030 && m0300b1;
    return result;
  }

  public boolean isTwoOrMoreVenousArterialUlcers() {
    boolean m1030 = claim.isAnyAssessmentValuesGreaterThanN(SnfUtils.toSet(Rai300.M1030), 1);
    boolean result = m1030 && finalSkinTreatmentsCount >= 2;
    return result;

  }


  public boolean isStage3Or4Ulcer() {
    boolean meet = claim.isAnyAssessmentValuesGreaterThanN(
        SnfUtils.toSet(Rai300.M0300C1, Rai300.M0300D1, Rai300.M0300F1), 0);
    boolean result = meet && finalSkinTreatmentsCount >= 2;
    return result;
  }


  public boolean isStage2Ulcer() {
    boolean m0300b1 = claim.isAnyAssessmentValuesGreaterThanN(SnfUtils.toSet(Rai300.M0300B1), 1);
    boolean result = m0300b1 && finalSkinTreatmentsCount >= 2;
    return result;

  }

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

  public boolean isFeedingTube() {
    boolean present =
        claim.isAnyAssessmentValuesPresent(SnfUtils.toSet(Rai300.K0510B1, Rai300.K0510B2));
    boolean classifiedTubeFeeding = this.classifiedTubeFeeding;
    boolean result = present && classifiedTubeFeeding;
    return result;

  }


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


  public boolean isParkinson() {
    int functionScore = this.claim.getFunctionScore();
    boolean i5300 = claim.isCheckedAndNotNull(Rai300.I5300);
    boolean result = i5300 && functionScore <= Const.functionScoreLimit;
    return result;
  }


  public boolean isMultiSclerosis() {
    boolean i5200 = claim.isCheckedAndNotNull(Rai300.I5200);
    int functionScore = this.claim.getFunctionScore();
    boolean result = i5200 && functionScore <= Const.functionScoreLimit;
    return result;

  }


  public boolean isCerebralPalsy() {
    boolean i4400 = claim.isCheckedAndNotNull(Rai300.I4400);
    int functionScore = this.claim.getFunctionScore();

    boolean result = i4400 && functionScore <= Const.functionScoreLimit;
    return result;
  }


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


  public boolean isSick() {
    boolean j1550a = claim.isCheckedAndNotNull(Rai300.J1550A);
    boolean hasK0300 = claim.hasAssessmentOf(Rai300.K0300,
        item -> SnfComparator.betweenInclusive(1, item.getValueInt(), 2));
    boolean anyPresentJ1550B$I2000 =
        claim.isAnyAssessmentValuesPresent(SnfUtils.toSet(Rai300.I2000, Rai300.J1550B));
    boolean anyPresentK0510B1$K0510B2 =
        claim.isAnyAssessmentValuesPresent(SnfUtils.toSet(Rai300.K0510B1, Rai300.K0510B2));

    boolean result = j1550a && (hasK0300 || anyPresentJ1550B$I2000
        || anyPresentK0510B1$K0510B2 && this.classifiedTubeFeeding);
    return result;
  }

  public boolean isParenteral() {
    boolean result =
        claim.isAnyAssessmentValuesPresent(SnfUtils.toSet(Rai300.K0510A1, Rai300.K0510A2));
    return result;
  }


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

    boolean result = k0710a3Equals3 || (k0710a3Equals2 && k0710b3);

    return result;
  }


  public boolean isBreathing() {
    boolean result =
        claim.isCheckedAndNotNull(Rai300.I6200) && claim.isCheckedAndNotNull(Rai300.J1100C);
    return result;
  }

  public boolean isComatose() {
    boolean result = claim.isComaAndNoActivities(() -> claim.getAssessmentValue(Rai300.B0100));
    return result;
  }

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

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

  public boolean isQuadriplegia() {
    int nursingFunctionScore = this.claim.getFunctionScore();
    boolean result =
        claim.isCheckedAndNotNull(Rai300.I5100) && nursingFunctionScore <= Const.functionScoreLimit;
    return result;
  }

  public ClaimInfo getClaim() {
    return claim;
  }

  public int getFinalSkinTreatmentsCount() {
    return finalSkinTreatmentsCount;
  }

  public boolean isRadiation() {
    return radiation;
  }

  public boolean isDialysis() {
    return dialysis;
  }
}
