/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * @author max
 */
package com.intellij.java.psi;

import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.testFramework.LightJavaCodeInsightTestCase;
import org.jetbrains.annotations.NonNls;

public class TreeIsCorrectAfterDiffReparseTest extends LightJavaCodeInsightTestCase {

  public void testIDEADEV41862() throws Throwable {
    @NonNls String part1 = """
      package com.test;


      //------------------------------------------------------------------
      // Copyright (c) 1999, 2007
      // WorkForce Software, Inc.
      // All rights reserved.
      //
      // Web-site: http://www.workforcesoftware.com
      // E-mail:   support@workforcesoftware.com
      // Phone:    (877) 493-6723
      //
      // This program is protected by copyright laws and is considered
      // a trade secret of WorkForce Software.  Access to this program
      // and source code is granted only to licensed customers.  Under
      // no circumstances may this software or source code be distributed
      // without the prior written consent of WorkForce Software.
      // -----------------------------------------------------------------

      import com.workforcesoftware.Data.Employee.*;
      import com.workforcesoftware.Data.*;
      import com.workforcesoftware.Data.assignment.Asgnmt_master;
      import com.workforcesoftware.Data.assignment.Asgnmt;
      import com.workforcesoftware.Data.Output.Time_sheet_output;
      import com.workforcesoftware.Data.TimeSched.PayPeriodData;
      import com.workforcesoftware.Data.TimeSched.TimeSchedUtils;
      import com.workforcesoftware.Data.TimeSched.Schedule.Schedule_detailList;
      import com.workforcesoftware.Data.TimeSched.TimeSheet.*;
      import com.workforcesoftware.Data.assignment.Asgnmt_masterList;
      import com.workforcesoftware.Exceptions.*;
      import com.workforcesoftware.Gen.Choice.Approval_event_type;
      import com.workforcesoftware.Gen.Choice.Transaction_status;
      import com.workforcesoftware.Gen.Choice.Messages;
      import com.workforcesoftware.Gen.Choice.Program_source;
      import com.workforcesoftware.Gen.Other.DbRec.DbRecTime_sheet_output;
      import com.workforcesoftware.Gen.Other.DbRec.DbRecTime_sheet;
      import com.workforcesoftware.Gen.Other.List.WDateList;
      import com.workforcesoftware.Gen.Policy.Right_grp;
      import com.workforcesoftware.Gen.Policy.Policy_profile;
      import com.workforcesoftware.Policy.*;
      import com.workforcesoftware.Util.DateTime.WDate;
      import com.workforcesoftware.Util.DateTime.WDateTime;
      import com.workforcesoftware.Util.DB.ListWriter;
      import com.workforcesoftware.Util.DB.DbRecFieldCopier;
      import com.workforcesoftware.ClientRequests.TimeSched.TimeSchedUtil;
      import com.workforcesoftware.ClientRequests.TimeSched.PayPeriodInfo;
      import com.workforcesoftware.ClientRequests.TimeEntry.ApprovalInfo;
      import com.workforcesoftware.ClientRequests.TimeEntry.BankBalanceResults;
      import com.workforcesoftware.Misc.ServerErrorLogger;
      import com.workforcesoftware.Misc.ServerError;
      import com.workforcesoftware.AssignmentPeriod.*;
      import com.workforcesoftware.AssignmentPeriod.TimeSheetState;
      import com.workforcesoftware.Dictionary.DataDictionary;

      import java.sql.SQLException;
      import java.util.*;
      import org.log4j.Category;

      /**
       * Holds definition of {@link CalcInfo}
       */
      class AssignmentManager {
        /**
         * Implementation of {@link TimeSheetCalculationInfo}.
         * Extracts all the required data from AllCalcDataManager, AllCalculationData, Time_sheet, etc.
         * Makes copies of all mutable data, unmodifiable lists where possible.
         */
        static class CalcInfo implements TimeSheetCalculationInfo {
          /**
           * Use the passed in AllCalculationData object - DO NOT try to get it from the passed in AllCalcDataManager because
           * the ACD object that's passed in is obtained from AssignmentManager.getAllCalcData method which handles the
           * scenario where a prior period may need to be amended automatically and if ACDM.getAllCalcData() method is called,
           * you might get an unmodifiable ACD
           * @param acdm
           * @param acd
           * @param timeSheetId
           * @param parms
           */
          CalcInfo(AllCalcDataManager acdm, AllCalculationData acd, TimeSheetIdentifier timeSheetId, TimeEntryParms parms) {
            // we'll extract all these vars separately, for readability
            final Right_grp rightGrp = parms.getRight_grp(timeSheetId);
            final AllPolicies ap = acd.getAllPolicies();
            final WDate ppEndForToday = TimeSchedUtil.getPayPeriodRangeForToday(acdm).getEnd();

            this.acd = acd;
            this.timeSheetIdentifier = timeSheetId;
            this.approval_eventList = acd.getApproval_eventList().getUnmodifiableList();
            this.policyProfile = acd.getAsgnmtMaster().getPolicyProfilePolicy(ppEndForToday, ap);
            this.adjustmentsPaidWithTimesheet = createAdjustmentsPaidWithTimesheet(acdm, acd);
            this.bankBalanceResults = acdm.getBankDataForBankBalancePreview(ap, acd, rightGrp.getRight_grp(), acd.getPP_end());
            this.tsoListForPayPreview = Collections.unmodifiableList(acdm.getTsoListForPayPreview(acd, rightGrp.getRight_grp()));
            this.approvedDays = createApprovedDays(acd);

            try {
              // these are fairly involved calculations, and we want to validate the pay period list,
              // so we'll do them both here together instead of upon request
              employeePeriodInfo = acdm.getEmployeePeriodInfo(timeSheetId.getPpEnd());
              payPeriodList = TimeSchedUtil.calcPayPeriodList(acdm, rightGrp.getRight_grp());
            }
            catch (Exception e) {
              throw new InternalApplicationException("Loading calc data for: " + timeSheetId, e);
            }
            assertPayPeriodList();

            AssignmentPeriodStateImpl aps = new AssignmentPeriodStateImpl(employeePeriodInfo,
                rightGrp, parms.getSystemFeature(), ap, ppEndForToday, acdm, policyProfile);
            assignmentPeriodState = aps;
            timeSheetState = new TimeSheetStateImpl(this, aps, policyProfile);
          }

          /**
           * Returns an ApprovalInfo object, which contains information about whether
           * employee/manager approved a timesheet.
           *
           * @see ApprovalInfo
           */
          public ApprovalInfo getApprovalInfo() { return new ApprovalInfo(acd.getTime_sheet(), approval_eventList); }

          public Collection<DbRecTime_sheet_output> getAdjustmentsPaidWithTimesheet() {
            return adjustmentsPaidWithTimesheet;
          }

          public Approval_eventList getApproval_eventList() {
            return approval_eventList;
          }


          /**
           * Returns state information about a given assignment and period, encompassing
           * the cross-section of security related settings calculated from:
           * <ul>
           * <li>App_user Roles (app_user_right and right_grp tables)
           * <li>Assignment effective dates
           * <li>Employee record effective dates
           * <li>System_feature read/write privileges
           * </ul>
           *
           * @return not ever null
           */
          public AssignmentPeriodState getAssignmentPeriodState() {
            return assignmentPeriodState;
          }

          public Set<WDate> getDailyApprovalDays() {
            return approvedDays;
          }

          /**
           * Returns map to the accrual banks data, which is a read only reference
           * of the information contained there. The banks returned will be all banks attached
           * to the assignment, including aggregate banks if applicable.
           *
           * The list will not contain any banks that the current App_user's Right_grp cannot view for this assignment's
           * Policy_profile (as defined by {@link com.workforcesoftware.Gen.Policy.User_entry_rule_detail#getDisplay_bank_set()})
           */
          public List<BankBalanceResults> getBankBalanceResults() {
            return bankBalanceResults;
          }

          public Approval_event getLastValidEmployeeApproveEvent() {
            return approval_eventList.getLastValidEmployeeApproveEvent();
          }

          public Time_sheet_detail_splitList getTime_sheet_detail_splitList() {
            return acd.getTime_sheet().getTime_sheet_detail_splitList().getUnmodifiableList();
          }

          public Time_sheet_detailList getTime_sheet_detailList() {
            return acd.getTime_sheet().getDetailList().getUnmodifiableList();
          }

          public Schedule_detailList getSchedule_detailList() {
            return acd.getSchedule().getDetailList().getUnmodifiableList();
          }

          public Time_sheet_exceptionList getTime_sheet_exceptionList() {
            return acd.getTime_sheet().getTime_sheet_exceptionList().getUnmodifiableList();
          }

          public List<Time_sheet_output> getTsoListForPayPreview() {
            return tsoListForPayPreview;
          }

          public Approval_eventList getValidManagerApproveEvents() {
            return approval_eventList.getValidManagerApproveEvents();
          }

          public TimeSheetIdentifier getTimeSheetIdentifier() {
            return timeSheetIdentifier;
          }

          public EmployeePeriodInfo getEmployeePeriodInfo() {
            return employeePeriodInfo;
          }

          public Employee_master getEmployeeMaster() {
            return new Employee_master(acd.getEmployee_master());
          }

          public Employee getEmployee() {
            return new Employee(acd.getEmployee_master().getEmployeeAsOfOrAfter(getPPEnd()));
          }

          public Asgnmt_master getAsgnmtMaster() {
            return new Asgnmt_master(acd.getAsgnmtMaster());
          }

          public Asgnmt getAsgnmt() {
            return new Asgnmt(acd.getAsgnmt());
          }

          public WDate getPPEnd() {
            return employeePeriodInfo.getPp_end();
          }

          public WDate getPPBegin() {
            return employeePeriodInfo.getPp_begin();
          }

          public Policy_profile getPolicy_profile() {
            return policyProfile;
          }


          /**
           * Return state information about the timesheet, which considers security
           * relationship between the current state (locked/closed/amended) in relation to:
           * <ul>
           * <li>App_user Roles (app_user_right and right_grp tables)
           * <li>Assignment effective dates
           * <li>Employee record effective dates
           * <li>System_feature read/write privileges
           * <li>Approval level of the timesheet
           * </ul>
           *
           * @return not ever null
           */
          public TimeSheetState getTimeSheetState() {
            return timeSheetState;
          }

          public int getVersionNumber() {
            return timeSheetIdentifier.getVersion();
          }

          public PayPeriodData getPayPeriodData() {
            return acd.getPayPeriodData();
          }

          public boolean getIsNewlyUserAmended() {
            return acd.isNewlyUserAmended();
          }

          public List<PayPeriodInfo> getPayPeriodList() {
            return payPeriodList;
          }

          private static Collection<DbRecTime_sheet_output> createAdjustmentsPaidWithTimesheet(AllCalcDataManager acdm, AllCalculationData acd) {
            try { return Collections.unmodifiableCollection(acdm.getAdjustmentsPaidWithTimesheet(acd)); }
            catch (SQLException e) { throw new InternalApplicationException("Unexpected SQL Exception", e); }
          }

          private void assertPayPeriodList() {
            //TODO - probably need to show a "nice" message to the user saying that the employee doesn't
            //TODO - have a viewable/editable pay period.
            if(payPeriodList.isEmpty()) {
              throw new InternalApplicationException("No viewable/editable pay periods found for employee "
                      + employeePeriodInfo.getEmployeeMaster().getEmployee());
            }
          }

          private static Set<WDate> createApprovedDays(AllCalculationData acd) {
            // calculate days approved by daily approvals
            Set<WDate> approvedDays = new HashSet<WDate>();
            for( WDate date : acd.getPayPeriodData().getActiveDates().getAllDates() ) {
              if( acd.isDayApproved(date) ) { approvedDays.add(date); }
            }
            return Collections.unmodifiableSet(approvedDays);
          }

          private final Collection<DbRecTime_sheet_output> adjustmentsPaidWithTimesheet;
          private final Approval_eventList approval_eventList;
          private final AssignmentPeriodState assignmentPeriodState;
          private final List<BankBalanceResults> bankBalanceResults;
          private final List<Time_sheet_output> tsoListForPayPreview;
          private final TimeSheetIdentifier timeSheetIdentifier;
          private final TimeSheetState timeSheetState;
          private final EmployeePeriodInfo employeePeriodInfo;
          private final ArrayList<PayPeriodInfo> payPeriodList;
          private final Set<WDate> approvedDays;
          private final Policy_profile policyProfile;
          private final AllCalculationData acd;
        }

        AssignmentManager(GeneratedId assignmentId, Set<TimeSheetIdentifier> timeSheetIdentifiers, TimeEntryParmsPerAssignment parms) {
          this.asgnmtMaster = getAggregateOrSingleAssignmentMaster(assignmentId);
          setClassFields(parms, timeSheetIdentifiers);
        }

        /**
         * Obtains the single or aggregate assignment master for the given assignmentId.  If the given assignmentId is not a
         * single or aggregate assignment, fetches the aggregate associated with the given assignmentId and returns that.
         *
         * @param assignmentId Id of the assigment to load.
         * @return the single or aggregate assignment master for the given assignmentId.
         */
        private static Asgnmt_master getAggregateOrSingleAssignmentMaster(GeneratedId assignmentId) {
          Asgnmt_master am = Asgnmt_master.load(assignmentId);
          if(am.isAggregate() || am.isSingle()) {
            return am;
          }

          //If it's not a single or aggregate, we need to load the aggregate and load that.
          return Asgnmt_master.load(am.getAggregate_asgnmt());
        }

        AggregateTimeEntryTransactionResults load() {
          return loadImpl();
        }

        /**
         * Reverts any changes that have not been committed to the database on this AssignmentManager
         */
        void revert() {
          updatedAllCalcDataManager = null;
        }

        /**
         * @return Return initial transaction results from originalAllCalcData, which must be set before calling this.
         */
        private AggregateTimeEntryTransactionResults loadImpl() {
          AggregateTimeEntryTransactionResults aggregateResults = (AggregateTimeEntryTransactionResults) parms.getTimeEntryResultsFactory().newInstance();
          for (TimeSheetIdentifier timeSheetId : getTimeSheetIdentifiers()) {
            AllCalculationData acd = getOriginalAllCalcData(timeSheetId);
            // Make sure system_record_id's are assigned - TimeEntryManager and client do not function correctly without
            assignSystemRecordIds(acd);
            CalcInfo calcInfo = new CalcInfo(getOriginalAllCalcDataManager(timeSheetId.getAsgnmt()), acd, timeSheetId, parms);
            //todo-ute-nazim do we really need to perform TimeSheetDiff for the initial load ?
            TimeEntryTransactionResults singleTimeSheetResults =
                    new TimeSheetDiff(timeSheetId, acd, calcInfo, TimeSheetCounters.getEmptyCounters(), NewToOldIdMap.EMPTY,
                                      Collections.EMPTY_LIST, new StatusMapsForTimesheetAndSchedule(), true, parms, null);
            aggregateResults.add(singleTimeSheetResults);
          }
          return aggregateResults;
        }

        /**
         * Assign system_record_id's for time_sheet, time_sheet_detail's and time_sheet_exception's
         * @param acd
         */
        private void assignSystemRecordIds(AllCalculationData acd) {
          assignSystemRecordId(acd.getTime_sheet());
          assignSystemRecordIds(acd.getTime_sheet().getTime_sheet_detailList());
          assignSystemRecordIds(acd.getTime_sheet().getTime_sheet_exceptionList());
        }

        /**
         * todo: store the event.getComment()
         * @param event
         */
        AggregateTimeEntryTransactionResults amend(TransactionApprovalEvent event) {
          TimeSheetIdentifier singleTimeSheetId = event.getTimeSheetIdentifier();
          //TODO: Shouldn't this call createOriginalAllCalcDataManagerIfNeeded?  Seems like we needlessly reload the original
          //TODO: ACDM.  This is because a timesheet which was prepared for amendment couldn't have been modified before it
          //TODO: was amended.
          invalidateAllCalcDataManagers();

          // Create the amended ACD and put into the ACDM cache
          EmployeePeriodInfo epInfo = null;
          try {
            AllCalcDataManager acdm = getOriginalAllCalcDataManager(singleTimeSheetId.getAsgnmt());
            epInfo = acdm.getEmployeePeriodInfo(singleTimeSheetId.getPpEnd());
            AllCalculationData amendedAllCalcData = acdm.createAmendedAllCalcData(epInfo, parms.getApp_user(), WDateTime.now());
            // No need to hook this up to approval change logic, since it is an in-memory change so far.
            //Recalculate the amended ACD object so that timesheet exceptions that are on the closed version (if any)
            // get created on the amended timesheet as well
            amendedAllCalcData.recalc(acdm);
          }
          catch (SQLException e) {
            throw new InternalApplicationException("amending time sheet for " + singleTimeSheetId, e);
          }
          catch (MultipleRowDbRecException e) {
            throw new InternalApplicationException("amending time sheet for " + singleTimeSheetId, e);
          } catch (Exception e) {
            throw new InternalApplicationException("amending time sheet for " + singleTimeSheetId, e);
          }
          return loadImpl();
        }

        TimeSheetTransactionApplier applyTransaction(TimeSheetIdentifier id, TimeEntryTransaction trans) {
          AllCalcDataManager updatedAcdm = getUpdatedAllCalcDataManager(id.getAsgnmt());
          AllCalculationData acd = getAllCalcData(updatedAcdm, id);
          return new TimeSheetTransactionApplier(trans, updatedAcdm, acd, parms, id);
        }

        private void recalc(TimeSheetIdentifier timeSheetId, Approval_event_type approvalEventType) {
          AllCalcDataManager updatedAcdm = getUpdatedAllCalcDataManager(timeSheetId.getAsgnmt());
          AllCalculationData acd = getAllCalcData(updatedAcdm, timeSheetId);
          try {
            if (approvalEventType == Approval_event_type.SAVE_SCHEDULE) {
              // if we're saving a change to the schedule, the timesheet might need to be re-initialized.
              acd.reinitTimeSheet(updatedAcdm, true);
            }
            acd.recalc(updatedAcdm);
          }
          catch (Exception e) {
            throw new InternalApplicationException("calculate for " + timeSheetId, e);
          }
        }

        /**
         * Save one timesheet to a ListWriter using AllCalculationData.
         * @param timeSheetId
         * @param saveEvent
         * @throws Exception
         */
        void save(TimeSheetIdentifier timeSheetId, Approval_event_type saveEvent) throws Exception {
          GeneratedId asgnmtId = timeSheetId.getAsgnmt();
          AllCalcDataManager acdMgr = getUpdatedAllCalcDataManager(asgnmtId);
          AllCalculationData acd = getAllCalcData(acdMgr, timeSheetId);

          // TODO: it'd be nice if we were able to retry a save if it failed because of unrelated changes (to accomodate for
          // concurrent save operations on other component timesheets besides the one that the user is trying to save)
          final ListWriter lw = new ListWriter();
          acd.saveAfterApplyingApprovalEvent(acdMgr, saveEvent, lw);

          //The exceptions must be generated after the ACD has performed approval related operations.
          // Loading the original ACD to get the timesheet object before the approval. This will be used to determine
          // timesheet's approval level before this save event.
          AllCalculationData originalAcd = getAllCalcData(getOriginalAllCalcDataManager(asgnmtId), timeSheetId);
          acdMgr.generateExceptionNotifications(acd, originalAcd.getTime_sheet(), acd.getTime_sheet(), lw);
          lw.writeLists();

        }

        /**
         * If time sheets were saved and written to the database successfully, update internal data
         * to reflect what's in the database as the new "original" data.
         */
        void postSave() {
          originalAllCalcDataManager = updatedAllCalcDataManager;
          updatedAllCalcDataManager = null;
        }

        /**
         * Returns true if the given timesheet contains exceptions that should prevent save.
         * @param timeSheetId time sheet ID
         * @return true if exceptions exist that should prevent save, false otherwise.
         */
        boolean hasExceptionsPreventingSave(TimeSheetIdentifier timeSheetId) {
          AllCalculationData acd = getUpdatedAllCalcData(timeSheetId);
          try {
            return acd.getTime_sheet().getTime_sheet_exceptionList().getDisallow_timesheet_save(acd.getAllPolicies());
          }
          catch (PolicyLookupRequiredException e) {
            throw new InternalApplicationException("Policy error on timesheet " + timeSheetId);
          }
        }

        /**
         * Returns true if the given timesheet contains exceptions that should prevent submit.
         * @param timeSheetId time sheet ID
         * @return true if exceptions exist that should prevent submit, false otherwise.
         */
        boolean hasExceptionsPreventingSubmit(TimeSheetIdentifier timeSheetId) {
          AllCalculationData acd = getUpdatedAllCalcData(timeSheetId);
          try {
            return acd.getTime_sheet().getTime_sheet_exceptionList().getDisallow_timesheet_submit(acd.getAllPolicies());
          }
          catch (PolicyLookupRequiredException e) {
            throw new InternalApplicationException("Policy error on timesheet " + timeSheetId);
          }
        }

        /**
         * Compute {@link TimeEntryTransactionResults} changes to be returned in client.
         * @param timeSheetId
         * @param trans transaction which client just sent
         * @param applyResults results (errors) of applying the transaction
         * @param timeSheetSavedStatus a status to be added representing the save results - SAVED or ERROR, or null if none.
         * @param transactionSuccessful value for {@link TimeEntryTransactionResults#getTransactionWasSuccessful}.
         */
        TimeSheetDiff getDiff(TimeSheetIdentifier timeSheetId, TimeEntryTransaction trans,
                              TimeSheetTransactionApplier applyResults, TransactionStatus timeSheetSavedStatus,
                              boolean transactionSuccessful)
        {
          AllCalcDataManager acdm = getLatestAllCalcDataManager(timeSheetId.getAsgnmt());
          AllCalculationData acd = getAllCalcData(acdm, timeSheetId);
          NewToOldIdMap newToOldIdMap = applyResults.getNewToOldIdMap();
          StatusMapsForTimesheetAndSchedule timeSheetRowStatusMap = applyResults.getTimeEntryRowErrorMap();

          if (cat.isDebugEnabled()) {
            ObjectDumper.debug(cat, "TimeSheetDiff getDiff before", timeSheetRowStatusMap, null, true);
          }

          List<TransactionStatus> timeSheetStatuses = new ArrayList<TransactionStatus>(applyResults.getTimeSheetErrors());
          TimeSheetCounters counters = new TimeSheetCounters(trans, timeSheetId, newToOldIdMap);
          if (timeSheetSavedStatus != null) {
            timeSheetStatuses.add(timeSheetSavedStatus);
            if (timeSheetSavedStatus.getTransaction_status() == Transaction_status.SAVED ||
                    timeSheetSavedStatus.getTransaction_status() == Transaction_status.ERROR) {
              // if we saved or there is an error mark every TimeEntryRow in the transaction as such.
              // get all row id's from the transaction (new id's - not temp id's)
              Collection transTimeSheetRowIds = applyResults.getTransactionTimeSheetRowIds();
              Collection transScheduleRowIds = applyResults.getTransactionScheduleRowIds();
              timeSheetRowStatusMap = new StatusMapsForTimesheetAndSchedule(transTimeSheetRowIds, transScheduleRowIds, timeSheetSavedStatus);
              timeSheetRowStatusMap.addAll(applyResults.getTimeEntryRowErrorMap());
            }
          }

          if (cat.isDebugEnabled()) {
            ObjectDumper.debug(cat, "TimeSheetDiff getDiff after", timeSheetRowStatusMap, null, true);
          }

          CalcInfo calcInfo = new CalcInfo(acdm, acd, timeSheetId, parms);
          return new TimeSheetDiff(timeSheetId, acd, calcInfo, counters, newToOldIdMap, timeSheetStatuses,
                                   timeSheetRowStatusMap, transactionSuccessful, parms, trans);
        }

        TimeSheetCalculationInfo getCalculationInfo(TimeSheetIdentifier timeSheetIdentifier) {
          validate(timeSheetIdentifier);
          final AllCalcDataManager acdm = getLatestAllCalcDataManager(timeSheetIdentifier.getAsgnmt());
          return new CalcInfo(acdm, getAllCalcData(acdm, timeSheetIdentifier), timeSheetIdentifier, parms);
        }

        /**
         * Return the calculation info without auto-amending
         * @param acdm
         * @param timeSheetIdentifier
         * @return
         */
        private TimeSheetCalculationInfo getCalculationInfo(AllCalcDataManager acdm, TimeSheetIdentifier timeSheetIdentifier) {
          validate(timeSheetIdentifier);
          try {
            return new CalcInfo(acdm, acdm.getAllCalcData(timeSheetIdentifier.getPpEnd(), timeSheetIdentifier.getVersion()), timeSheetIdentifier, parms);
          } catch (Exception e) {
            throw new InternalApplicationException("Error creating CalcInfo ", e);\s
          }
        }

        private void invalidateAllCalcDataManagers() {
          originalAllCalcDataManager = null;
          updatedAllCalcDataManager = null;
        }

        private void createUpdatedAllCalcDataManager(Set<TimeSheetIdentifier> timeSheetIds) {
          AllCalcDataManager origAcdMgr = getOriginalAllCalcDataManager(asgnmtMaster.getAsgnmt());
          try {
            WDateList dateList = new WDateList();
            for(TimeSheetIdentifier id : timeSheetIds) {
              dateList.add(id.getPpEnd());
            }
            updatedAllCalcDataManager = origAcdMgr.copyForUpdate(dateList);
          } catch (Exception e) {
            throw new InternalApplicationException("Cloning for " + timeSheetIds, e);
          }
        }

        private void validate(TimeSheetIdentifier timeSheetId) throws IllegalArgumentException {
          if (!timeSheetIdentifiers.contains(timeSheetId)) {
            throw new IllegalArgumentException("Invalid time sheet specified: " + timeSheetId
              + ". not in " + timeSheetIdentifiers);
          }
        }

        /**
         * Obtains the original AllCalcDataManager for the given assignment Id.  The assignment id must be given because in a
         * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers
         * to the aggregate assignment.
         *
         * @param asgnmtId The id of the assignment to load.
         * @return the original AllCalcDataManager for the given assignment Id
         */
        private AllCalcDataManager getOriginalAllCalcDataManager(GeneratedId asgnmtId) {
          if(originalAllCalcDataManager == null) {
            originalAllCalcDataManager = createOriginalAllCalcDataManager();
            updatedAllCalcDataManager = null;
          }

          if(asgnmtId.equals(originalAllCalcDataManager.getAsgnmtId())) {
            return originalAllCalcDataManager;
          }

          return originalAllCalcDataManager.getAllCalcDataManager(asgnmtId);
        }

        private AllCalcDataManager createOriginalAllCalcDataManager() {
          AllPolicies allPolicies = PolicyManager.getInstance().getAllPolicies();
          try {
            return AllCalcDataManager.createForAsgnmt(asgnmtMaster, allPolicies);
          }
          catch (Exception e) {
            throw new InternalApplicationException("Failed to load ACDM for assignment with id:"+asgnmtMaster.getAsgnmt(), e);
          }
        }

        /**
         * Obtains the updated AllCalcDataManager for the given assignment Id.  The assignment id must be given because in a
         * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers
         * to the aggregate assignment.
         *
         * @param asgnmtId The id of the assignment to load.
         * @return the updated AllCalcDataManager for the given assignment Id
         */
        private AllCalcDataManager getUpdatedAllCalcDataManager(GeneratedId asgnmtId) {
          if(updatedAllCalcDataManager == null) {
            throw new InternalApplicationException("Attempted to use the updated all calc data manager before it was created.");
          }

          if(asgnmtId.equals(updatedAllCalcDataManager.getAsgnmtId())) {
            return updatedAllCalcDataManager;
          }

          return updatedAllCalcDataManager.getAllCalcDataManager(asgnmtId);
        }

        /**
         * Obtains the updated AllCalcDataManager for the given assignment Id, if present.  If not present, returns the
         * original ACDM instead.  The assignment id must be given because in a
         * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers
         * to the aggregate assignment.
         *
         * @param asgnmtId The id of the assignment to load.
         * @return  he updated AllCalcDataManager for the given assignment Id, if present.  If not present, returns the
         * original ACDM instead.
         */
        AllCalcDataManager getLatestAllCalcDataManager(GeneratedId asgnmtId) {
          if (updatedAllCalcDataManager != null) {
            return getUpdatedAllCalcDataManager(asgnmtId);
          }

          return getOriginalAllCalcDataManager(asgnmtId);
        }

        //todo-ute: the method name is misleading as it may also create a new amended ACD if period is amendable by system on user's behalf
        private AllCalculationData getAllCalcData(AllCalcDataManager mgr, TimeSheetIdentifier id) {
          try {
            EmployeePeriodInfo epInfo = mgr.getEmployeePeriodInfo(id.getPpEnd());
            if (epInfo == null) {
              final PolicyID asgnmtPolicyProfile = mgr.getAsgnmtMaster().getPolicy_profile(id.getPpEnd());
              final PolicyID policyProfileId = (asgnmtPolicyProfile == null) ? PolicyID.EMPTY_POLICYID : asgnmtPolicyProfile;
              throw new InvalidPayPeriodException("EmployeePeriodInfo could not be obtained for employee " +
                  id.getEmployee() + " and assignment " + id.getAsgnmt() + " and period end - " + id.getPpEnd() + ". " +
