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

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mmm.his.cer.foundation.exception.FoundationException;
import gov.cms.grouper.snf.SnfComponentVersion;
import gov.cms.grouper.snf.SnfContext;
import gov.cms.grouper.snf.SnfRuntimeOption;
import gov.cms.grouper.snf.SnfTables;
import gov.cms.grouper.snf.component.r2.logic.CognitiveLevelLogic;
import gov.cms.grouper.snf.component.r2.logic.NtaLogic;
import gov.cms.grouper.snf.component.r2.logic.NursingLogic;
import gov.cms.grouper.snf.component.r2.logic.SpeechLogic;
import gov.cms.grouper.snf.component.r2.logic.TherapyLogic;
import gov.cms.grouper.snf.component.r2.logic.nursing.BscpLogic;
import gov.cms.grouper.snf.component.r2.logic.nursing.ReducedPhysicalFunctionLogic;
import gov.cms.grouper.snf.component.r2.logic.nursing.SpecialCare;
import gov.cms.grouper.snf.lego.SnfCache;
import gov.cms.grouper.snf.lego.SnfComparator;
import gov.cms.grouper.snf.lego.SnfCache.Reference;
import gov.cms.grouper.snf.lego.SnfUtils;
import gov.cms.grouper.snf.model.ClinicalCategory;
import gov.cms.grouper.snf.model.CognitiveLevel;
import gov.cms.grouper.snf.model.SnfComponentAbstract;
import gov.cms.grouper.snf.model.SnfDiagnosisCode;
import gov.cms.grouper.snf.model.SnfError;
import gov.cms.grouper.snf.model.SnfProcessError;
import gov.cms.grouper.snf.model.enums.NursingCmg;
import gov.cms.grouper.snf.model.reader.Rai300;
import gov.cms.grouper.snf.model.table.BasicRow;
import gov.cms.grouper.snf.model.table.DiagnosisMasterRow;
import gov.cms.grouper.snf.transfer.ISnfClaim;
import gov.cms.grouper.snf.util.ClaimInfo;

public class SnfComponent extends SnfComponentAbstract {

  private static Logger log = LoggerFactory.getLogger(SnfComponent.class);

  public SnfComponent() {
    this(100);
  }

  public SnfComponent(int dataVersion) {
    super(dataVersion);
    if (this.getClass().equals(SnfComponent.class)
        && SnfComparator.betweenInclusive(SnfComponentVersion.r2.getFrom(), dataVersion,
            SnfComponentVersion.r2.getTo()) == false) {
      throw new RuntimeException("Version miss match");
    }

  }

  /**
   * Generate HIPPS code based on CMG from each payment component
   *
   * @param therapyLogicCmg PT/OT CMG
   * @param speechLogicCmg SLP CMG
   * @param nursingLogicCmg Nursing CMG
   * @param ntaPaymentCompLogicCmg NTA CMG
   * @param aiCode Based on item A0310B, IPA is 0 and 5-Day is 1
   * @return HIPPS codes as five characters string
   */
  protected static String getHIPPSCode(String therapyLogicCmg, String speechLogicCmg,
      NursingCmg nursingLogicCmg, String ntaPaymentCompLogicCmg, Integer aiCode) {
    return String.join("", therapyLogicCmg.substring(1), speechLogicCmg.substring(1),
        nursingLogicCmg.getHipps(), ntaPaymentCompLogicCmg.substring(1),
        Objects.toString(aiCode, ""));
  }

  public static List<SnfDiagnosisCode> populate(int version, List<SnfDiagnosisCode> dxCodes) {
    return populate(version, dxCodes, null);
  }

  public static List<SnfDiagnosisCode> populate(int version, List<SnfDiagnosisCode> dxCodes,
      Consumer<String> codes) {
    List<SnfDiagnosisCode> result = new ArrayList<>();
    for (SnfDiagnosisCode code : dxCodes) {
      try {
        Set<DiagnosisMasterRow> row = SnfTables.getAll(SnfTables.diagnosisMasterTable,
            SnfUtils.trimICDCodes(code.getValue()), BasicRow.getVersionSelector(), version);
        result.addAll(row.stream().map(DiagnosisMasterRow::of).collect(Collectors.toList()));
      } catch (NullPointerException ex) {
        log.warn("Invalid ICD 10 code: " + code.getValue());
        if (codes != null) {
          codes.accept(code.getValue());
        }
      }
    }
    return result;
  }

  @Override
  public Boolean hasIPA(Integer aiCode, Integer obra) {
    Boolean result = null;
    if (aiCode != null) {
      if (aiCode == 1) {
        // 5-day assessment
        result = false;
      } else if (aiCode == 0) {
        // IPA - Interim payment assessment
        result = true;
      }
    }

    return result;
  }

  private void addInvalidControlElementsError(ISnfClaim claim) {
    // when execute with snfClaim
    if (claim.getOriginalRecord() == null) {
      claim.addErrors(SnfError.INVALID_A0310A_OR_A0310B.getReason(claim.getAiCode().toString(),
          claim.getObra().toString()));
    }
    // when execute with fixed length string
    else {
      Rai300 a0310a = Rai300.A0310A;
      Rai300 a0310b = Rai300.A0310B;
      int begin = a0310a.getIndex() - 1;
      int end = begin + a0310a.getLength();
      String a0310aValue = claim.getOriginalRecord().substring(begin, end);
      begin = a0310b.getIndex() - 1;
      end = begin + a0310b.getLength();
      String a0310bValue = claim.getOriginalRecord().substring(begin, end);
      claim.addErrors(SnfError.INVALID_A0310A_OR_A0310B.getReason(a0310aValue, a0310bValue));
    }
  }

  @Override
  public void reconfigure(SnfRuntimeOption option) throws FoundationException {
    // TODO Auto-generated method stub

  }

  @Override
  public void exec(ISnfClaim claim) {
    SnfContext.trace(claim);

    Integer aiCode = claim.getAiCode();
    Boolean hasIPA = hasIPA(aiCode, claim.getObra());

    if (hasIPA == null) {
      addInvalidControlElementsError(claim);
    }

    if (claim.hasError()) {
      throw new SnfProcessError();
    }

    ClaimInfo asstInfo = ClaimInfo.of(this.getDataVersion(), hasIPA, claim.getAssessments());
    try {
      claim.setPrimaryDiagnosis(SnfTables.get(SnfTables.diagnosisMasterTable,
          SnfUtils.trimICDCodes(claim.getPrimaryDiagnosis().getValue()),
          BasicRow.getVersionSelector(), this.getDataVersion()).of());
    } catch (NullPointerException ex) {
      log.warn("Invalid Pdx code: " + claim.getPrimaryDiagnosis().getValue());
    }

    List<SnfDiagnosisCode> secondaryDiagnoses =
        SnfComponent.populate(this.getDataVersion(), claim.getSecondaryDiagnoses());

    final ClinicalCategory pdxClinicalCategory = TherapyLogic.getPdxClinicalCategory(
        this.getDataVersion(), asstInfo, claim.getPrimaryDiagnosis().getValue());

    if (claim.hasError() == false) {
      String therapyLogicCmg =
          new TherapyLogic(this.getDataVersion(), asstInfo, pdxClinicalCategory).exec();

      CognitiveLevel cognitiveLevel = new CognitiveLevelLogic(asstInfo).exec();
      String speechLogicCmg = new SpeechLogic(this.getDataVersion(), asstInfo, secondaryDiagnoses,
          cognitiveLevel, pdxClinicalCategory).exec();

      Reference<ReducedPhysicalFunctionLogic> physicRef = SnfCache.of();

      SpecialCare specialCare = new SpecialCare(asstInfo);
      BscpLogic bscp = new BscpLogic(asstInfo, physicRef);
      ReducedPhysicalFunctionLogic physical = new ReducedPhysicalFunctionLogic(asstInfo, bscp);
      physicRef.set(physical);

      NursingCmg nursingLogicCmg = new NursingLogic(asstInfo, specialCare, physical, bscp).exec();

      String ntaPaymentCompLogicCmg = new NtaLogic(asstInfo, secondaryDiagnoses).exec();

      String hippsCode = SnfComponent.getHIPPSCode(therapyLogicCmg, speechLogicCmg, nursingLogicCmg,
          ntaPaymentCompLogicCmg, aiCode);

      claim.setHippsCode(hippsCode);
    }
  }

  @Override
  public void close() {
    SnfUtils.doOrDie(() -> super.close());
  }

  @Override
  public SnfError validates(ISnfClaim claim) {
    // SnfValidations valid = new SnfValidations(claim);
    return null;
  }



}
