package com.oguerreiro.resilient.expression;

import java.math.BigDecimal;

import org.springframework.beans.factory.annotation.Autowired;

import com.oguerreiro.resilient.domain.Organization;
import com.oguerreiro.resilient.domain.Period;
import com.oguerreiro.resilient.domain.PeriodVersion;
import com.oguerreiro.resilient.repository.InputDataRepository;
import com.oguerreiro.resilient.repository.OrganizationRepository;
import com.oguerreiro.resilient.repository.OutputDataRepository;
import com.oguerreiro.resilient.service.OutputDataService;

/**
 * This s a Expressionfunction's provider. To simplify development, an instance must be created an then call
 * autowireBean() to inject other beans
 */
public class OutputVariableExpressionFunctions extends EmissionFactorExpressionFunctions {

  @Autowired
  private InputDataRepository inputDataRepository;
  @Autowired
  private OutputDataRepository outputDataRepository;
  @Autowired
  private OutputDataService outputDataService;
  @Autowired
  private OrganizationRepository organizationRepository;

  private Period currentPeriod;
  private PeriodVersion currentPeriodVersion;
  private Organization currentOrganization;

  public OutputVariableExpressionFunctions(Period currentPeriod, PeriodVersion currentPeriodVersion,
      Organization currentOrganization) {
    super(currentPeriod);
    this.currentOrganization = currentOrganization;
    this.currentPeriod = currentPeriod;
    this.currentPeriodVersion = currentPeriodVersion;
  }

  /* Public accessible functions in expressions */
  public BigDecimal out(String variableCode) {
    //Wildcard prepare
    variableCode = variableCode.replace('*', '%');

    BigDecimal value = this.outputDataService.sumAllOutputsFor(this.currentOrganization, this.currentPeriodVersion,
        variableCode);
    BigDecimal result = safeBigDecimal(value);

    // addTrace (function, warn, result, args...) 
    addTrace("out", "", result, variableCode, this.currentOrganization.getCode());

    return result;
  }

  public BigDecimal out(String variableCode, String ownerCode) {
    BigDecimal value = BigDecimal.ZERO;

    //Wildcard prepare
    variableCode = variableCode.replace('*', '%');

    //Search Organization for the given ownerCode
    Organization organization = organizationRepository.findOneByCode(ownerCode).orElse(null);
    String warn = "";
    if (organization != null) {
      value = this.outputDataService.sumAllOutputsFor(organization, this.currentPeriodVersion, variableCode);
    } else {
      warn = "Organization not found for code [" + ownerCode + "]";
    }
    BigDecimal result = safeBigDecimal(value);

    // addTrace (function, warn, result, args...) 
    addTrace("out", warn, result, variableCode, ownerCode);

    return result;
  }

  public BigDecimal outByUnit(String unitCode) {
    BigDecimal value = this.outputDataRepository.sumAllOutputsForUnit(this.currentOrganization,
        this.currentPeriodVersion, unitCode);
    BigDecimal result = safeBigDecimal(value);

    // addTrace (function, warn, result, args...) 
    addTrace("outByUnit", "", result, unitCode, this.currentOrganization.getCode());

    return result;
  }

  public BigDecimal outByUnit(String unitCode, String ownerCode) {
    BigDecimal value = BigDecimal.ZERO;

    //Search Organization for the given ownerCode
    Organization organization = organizationRepository.findOneByCode(ownerCode).orElse(null);
    String warn = "";

    if (organization != null) {
      value = this.outputDataRepository.sumAllOutputsForUnit(organization, this.currentPeriodVersion, unitCode);
    } else {
      warn = "Organization not found for code [" + ownerCode + "]";
    }

    BigDecimal result = safeBigDecimal(value);

    // addTrace (function, warn, result, args...) 
    addTrace("outByUnit", warn, result, unitCode, ownerCode);

    return result;
  }

  public BigDecimal in(String variableCode) {
    return inFunction(variableCode, "*ALL", this.currentOrganization.getCode(), this.currentOrganization);
  }

  public BigDecimal in(String variableCode, String classCode) {
    return inFunction(variableCode, classCode, this.currentOrganization.getCode(), this.currentOrganization);
  }

  public BigDecimal in(String variableCode, String classCode, String ownerCode) {
    //Search Organization for the given ownerCode
    Organization organization = organizationRepository.findOneByCode(ownerCode).orElse(null);

    return inFunction(variableCode, classCode, ownerCode, organization);
  }

  private BigDecimal inFunction(String variableCode, String classCode, String ownerCode, Organization organization) {
    //Wildcard prepare
    variableCode = variableCode.replace('*', '%');

    //ClassCode pre-processing. If *ALL, convert to null
    if (classCode != null && classCode.toUpperCase().equals("*ALL")) {
      classCode = null;
    }

    BigDecimal result = BigDecimal.ZERO;
    String warn = "";
    if (organization != null) {
      if (classCode != null) {
        result = this.inputDataRepository.sumAllInputsFor(organization, this.currentPeriod, variableCode, classCode);
      } else {
        result = this.inputDataRepository.sumAllInputsFor(organization, this.currentPeriod, variableCode);
      }
    } else {
      warn = "Organization not found for code [" + ownerCode + "]";
    }

    // Make sure NEVER to return null
    result = safeBigDecimal(result);

    // addTrace (function, warn, result, args...) 
    addTrace("in", warn, result, variableCode, classCode == null ? "*ALL" : classCode, ownerCode);

    return result;
  }

  /* Private methods */

}
