package de.tu_dresden.diplom.richter_mirko_mat2628335.witness.context;

import org.apache.log4j.Logger;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.digestAgreement.IFDigestAgreementRequest;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.evidence.IFBodyEvidence;
import de.tu_dresden.diplom.richter_mirko_mat2628335.witness.account.*;
import de.tu_dresden.diplom.richter_mirko_mat2628335.witness.context.ex.ContextInfoInvalidException;
import de.tu_dresden.diplom.richter_mirko_mat2628335.witness.process.result.IFDigestAgreementConfirmationResult;
import de.tu_dresden.diplom.richter_mirko_mat2628335.witness.process.result.DigestAgreementConfirmationResult;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.entity.IFEntity;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.result.BaseResult;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.digestAgreement.IFDigestAgreementRequest;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.evidence.IFBodyEvidence;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.account.AmountTool;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.keystore.ApplicationSpecificKeystoreUtility;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.entity.IFEntity;

/**
 * 
 */
public class DigestAgreementProcess implements IFDigestAgreementProcess {

   private static final Logger logger = Logger.getLogger(DigestAgreementProcess.class);
   private static final Logger accountMonitor = Logger.getLogger("monitor.state.account.witness");

   private IFDigestAgreementRequest firstRequest = null;
   /**
    * Indicates wheather or not the first requesting process (SOAP-Party)
    * got the answer for the digest agreement request (except waiting-state)
    */
   private boolean firstRequesterGotAgreementResult = false;
   private IFBodyEvidence firstRequestEvidence = null;
   private IFEntity firstActingEntity = null;

   private IFDigestAgreementRequest secondRequest = null;
   /**
    * Indicates wheather or not the first requesting process (SOAP-Party)
    * got the answer for the digest agreement request (except waiting state)
    */
   private boolean secondRequesterGotAgreementResult = false;
   private IFBodyEvidence secondRequestEvidence = null;
   private IFEntity secondActingEntity = null;


   /**
    * The context info object to which this process belongs
    */
   private IFWitnessContextInfo witnessContextInfo = null;

   public DigestAgreementProcess(IFWitnessContextInfo witnessContextInfo) {
      this.witnessContextInfo = witnessContextInfo;
   }

   public IFProcessOperationResult setFirst(IFDigestAgreementRequest request, IFEntity actingEntity, IFBodyEvidence bodyEvidence) {
      final String fn = "[setFirst]";
      ProcessOperationResult result = new ProcessOperationResult();
      if (request != null && actingEntity != null && bodyEvidence != null) {
         if (checkDependencies(request, result).isSuccess()) {
            if (getFirstRequest() != null) {
               logger.warn(fn + " overwriting firstRequest!");
            }
            setFirstRequest(request);
            if (getFirstActingEntity() != null) {
               logger.warn(fn + " overwriting firstActingEntity!");
            }
            setFirstActingEntity(actingEntity);
            if (getFirstRequestEvidence() != null) {
               logger.warn(fn + " overwriting firstRequestEvidence!");
            }
            setFirstRequestEvidence(bodyEvidence);
         }
      } else {
         logger.error(fn + "PARAM NOT ALLOWED TO BE NULL!");
         result.setResultCode(ProcessOperationResult.PARAMETER_WAS_NULL);
      }
      return result;
   }

   private ProcessOperationResult checkDependencies(IFDigestAgreementRequest request, ProcessOperationResult result) {
      try {
         if (!getContextInfo().getDependencyManager().checkDependency(request.getDependency())) {
            result.setResultCode(ProcessOperationResult.DEPENDENCIES_COULD_NOT_BE_MET);
         }
      } catch (ContextInfoInvalidException e) {
         result.setResultCode(ProcessOperationResult.CONTEXT_INVALID);
      }
      return result;
   }

   public IFProcessOperationResult setSecond(IFDigestAgreementRequest request, IFEntity actingEntity, IFBodyEvidence bodyEvidence) {
      final String fn = "[setSecond]";
      ProcessOperationResult result = new ProcessOperationResult();
      if (request != null && actingEntity != null && bodyEvidence != null) {
         if (checkDependencies(request, result).isSuccess()) {
            if (getSecondRequest() != null) {
               logger.warn(fn + " overwriting secondRequest!");
            }
            setSecondRequest(request);
            if (getSecondActingEntity() != null) {
               logger.warn(fn + " overwriting secondActingEntity!");
            }
            setSecondActingEntity(actingEntity);
            if (getSecondRequestEvidence() != null) {
               logger.warn(fn + " overwriting secondRequestEvidence!");
            }
            setSecondRequestEvidence(bodyEvidence);
         }
      } else {
         logger.error(fn + "PARAM NOT ALLOWED TO BE NULL!");
         result.setResultCode(ProcessOperationResult.PARAMETER_WAS_NULL);
      }
      return result;
   }

   public IFDigestAgreementConfirmationResult getConfirmationResult(IFDigestAgreementRequest request) {
      final String fn = "[getConfirmationResult]";
      DigestAgreementConfirmationResult result = new DigestAgreementConfirmationResult();
      if (getFirstRequest() == request) {
         if (logger.isDebugEnabled()) logger.debug(fn + " found request in firstRequest - object (actingEntity '" + ApplicationSpecificKeystoreUtility.getEntityNameByID(getFirstActingEntity().getId()) + "')");
         getConfirmationResult(result);
         if (result.getResultCode() != IFDigestAgreementConfirmationResult.WAITING_FOR_SECOND_REQUEST) {
            firstRequesterGotAgreementResult = true;
         }
      } else if (getSecondRequest() == request) {
         if (logger.isDebugEnabled()) logger.debug(fn + " found request in secondRequest - object (actingEntity '" + ApplicationSpecificKeystoreUtility.getEntityNameByID(getSecondActingEntity().getId()) + "')");
         getConfirmationResult(result);
         if (result.getResultCode() != IFDigestAgreementConfirmationResult.WAITING_FOR_SECOND_REQUEST) {
            secondRequesterGotAgreementResult = true;
         }
      } else {
         logger.warn(fn + " couldn't find request - object '" + request + "'");
         DigestAgreementConfirmationResult temp = new DigestAgreementConfirmationResult();
         temp.setResultCode(IFDigestAgreementConfirmationResult.UNMANAGED_REQUEST);
         result = temp;
      }
      if (result.isSuccess() && getFirstRequest().isRequestPhase()) {
         checkAccountTransferContextCreation(result);
      } else if (result.isSuccess() && getFirstRequest().isResponsePhase()) {
         checkAccountTransferContextExecution(result);
      }
      return result;
   }

   private void checkAccountTransferContextExecution(DigestAgreementConfirmationResult result) {
      final String fn = "[checkAccountTransferContextExecution] ";
      if (getFirstRequest().getAgreementReference().equals(getSecondRequest().getAgreementReference())) {
         IFAccountTransferContext accTransCtx = getContextInfo().getAccountTransferContext(getFirstRequest().getAgreementReference());
         if (accTransCtx == null) {
            logger.info(fn + "the referenced AccountTransferContext was NULL");
            result.setResultCode(IFDigestAgreementConfirmationResult.NO_ACCOUNT_TRANSFER_CONTEXT_FOUND);
            result.setResultMessage("the referenced AccountTransferContext was NULL");
         } else if (!accTransCtx.isValid()) {
            logger.error(fn + "the referenced AccountTransferContext was INVALID");
            result.setResultCode(IFDigestAgreementConfirmationResult.ACCOUNT_TRANSFER_CONTEXT_INVALID);
            result.setResultMessage("the referenced AccountTransferContext was INVALID");
         } else {
            if (logger.isInfoEnabled()) logger.info(fn + "executing AccountTransferContext: '" + accTransCtx + "'");
            accTransCtx.startTransfer();
            getContextInfo().removeAccountTransferContext(getFirstRequest().getAgreementReference());
            if (accountMonitor.isDebugEnabled()) {
               try {
                  IFEntity targetEntity = null;
                  IFEntity sourceEntity = null;
                  IFDigestAgreementRequest targetRequest = null;
                  if (getContextInfo().getOwningEntity().equals(getFirstActingEntity())) {
                     targetEntity = getSecondActingEntity();
                     sourceEntity = getFirstActingEntity();
                     targetRequest = getSecondRequest();
                  } else if (getContextInfo().getOwningEntity().equals(getSecondActingEntity())) {
                     targetEntity = getFirstActingEntity();
                     sourceEntity = getSecondActingEntity();
                     targetRequest = getFirstRequest();
                  } else {
                     logger.fatal(fn + "both entities in the digestagreement request doesn't equal the owner of the context!");
                     result.setResultCode(IFDigestAgreementConfirmationResult.FATAL_STATE_ERROR);
                     result.setResultMessage("both entities in the digestagreement request doesn't equal the owner of the context!");
                     return;
                  }
                  IFAccount targetAccount = AccountManagerFactory.getAccountManager().locateAccount(targetEntity, targetRequest.getAccountIdentification());
                  logAccountState(sourceEntity, targetAccount, targetEntity, fn, "completing existing AccountTransferContext");
               } catch (ContextInfoInvalidException e) {
                  e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
               }
            }
         }
      } else {
         logger.error(fn + "agreement references in the digest agreement requests differ; first:'" + getFirstRequest().getAgreementReference() + "'; second:'" + getSecondRequest().getAgreementReference() + "'!");
         result.setResultCode(IFDigestAgreementConfirmationResult.FORMER_AGREEMENT_REFERENCES_DIFFER);
         result.setResultMessage("agreement references in the digest agreement requests differ; first:'" + getFirstRequest().getAgreementReference() + "'; second:'" + getSecondRequest().getAgreementReference() + "'!");
      }
   }

   /**
    * Checks wheather an accountTransferContext is beeing created or not. If yes
    * the corresponding agreed digests from requester (client) and responder (server) over the
    * result message commits the account-reservation.
    */
   private void checkAccountTransferContextCreation(DigestAgreementConfirmationResult result) {
      final String fn = "[checkAccountTransferContextCreation] ";
      try {
         if (firstRequesterGotAgreementResult && secondRequesterGotAgreementResult && getFirstRequest().isRequestPhase()) {
            IFEntity targetEntity = null;
            IFEntity sourceEntity = null;
            IFDigestAgreementRequest targetRequest = null;
            if (getContextInfo().getOwningEntity().equals(getFirstActingEntity())) {
               targetEntity = getSecondActingEntity();
               sourceEntity = getFirstActingEntity();
               targetRequest = getSecondRequest();
            } else if (getContextInfo().getOwningEntity().equals(getSecondActingEntity())) {
               targetEntity = getFirstActingEntity();
               sourceEntity = getSecondActingEntity();
               targetRequest = getFirstRequest();
            } else {
               logger.fatal(fn + "both entities in the digestagreement request doesn't equal the owner of the context!");
               result.setResultCode(IFDigestAgreementConfirmationResult.FATAL_STATE_ERROR);
               result.setResultMessage("both entities in the digestagreement request doesn't equal the owner of the context!");
               return;
            }
            IFAccount targetAccount = AccountManagerFactory.getAccountManager().locateAccount(targetEntity, targetRequest.getAccountIdentification());
            if (targetAccount != null) {
               AccountTransferContext ctx = new AccountTransferContext(getContextInfo().getSourceAccount(), targetAccount, targetRequest.getAmount());
               if (ctx.isValid()) {
                  getContextInfo().addAccountTransferContext(result.getReference(), ctx);
                  logAccountState(sourceEntity, targetAccount, targetEntity, fn, "registering newly created AccountTransferContext");
               } else {
                  logger.fatal(fn + "Creation of AccountTransferContext failed!");
                  result.setResultCode(IFDigestAgreementConfirmationResult.CREATION_OF_ACCOUNT_TRANSFER_CONTEXT_FAILED);
                  result.setResultMessage("Creation of AccountTransferContext failed!");
               }
            } else {
               logger.fatal(fn + "couldn't localize the account of entity '" + targetEntity + "' and accountIdent '" + targetRequest.getAccountIdentification() + "'");
               result.setResultCode(IFDigestAgreementConfirmationResult.TARGET_ACCOUNT_UNKNOWN);
               result.setResultMessage("couldn't localize the account of entity '" + targetEntity + "' and accountIdent '" + targetRequest.getAccountIdentification() + "'");
            }
         } else {
            if (logger.isInfoEnabled()) logger.info(fn + "only one participant got its result or we are not in request-phase");
         }
      } catch (ContextInfoInvalidException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
   }

   private void logAccountState(IFEntity sourceEntity, IFAccount targetAccount, IFEntity targetEntity, final String fn, String msg) throws ContextInfoInvalidException {
      if (accountMonitor.isDebugEnabled()) {
         StringBuffer mon = new StringBuffer(msg + "\n");
         mon.append("\tSourceAccount (id: '" + getContextInfo().getSourceAccount().getIdentification() + "'; owningEntity: '" + ApplicationSpecificKeystoreUtility.getEntityNameByID(sourceEntity.getId()) + "'):\n");
         mon.append("\t\tAmount:\t\t").append(getContextInfo().getSourceAccount().getAmount().getAmount()).append("\n");
         mon.append("\t\tCredit:\t\t").append(getContextInfo().getSourceAccount().getCredit().getAmount()).append("\n");
         mon.append("\t\tV-Debit:\t").append(getContextInfo().getSourceAccount().getVirtualDebitSum().getAmount()).append("\n");
         mon.append("\t\tV-Credit:\t").append(getContextInfo().getSourceAccount().getVirtualCreditSum().getAmount()).append("\n");
         mon.append("\tTargetAccount (id: '" + targetAccount.getIdentification() + "'; owningEntity: '" + ApplicationSpecificKeystoreUtility.getEntityNameByID(targetEntity.getId()) + "')\n");
         mon.append("\t\tAmount:\t\t").append(targetAccount.getAmount().getAmount()).append("\n");
         mon.append("\t\tCredit:\t\t").append(targetAccount.getCredit().getAmount()).append("\n");
         mon.append("\t\tV-Debit:\t").append(targetAccount.getVirtualDebitSum().getAmount()).append("\n");
         mon.append("\t\tV-Credit:\t").append(targetAccount.getVirtualCreditSum().getAmount()).append("\n");
         accountMonitor.debug(fn + mon);
      }
   }

   private DigestAgreementConfirmationResult getConfirmationResult(DigestAgreementConfirmationResult result) {
      final String fn = "[getConfirmationResult]";
      if (result == null) {
         result = new DigestAgreementConfirmationResult();
      }
      if (result.isSuccess()) {
         confirmBasics(result);
      }
      if (result.isSuccess()) {
         confirmDependencies(result);
      }
      if (result.isSuccess()) {
         confirmDigest(result);
      }
      if (result.isSuccess()) {
         confirmAmounts(result);
      }
      return result;
   }

   private void confirmBasics(DigestAgreementConfirmationResult result) {
      if (getFirstRequest() == null && getSecondRequest() == null) {
         result.setResultCode(IFDigestAgreementConfirmationResult.PARAMETER_WAS_NULL);
      } else if (firstRequest != null && secondRequest == null) {
         result.setResultCode(IFDigestAgreementConfirmationResult.WAITING_FOR_SECOND_REQUEST);
      } else {
         if (getFirstRequest().getPhase() != getSecondRequest().getPhase()) {
            result.setResultCode(IFDigestAgreementConfirmationResult.REQUESTS_BELONG_TO_DIFFERENT_COMM_STEPS);
         }
      }
   }

   private void confirmAmounts(DigestAgreementConfirmationResult result) {
      if (!AmountTool.isEqual(getFirstRequest().getAmount(), secondRequest.getAmount())) {
         result.setResultCode(IFDigestAgreementConfirmationResult.AMOUNTS_DIFFER);
      }
   }

   private void confirmDependencies(DigestAgreementConfirmationResult result) {
      try {
         if (!getContextInfo().getDependencyManager().isEqual(getFirstRequest().getDependency(), getSecondRequest().getDependency())) {
            result.setResultCode(IFDigestAgreementConfirmationResult.DEPENDENCIES_DIFFER);
         }
      } catch (ContextInfoInvalidException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
   }

   private void confirmDigest(DigestAgreementConfirmationResult result) {
      final String fn = "[confirmDigest] ";
      if (logger.isDebugEnabled()) logger.debug(fn + "checking both digests for equality; first: '" + firstRequest.getDigest() + "' second: '" + secondRequest.getDigest() + "'...");
      if (getFirstRequest().getDigest() != null && getSecondRequest().getDigest() != null) {
         if (getFirstRequest().getDigest().equals(getSecondRequest().getDigest())) {
            result.setResultCode(IFDigestAgreementConfirmationResult.SUCCESS);
            result.setReference(getFirstRequest().getDigest());
            if (logger.isDebugEnabled()) logger.debug(fn + "... MATCH!");
         } else {
            result.setResultCode(IFDigestAgreementConfirmationResult.DIGEST_MATCH_FAILURE);
            result.setResultMessage("digests didn't match!");
            if (logger.isDebugEnabled()) logger.debug(fn + "... DIDN'T MATCH");
            if (logger.isInfoEnabled()) logger.info(fn + "digests didn't match: first: '" + firstRequest.getDigest() + "' second: '" + secondRequest.getDigest() + "'...");
         }
      } else if (getFirstRequest().getDigest() == null ^ getSecondRequest().getDigest() == null) {
         result.setResultCode(IFDigestAgreementConfirmationResult.DIGEST_MATCH_FAILURE);
         result.setResultMessage("digests didn't match (one was NULL)!");
      } else {
         result.setResultCode(IFDigestAgreementConfirmationResult.SUCCESS);
         result.setResultMessage("both digests were NULL");
         result.setReference(getFirstRequest().getDigest());
      }
   }

   private IFWitnessContextInfo getContextInfo() {
      return witnessContextInfo;
   }

   private void setContextInfo(IFWitnessContextInfo witnessContextInfo) {
      this.witnessContextInfo = witnessContextInfo;
   }

   private IFDigestAgreementRequest getFirstRequest() {
      return firstRequest;
   }

   private void setFirstRequest(IFDigestAgreementRequest firstRequest) {
      this.firstRequest = firstRequest;
   }

   private IFDigestAgreementRequest getSecondRequest() {
      return secondRequest;
   }

   private void setSecondRequest(IFDigestAgreementRequest secondRequest) {
      this.secondRequest = secondRequest;
   }

   private IFEntity getFirstActingEntity() {
      return firstActingEntity;
   }

   private void setFirstActingEntity(IFEntity firstActingEntity) {
      this.firstActingEntity = firstActingEntity;
   }

   private IFEntity getSecondActingEntity() {
      return secondActingEntity;
   }

   private void setSecondActingEntity(IFEntity secondActingEntity) {
      this.secondActingEntity = secondActingEntity;
   }

   public IFBodyEvidence getFirstRequestEvidence() {
      return firstRequestEvidence;
   }

   private void setFirstRequestEvidence(IFBodyEvidence firstRequestEvidence) {
      this.firstRequestEvidence = firstRequestEvidence;
   }

   public IFBodyEvidence getSecondRequestEvidence() {
      return secondRequestEvidence;
   }

   private void setSecondRequestEvidence(IFBodyEvidence secondRequestEvidence) {
      this.secondRequestEvidence = secondRequestEvidence;
   }

   public boolean isInvalid() {
      return firstRequesterGotAgreementResult && secondRequesterGotAgreementResult;
   }

   public class ProcessOperationResult extends BaseResult implements IFProcessOperationResult {

      public static final int CONTEXT_INVALID = CUSTOM_START + 1;
      public static final int DEPENDENCIES_COULD_NOT_BE_MET = CUSTOM_START + 2;
      public static final int PARAMETER_WAS_NULL = CUSTOM_START + 3;

      public ProcessOperationResult() {
         setResultCode(SUCCESS);
      }

   }
}
