/**
   * Updates the tax lots for the transaction line in the case the transaction sub type is non-cash
   * and the user entered the transaction amount. This method is specific to the Asset Decrease
   * document.
   *
   * @param isSubTypeCash
   * @param endowmentTransactionSecurity
   * @param transLine
   */
  private void updateTaxLotsForSubTypeNonCashAndTransAmtNonZero(
      boolean isUpdate,
      EndowmentTransactionSecurity endowmentTransactionSecurity,
      EndowmentTransactionLine transLine) {

    BigDecimal transactionUnits = transLine.getTransactionUnits().bigDecimalValue();
    BigDecimal totalTaxLotsUnits = BigDecimal.ZERO;
    BigDecimal transactionAmount = transLine.getTransactionAmount().bigDecimalValue();
    BigDecimal perUnitValue = BigDecimal.ZERO;

    List<HoldingTaxLot> holdingTaxLots = new ArrayList<HoldingTaxLot>();
    Map<Integer, HoldingTaxLot> lotsMap = new HashMap<Integer, HoldingTaxLot>();

    if (!isUpdate) {
      transLine.getTaxLotLines().clear();
      holdingTaxLots =
          taxLotService.getAllTaxLots(
              transLine.getKemid(),
              endowmentTransactionSecurity.getSecurityID(),
              endowmentTransactionSecurity.getRegistrationCode(),
              transLine.getTransactionIPIndicatorCode());
    } else {
      List<EndowmentTransactionTaxLotLine> existingTransactionLines = transLine.getTaxLotLines();
      for (EndowmentTransactionTaxLotLine endowmentTransactionTaxLotLine :
          existingTransactionLines) {
        HoldingTaxLot holdingTaxLot =
            taxLotService.getByPrimaryKey(
                transLine.getKemid(),
                endowmentTransactionSecurity.getSecurityID(),
                endowmentTransactionSecurity.getRegistrationCode(),
                endowmentTransactionTaxLotLine.getTransactionHoldingLotNumber(),
                transLine.getTransactionIPIndicatorCode());

        if (ObjectUtils.isNotNull(holdingTaxLot)) {
          holdingTaxLots.add(holdingTaxLot);
        }
      }

      transLine.getTaxLotLines().clear();
    }

    Map<KualiInteger, EndowmentTransactionTaxLotLine> decreaseHoldingTaxLots =
        new HashMap<KualiInteger, EndowmentTransactionTaxLotLine>();

    if (holdingTaxLots != null && holdingTaxLots.size() > 0) {
      boolean keepIntegers = true;
      // compute the total number of units for tax lots
      for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
        totalTaxLotsUnits = totalTaxLotsUnits.add(holdingTaxLot.getUnits());

        // 3. Calculate the number of units to be transacted in each lot
        // check if percentage and tax lot units are integers
        BigDecimal lotUnits = BigDecimal.ZERO;
        try {
          int lotUnitsInt = holdingTaxLot.getUnits().intValueExact();
        } catch (ArithmeticException ex) {
          keepIntegers = false;
        }
      }

      for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
        EndowmentTransactionTaxLotLine taxLotLine = new EndowmentTransactionTaxLotLine();
        taxLotLine.setDocumentLineNumber(transLine.getTransactionLineNumber());

        BigDecimal lotUnits = BigDecimal.ZERO;
        // 2. Calculate percentage each lot holds out of the total units
        BigDecimal percentage =
            KEMCalculationRoundingHelper.divide(holdingTaxLot.getUnits(), totalTaxLotsUnits, 5);

        lotUnits =
            KEMCalculationRoundingHelper.multiply(
                percentage, transLine.getTransactionUnits().bigDecimalValue(), 5);

        // IF all original units per lot are integers (no decimal values), the result is rounded to
        // the nearest
        // integer and stored with the five decimals as zero. If the original units are not all
        // integers, then the
        // value is rounded to five decimals and stored as the five decimal values.
        if (keepIntegers) {
          lotUnits = lotUnits.setScale(0, BigDecimal.ROUND_HALF_UP);
          lotUnits = lotUnits.setScale(5);
        }
        taxLotLine.setLotUnits(lotUnits);

        // 6. Calculate holding cost
        BigDecimal holdingCost =
            KEMCalculationRoundingHelper.multiply(percentage, transactionAmount, 2);
        taxLotLine.setLotHoldingCost(holdingCost);

        // set tax lot line lot number and acquired date
        taxLotLine.setTransactionHoldingLotNumber(holdingTaxLot.getLotNumber().intValue());
        taxLotLine.setKemid(transLine.getKemid());
        taxLotLine.setSecurityID(holdingTaxLot.getSecurityId());
        taxLotLine.setRegistrationCode(holdingTaxLot.getRegistrationCode());
        taxLotLine.setIpIndicator(holdingTaxLot.getIncomePrincipalIndicator());
        taxLotLine.setLotAcquiredDate(holdingTaxLot.getAcquiredDate());

        // set the new lot indicator
        taxLotLine.setNewLotIndicator(false);

        // add the new tax lot line to the transaction line tax lots
        addTaxLotLine(transLine, taxLotLine);
        lotsMap.put(taxLotLine.getTransactionHoldingLotNumber(), holdingTaxLot);
      }
      adjustUnitsAndAmountsForNonCashAndTransactionAmtNotZero(transLine);
    }
  }
  /**
   * @see
   *     org.kuali.kfs.module.endow.document.service.UpdateTaxLotsBasedOnAccMethodAndTransSubtypeService#updateTransactionLineTaxLots(boolean,
   *     org.kuali.kfs.module.endow.document.EndowmentTaxLotLinesDocument,
   *     org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine)
   */
  public void updateTransactionLineTaxLots(
      boolean isUpdate,
      EndowmentTaxLotLinesDocument endowmentTaxLotLinesDocument,
      EndowmentTransactionLine transLine) {

    EndowmentTransactionSecurity endowmentTransactionSecurity =
        endowmentTaxLotLinesDocument.getSourceTransactionSecurity();
    String accountingMethod =
        parameterService.getParameterValueAsString(
            KfsParameterConstants.ENDOWMENT_ALL.class,
            EndowParameterKeyConstants.TAX_LOTS_ACCOUNTING_METHOD);
    Security security =
        securityService.getByPrimaryKey(endowmentTransactionSecurity.getSecurityID());

    if (ObjectUtils.isNotNull(security)) {
      // In the Asset Decrease document, if transaction sub type is non-cash and IF the user inserts
      // an amount in the
      // transaction amount, that amount along with the units sill be used to generate the
      // transaction tax lot lines. The
      // amount will not be calculated by the addition of the transaction line. In the event the
      // user has entered an amount in
      // the transaction amount field, that amount along with the units will be spread
      // proportionately among the tax lots.
      if (endowmentTaxLotLinesDocument instanceof AssetDecreaseDocument
          && EndowConstants.TransactionSubTypeCode.NON_CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())
          && transLine.getTransactionAmount() != null
          && transLine.getTransactionAmount().bigDecimalValue().compareTo(BigDecimal.ZERO) != 0) {
        updateTaxLotsForSubTypeNonCashAndTransAmtNonZero(
            isUpdate, endowmentTransactionSecurity, transLine);
      } else {

        if (EndowConstants.TaxLotsAccountingMethodOptions.AVERAGE_BALANCE.equalsIgnoreCase(
                accountingMethod)
            || (EndowConstants.TaxLotsAccountingMethodOptions.FIFO.equalsIgnoreCase(
                    accountingMethod)
                && !security.getClassCode().isTaxLotIndicator())) {
          if (EndowConstants.TransactionSubTypeCode.CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodAverageBalance(
                true, isUpdate, endowmentTransactionSecurity, transLine);
          }
          if (EndowConstants.TransactionSubTypeCode.NON_CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodAverageBalance(
                false, isUpdate, endowmentTransactionSecurity, transLine);
            setTransactionLineTotal(transLine);
          }
        }

        if ((EndowConstants.TaxLotsAccountingMethodOptions.FIFO.equalsIgnoreCase(accountingMethod)
                || EndowConstants.TaxLotsAccountingMethodOptions.LIFO.equalsIgnoreCase(
                    accountingMethod))
            && security.getClassCode().isTaxLotIndicator()) {
          boolean isFIFO =
              EndowConstants.TaxLotsAccountingMethodOptions.FIFO.equalsIgnoreCase(accountingMethod);

          if (EndowConstants.TransactionSubTypeCode.CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodFIFOorLIFO(
                true, isUpdate, isFIFO, endowmentTransactionSecurity, transLine);
          }
          if (EndowConstants.TransactionSubTypeCode.NON_CASH.equalsIgnoreCase(
              endowmentTaxLotLinesDocument.getTransactionSubTypeCode())) {
            updateTaxLotsForAccountingMethodFIFOorLIFO(
                false, isUpdate, isFIFO, endowmentTransactionSecurity, transLine);
            setTransactionLineTotal(transLine);
          }
        }
      }
    }
  }
  /**
   * Updates the tax lots for the transaction line in the case the accounting method is FIFO or
   * LIFO.
   *
   * @param isSubTypeCash
   * @param isFIFO
   * @param endowmentTransactionSecurity
   * @param transLine
   */
  private void updateTaxLotsForAccountingMethodFIFOorLIFO(
      boolean isSubTypeCash,
      boolean isUpdate,
      boolean isFIFO,
      EndowmentTransactionSecurity endowmentTransactionSecurity,
      EndowmentTransactionLine transLine) {
    BigDecimal transactionUnits = transLine.getTransactionUnits().bigDecimalValue();
    BigDecimal transactionAmount = BigDecimal.ZERO;
    BigDecimal perUnitVal = BigDecimal.ZERO;
    List<HoldingTaxLot> holdingTaxLots = new ArrayList<HoldingTaxLot>();

    if (!isUpdate) {
      transLine.getTaxLotLines().clear();
      holdingTaxLots =
          taxLotService.getAllTaxLotsOrderByAcquiredDate(
              transLine.getKemid(),
              endowmentTransactionSecurity.getSecurityID(),
              endowmentTransactionSecurity.getRegistrationCode(),
              transLine.getTransactionIPIndicatorCode(),
              isFIFO);
    } else {
      List<EndowmentTransactionTaxLotLine> existingTransactionLines = transLine.getTaxLotLines();
      for (EndowmentTransactionTaxLotLine endowmentTransactionTaxLotLine :
          existingTransactionLines) {
        HoldingTaxLot holdingTaxLot =
            taxLotService.getByPrimaryKey(
                transLine.getKemid(),
                endowmentTransactionSecurity.getSecurityID(),
                endowmentTransactionSecurity.getRegistrationCode(),
                endowmentTransactionTaxLotLine.getTransactionHoldingLotNumber(),
                transLine.getTransactionIPIndicatorCode());

        if (ObjectUtils.isNotNull(holdingTaxLot)) {
          holdingTaxLots.add(holdingTaxLot);
        }
      }
      transLine.getTaxLotLines().clear();
    }

    Map<KualiInteger, EndowmentTransactionTaxLotLine> decreaseHoldingTaxLots =
        new HashMap<KualiInteger, EndowmentTransactionTaxLotLine>();

    if (isSubTypeCash) {
      transactionAmount = transLine.getTransactionAmount().bigDecimalValue();
      // 1. Compute per unit value
      perUnitVal = KEMCalculationRoundingHelper.divide(transactionAmount, transactionUnits, 5);
    }

    BigDecimal remainingUnits = transactionUnits;

    if (holdingTaxLots != null && holdingTaxLots.size() > 0) {
      // compute the total number of units for tax lots
      for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
        EndowmentTransactionTaxLotLine taxLotLine = new EndowmentTransactionTaxLotLine();
        taxLotLine.setDocumentLineNumber(transLine.getTransactionLineNumber());

        // 2. Set the lot units

        // if transaction units is greater than the holding tax lot units then take all the holding
        // tax lot units
        if (remainingUnits.compareTo(holdingTaxLot.getUnits()) == 1) {
          taxLotLine.setLotUnits(holdingTaxLot.getUnits());
          remainingUnits = remainingUnits.subtract(holdingTaxLot.getUnits());
        }
        // if transaction units is less than the holding tax lot units then take just the remaining
        // number of units
        else {
          taxLotLine.setLotUnits(remainingUnits);
          remainingUnits = BigDecimal.ZERO;
        }

        // 4. Calculate the original unit value: cost/units
        BigDecimal originalUnitVal =
            KEMCalculationRoundingHelper.divide(
                holdingTaxLot.getCost(), holdingTaxLot.getUnits(), 5);

        // 5. Calculate original cost
        BigDecimal originalCost =
            KEMCalculationRoundingHelper.multiply(originalUnitVal, taxLotLine.getLotUnits(), 2);

        // set the tax lot holding cost
        taxLotLine.setLotHoldingCost(originalCost);

        // if sub type cash then the
        if (isSubTypeCash) {
          // 3. Calculate the value received for the units sold and round to 2 decimals
          BigDecimal receivedValue =
              KEMCalculationRoundingHelper.multiply(perUnitVal, taxLotLine.getLotUnits(), 2);
          // 6. Calculate the gain or loss
          calculateGainLoss(holdingTaxLot, taxLotLine, receivedValue, originalCost);
        }

        // set the lot number
        taxLotLine.setTransactionHoldingLotNumber(holdingTaxLot.getLotNumber().intValue());
        taxLotLine.setKemid(transLine.getKemid());
        taxLotLine.setSecurityID(holdingTaxLot.getSecurityId());
        taxLotLine.setRegistrationCode(holdingTaxLot.getRegistrationCode());
        taxLotLine.setIpIndicator(holdingTaxLot.getIncomePrincipalIndicator());
        // set the lot acquired date
        taxLotLine.setLotAcquiredDate(holdingTaxLot.getAcquiredDate());

        addTaxLotLine(transLine, taxLotLine);

        if (remainingUnits.compareTo(BigDecimal.ZERO) == 0) {
          break;
        }
      }
    }
  }