package gw.rating.flow

uses gw.api.locale.DisplayKey
uses gw.api.rating.DefaultFactorSourceLogger
uses gw.api.rating.MultiFactorVariable
uses gw.api.rating.RateQueryResult
uses gw.api.system.PCLoggerCategory
uses gw.plugin.Plugins
uses gw.plugin.rateflow.IRateQueryLookupPlugin
uses gw.plugin.rateflow.QueryPreemptionResult
uses gw.rating.flow.domain.CoverageQueryParameterSource
uses gw.rating.rtm.matchop.CoverageParam
uses gw.rating.rtm.matchop.StatelessMatchOperator
uses gw.rating.rtm.query.QueryStrategyFactory
uses gw.rating.rtm.query.RatingQuery
uses gw.rating.rtm.valueprovider.RateTableCellValueProvider
uses gw.rating.rtm.valueprovider.RateTableCellValueProviderFactory

/**
 * This allows ratebook queries using something like
 *   new gw.rating.flow.RateQuery<BigDecimal>(rateBook, "PA_Vehicle_Cost")
 *                                           .withParam(1, vehicle.CostNew)
 *                                           .withParam(2, vehicle.GarageLocation.State)
 *                                           .getFactor()
 **/
class RateQuery<R> {

  static var _logger = PCLoggerCategory.RTM
  var _orderedParams : Comparable[] as readonly OrderedParamsList
  var _tableDef : RateTableDefinition as readonly TableDef
  var _rateBook : RateBook as readonly RateBook
  var _query : RatingQuery as readonly RatingQuery
  var _table : RateTable as readonly RateTable
  var _matchOps : java.util.List <StatelessMatchOperator>
  var _rateQueryLookupPlugin : Optional<IRateQueryLookupPlugin>

  property get TableName() : String {
    return _table.RateBook.ID == _rateBook.ID ?
      _tableDef.TableName :
      DisplayKey.get("Web.Policy.RatingWorksheet.Node.Table.RateBookFromHierarchy", _table.RateBook.BookName, _table.RateBook.BookEdition, _tableDef.TableName)
  }

  property get TableCode() : String {
    return _tableDef.TableCode
  }

  construct(rbook : RateBook, tableCode : String) {
    this._rateBook = rbook
    this._table = _rateBook.getTable(tableCode)
    this._tableDef = _table.Definition
    this._query = new QueryStrategyFactory().getFactorQuery(_table)
    this._matchOps = _query.MatchOps
    this._orderedParams = new Comparable[_tableDef.MatchOps.length]
    this._rateQueryLookupPlugin = Plugins.isEnabled(IRateQueryLookupPlugin)
                                    ? Optional.of(Plugins.get(IRateQueryLookupPlugin))
                                    : Optional.empty()
  }

  function withParam<T extends Comparable>(pos : int, value : T) : RateQuery<R> {
    _orderedParams[pos] = _matchOps.get(pos).makeConvertedParam(value)
    return this
  }

  function withCoverageParam<T extends Comparable>(pos: int, cov : Coverage): RateQuery <R> {
    var op = _tableDef.SortedMatchOpArray[pos]
    var colDef = op.sortedParams().get(0)
    var provider = RateTableCellValueProviderFactory.getValueProvider(colDef) as CoverageQueryParameterSource
    _orderedParams[pos] = provider.getQueryParam(cov)
    return this
  }

  function withCoverageParam<T extends Comparable>(pos: int, patternCode : String): RateQuery <R> {
    var op = _tableDef.SortedMatchOpArray[pos]
    var colDef = op.sortedParams().get(0)
    var provider = RateTableCellValueProviderFactory.getValueProvider(colDef) as RateTableCellValueProvider
    var codeIdentifier = provider.convertPublicIDToCodeIdentifier(patternCode)
    _orderedParams[pos] = new CoverageParam(:codeIdentifier = codeIdentifier, :patternCode = patternCode)
    return this
  }

  property get Factor() : R {
    return getFactor(null)
  }

  function getFactor(factorColumnName : String) : R {
    return select(factorColumnName).Factor
  }

  function select(factorColumnName : String) : RateQueryResult<R> {
    if (_logger.DebugEnabled) {
      var queryParamsStr = _orderedParams.map(\op -> op?.toString() ?: "null").join(", ")
      _logger.debug("Query for - book: ${_rateBook.BookName}, version: ${_rateBook.BookEdition}, table: ${TableCode}, ordered query params: ${queryParamsStr}, For factor: ${factorColumnName}")
    }

    if (_rateQueryLookupPlugin.isPresent()) {
      var preemptionResult = _rateQueryLookupPlugin.get().shouldPreemptQueryWithFailover(this)
      if (preemptionResult.ShouldPreempt) {
        return _rateQueryLookupPlugin.get().processSingleFactorQueryFailover(this, factorColumnName, Optional.of(preemptionResult)) as RateQueryResult<R>
      }
    }

    var result = _query.query<R>(_table, OrderedParamsList, factorColumnName)

    if (_rateQueryLookupPlugin.isPresent() and result.Empty) {
      result = _rateQueryLookupPlugin.get().processSingleFactorQueryFailover(this, factorColumnName, Optional<QueryPreemptionResult>.empty()) as RateQueryResult<R>
    }

    if (_logger.DebugEnabled) {
      _logger.debug("Factor found:  ${result.Factor}")
    }
    return result
  }

  function getFactors(factorCodes : List<String>) : MultiFactorVariable {
    return select(factorCodes)
  }

  function select(factorCodes : List<String>) : MultiFactorVariable {
    if (_logger.DebugEnabled) {
      var queryParamsStr = _orderedParams.map(\op -> op?.toString() ?: "null").join(", ")
      _logger.debug("Query for - book: ${_rateBook.BookName}, version: ${_rateBook.BookEdition}, table: ${TableCode}, ordered query params: ${queryParamsStr}, For Factors: " + factorCodes)
    }

    if (_rateQueryLookupPlugin.isPresent()) {
      var preemptionResult = _rateQueryLookupPlugin.get().shouldPreemptQueryWithFailover(this)
      if (preemptionResult.ShouldPreempt) {
        return _rateQueryLookupPlugin.get().processMultiFactorQueryFailover(this, factorCodes, Optional.of(preemptionResult))
      }
    }

    var results = _query.queryMultipleFactors(_table, _orderedParams, factorCodes)

    if (_rateQueryLookupPlugin.isPresent() and results.Empty) {
      return _rateQueryLookupPlugin.get().processMultiFactorQueryFailover(this, factorCodes, Optional<QueryPreemptionResult>.empty())
    }

    if (_logger.DebugEnabled) {
      _logger.debug("Number factors found:     ${results.Keys.Count}")
    }
    return new MultiFactorVariable(results, new DefaultFactorSourceLogger())
  }
}
