#include "hbclass.ch"
#include "Xailer.ch"

#define LLDOMINUS_OUTPUT_DESIGN  1
#define LLDOMINUS_OUTPUT_PREVIEW 2
#define LLDOMINUS_OUTPUT_PRINT   3

#define LLDOMINUS_DETAIL_OPTIMIZATION 0
#define LLDOMINUS_DETAIL_MODE         "FULL"
#define LLDOMINUS_DETAIL_DIAGNOSTICS  0
#define LLDOMINUS_DETAIL_LOG_NAME     "LLDominusDetail.log"

CREATE CLASS TLLDominusPrintJob

   DATA cTemplateFile       INIT ""
   DATA cTemplate           INIT ""
   DATA cTitle              INIT "Sin Titulo"
   DATA nOutputType         INIT LLDOMINUS_OUTPUT_PREVIEW
   DATA cDesignerOutputFile INIT ""
   DATA cApiHost            INIT "servidorcvs.duckdns.org"
   DATA cApiPath            INIT "/LL-Dominus/public/apiLLDominus.php"
   DATA lApiSecure          INIT .T.
   DATA cApiKey             INIT ""
   DATA oOwner              INIT NIL
   DATA aDataVar            INIT {}
   DATA oDetail             INIT NIL
   DATA lExistoRuptura      INIT .F.
   DATA oQueryRuptura       INIT NIL
   DATA cClaveRuptura       INIT ""
   DATA oEngine             INIT NIL
   DATA oRenderer           INIT NIL
   DATA oDocument           INIT NIL
   DATA hJob                INIT {=>}
   DATA hIR                 INIT {=>}
   DATA hAskStringValues    INIT {=>}
   DATA cLastError          INIT ""
   DATA lCanceled           INIT .F.
   DATA lCreated            INIT .F.
   DATA lCapturandoRuptura  INIT .F.
   DATA nIndiceRuptura      INIT 0
   DATA lDetailOptimization INIT LLDOMINUS_DETAIL_OPTIMIZATION == 1
   DATA cDetailMode         INIT LLDOMINUS_DETAIL_MODE
   DATA cDetailTemplateCache INIT ""
   DATA hDetailPlanCache    INIT {=>}
   DATA hDetailStats        INIT {=>}
   DATA nRunStartedMs       INIT 0.0

   METHOD New() CONSTRUCTOR
   METHOD Create()
   METHOD End()

   METHOD AddDataVar( oMem )
   METHOD SetDetail( oMem )
   METHOD SetRuptura( oQueryRuptura, cClaveRuptura )
   METHOD SetDetailOptimization( lEnabled, cMode )
   METHOD SendRegistro( oMem )
   METHOD SetOwner( oOwner )
   METHOD GetAskStrings()
   METHOD HasAskStrings()
   METHOD SetAskStringValue( cKey, uValue )
   METHOD SetAskStringValues( hValues )
   METHOD Run()
   METHOD GetDocument() INLINE ::oDocument
   METHOD GetIR() INLINE ::hIR
   METHOD WasCanceled() INLINE ::lCanceled

   HIDDEN:
   METHOD LoadTemplate()
   METHOD ConvertLL12()
   METHOD SendData()
   METHOD MemToHash( oMem, nRecno )
   METHOD MemToHashSelected( oMem, hPlan )
   METHOD MemStructureToHash( oMem )
   METHOD MemStructureSelectedToHash( hPlan )
   METHOD BuildDetailPlan( oMem )
   METHOD ValidateDetailPlan( oMem, hPlan )
   METHOD BuildDetailProjection( oMem, hPlan )
   METHOD ResolveDetailMode( hPlan )
   METHOD ResetDetailStats()
   METHOD LogDetailStats()
   METHOD LogIRDiagnostic()
   METHOD NormalizeMemHash( hRecord )
   METHOD NormalizeMemFieldName( cFieldName )
   METHOD ResolveAskStrings()
   METHOD ResolveLabelStart()
   METHOD ExecuteOutput()
   METHOD ReleaseRuntimeOutput()
   METHOD OpenDesigner()
   METHOD BuildDesignerVariables()
   METHOD ResolveError()
   METHOD SetError( cError, lShow )

ENDCLASS

//------------------------------------------------------------------------------

METHOD New() CLASS TLLDominusPrintJob

   ::cTemplateFile       := ""
   ::cTemplate           := ""
   ::cTitle              := "Sin Titulo"
   ::nOutputType         := LLDOMINUS_OUTPUT_PREVIEW
   ::cDesignerOutputFile := ""
   ::cApiHost            := "servidorcvs.duckdns.org"
   ::cApiPath            := "/LL-Dominus/public/apiLLDominus.php"
   ::lApiSecure          := .T.
   ::cApiKey             := ""
   ::oOwner              := NIL
   ::aDataVar            := {}
   ::oDetail             := NIL
   ::lExistoRuptura      := .F.
   ::oQueryRuptura       := NIL
   ::cClaveRuptura       := ""
   ::oEngine             := NIL
   ::oRenderer           := NIL
   ::oDocument           := NIL
   ::hJob                := {=>}
   ::hIR                 := {=>}
   ::hAskStringValues    := {=>}
   hb_HAutoAdd( ::hAskStringValues, .T. )
   ::cLastError          := ""
   ::lCanceled           := .F.
   ::lCreated            := .F.
   ::lCapturandoRuptura  := .F.
   ::nIndiceRuptura      := 0
   ::lDetailOptimization := LLDOMINUS_DETAIL_OPTIMIZATION == 1
   ::cDetailMode         := LLDOMINUS_DETAIL_MODE
   ::cDetailTemplateCache := ""
   ::hDetailPlanCache    := {=>}
   ::hDetailStats        := {=>}
   ::nRunStartedMs       := 0.0

RETURN Self

//------------------------------------------------------------------------------

METHOD Create() CLASS TLLDominusPrintJob

   IF ! HB_ISOBJECT( ::oOwner ) .AND. HB_ISOBJECT( Application:oMainForm )
      ::SetOwner( Application:oMainForm )
   ENDIF

   ::lCreated := .T.

RETURN Self

//------------------------------------------------------------------------------

METHOD End() CLASS TLLDominusPrintJob

   ::ReleaseRuntimeOutput()

   ::cTemplate          := ""
   ::aDataVar           := {}
   ::oDetail            := NIL
   ::oQueryRuptura      := NIL
   ::cClaveRuptura      := ""
   ::lExistoRuptura     := .F.
   ::lCapturandoRuptura := .F.
   ::nIndiceRuptura     := 0
   ::cDetailTemplateCache := ""
   ::hDetailPlanCache   := {=>}
   ::hDetailStats       := {=>}
   ::hAskStringValues   := {=>}
   ::lCreated           := .F.

RETURN NIL

//------------------------------------------------------------------------------

METHOD AddDataVar( oMem ) CLASS TLLDominusPrintJob

   IF HB_ISOBJECT( oMem )
      AAdd( ::aDataVar, oMem )
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD SetDetail( oMem ) CLASS TLLDominusPrintJob

   IF HB_ISOBJECT( oMem )
      ::oDetail := oMem
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD SetRuptura( oQueryRuptura, cClaveRuptura ) CLASS TLLDominusPrintJob

   ::oQueryRuptura  := oQueryRuptura
   ::cClaveRuptura  := iif( HB_ISSTRING( cClaveRuptura ), AllTrim( cClaveRuptura ), "" )
   ::lExistoRuptura := HB_ISOBJECT( oQueryRuptura ) .AND. ! Empty( ::cClaveRuptura )

RETURN Self

//------------------------------------------------------------------------------

METHOD SetDetailOptimization( lEnabled, cMode ) CLASS TLLDominusPrintJob

   IF HB_ISLOGICAL( lEnabled )
      ::lDetailOptimization := lEnabled
   ENDIF
   IF HB_ISSTRING( cMode ) .AND. ;
      AScan( { "AUTO", "A", "B", "FULL" }, Upper( AllTrim( cMode ) ) ) > 0
      ::cDetailMode := Upper( AllTrim( cMode ) )
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD SendRegistro( oMem ) CLASS TLLDominusPrintJob

   LOCAL hRecord

   IF ! HB_ISOBJECT( oMem ) .OR. ! HB_ISOBJECT( ::oEngine )
      RETURN Self
   ENDIF

   hRecord := ::MemToHash( oMem, oMem:RecNo() )
   IF ! HB_ISHASH( hRecord )
      ::SetError( "No se han podido obtener los datos globales del grupo " + oMem:cName + ".", .T. )
      RETURN Self
   ENDIF

   IF ::lCapturandoRuptura
      ::oEngine:SendDataVarAt( ::nIndiceRuptura, oMem:cName, hRecord )
   ELSE
      ::oEngine:SendDataVar( oMem:cName, hRecord )
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD SetOwner( oOwner ) CLASS TLLDominusPrintJob

   IF HB_ISOBJECT( oOwner )
      ::oOwner := oOwner
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD GetAskStrings() CLASS TLLDominusPrintJob

   LOCAL oProbe
   LOCAL hAskStrings := { => }

   IF HB_ISOBJECT( ::oEngine )
      RETURN ::oEngine:GetAskStrings()
   ENDIF
   IF Empty( ::cTemplate ) .AND. ! ::LoadTemplate()
      RETURN hAskStrings
   ENDIF
   oProbe := TLLEngine():New():Create()
   oProbe:SetEncodingWIN()
   oProbe:SetPlantillaContent( ::cTemplate )
   hAskStrings := oProbe:GetAskStrings()
   oProbe:End()

RETURN hAskStrings

//------------------------------------------------------------------------------

METHOD HasAskStrings() CLASS TLLDominusPrintJob

RETURN Len( ::GetAskStrings() ) > 0

//------------------------------------------------------------------------------

METHOD SetAskStringValue( cKey, uValue ) CLASS TLLDominusPrintJob

   LOCAL uRuntimeValue := uValue

   cKey := AllTrim( iif( HB_ISSTRING( cKey ), cKey, "" ) )
   IF ! Empty( cKey )
      IF HB_ISSTRING( uRuntimeValue ) .AND. ! hb_StrIsUTF8( uRuntimeValue )
         uRuntimeValue := hb_StrToUTF8( uRuntimeValue, "ESWIN" )
      ENDIF
      ::hAskStringValues[ cKey ] := uRuntimeValue
      IF HB_ISOBJECT( ::oEngine )
         ::oEngine:SetAskStringValue( cKey, uRuntimeValue )
      ENDIF
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD SetAskStringValues( hValues ) CLASS TLLDominusPrintJob

   LOCAL cKey

   IF HB_ISHASH( hValues )
      FOR EACH cKey IN hb_HKeys( hValues )
         ::SetAskStringValue( cKey, hValues[ cKey ] )
      NEXT
   ENDIF

RETURN Self

//------------------------------------------------------------------------------

METHOD Run() CLASS TLLDominusPrintJob

   ::ReleaseRuntimeOutput()
   ::nRunStartedMs := 0.0
   IF LLDetailDiagnosticsEnabled()
      ::nRunStartedMs := LLDetailMilliseconds()
   ENDIF
   ::cLastError := ""
   ::lCanceled  := .F.
   IF ! ::lCreated
      RETURN ::SetError( "El trabajo de impresion no se ha creado.", .T. )
   ENDIF

   IF ! ::LoadTemplate()
      RETURN .F.
   ENDIF

   ::oEngine := TLLEngine():New():Create()
   ::oEngine:SetEncodingWIN()

   IF ::oEngine:IsPlantillaLL12( ::cTemplate )
      IF ! MsgYesNo( hb_UTF8ToStr( "Formato antiguo de L&L 12. ¿Desea convertirlo?", "ESWIN" ), "Decida que hacer" )
         ::lCanceled := .T.
         ::ReleaseRuntimeOutput()
         RETURN .F.
      ENDIF

      IF ! ::ConvertLL12()
         ::ReleaseRuntimeOutput()
         RETURN .F.
      ENDIF
   ENDIF

   ::oEngine:SetPlantillaContent( ::cTemplate )
   ::oEngine:OpenJob()
   IF __objHasMsg( ::oEngine, "SETINPUTDIAGNOSTICS" )
      ::oEngine:SetInputDiagnostics( LLDetailDiagnosticsEnabled() )
   ENDIF

   IF ::nOutputType != LLDOMINUS_OUTPUT_DESIGN .AND. ! ::ResolveAskStrings()
      ::oEngine:CloseJob()
      ::ReleaseRuntimeOutput()
      RETURN .F.
   ENDIF

   IF ::nOutputType != LLDOMINUS_OUTPUT_DESIGN .AND. ! ::ResolveLabelStart()
      ::oEngine:CloseJob()
      ::ReleaseRuntimeOutput()
      RETURN .F.
   ENDIF

   IF ! ::SendData()
      ::oEngine:CloseJob()
      ::ReleaseRuntimeOutput()
      RETURN .F.
   ENDIF

   ::hJob := ::oEngine:CloseJob()

RETURN ::ExecuteOutput()

//------------------------------------------------------------------------------

METHOD LoadTemplate() CLASS TLLDominusPrintJob

   IF Empty( ::cTemplateFile ) .OR. ! File( ::cTemplateFile )
      RETURN ::SetError( "No se encuentra la plantilla: " + ::cTemplateFile, .T. )
   ENDIF

   ::cTemplate := HB_MemoRead( ::cTemplateFile )
   IF Empty( ::cTemplate )
      RETURN ::SetError( "La plantilla esta vacia o no se ha podido leer: " + ::cTemplateFile, .T. )
   ENDIF

RETURN .T.

//------------------------------------------------------------------------------

METHOD ConvertLL12() CLASS TLLDominusPrintJob

   LOCAL oApi
   LOCAL hConversion
   LOCAL cConverted := ""
   LOCAL cError     := ""

   WITH OBJECT oApi := TApiDisenadorLLDominus():New():Create()
      :SetEncodingWIN()
      hConversion := :ConvertirLL12( ::cTemplate, ::cTemplateFile )
      IF HB_ISHASH( hConversion ) .AND. hb_HHasKey( hConversion, "lOk" ) .AND. hConversion[ "lOk" ]
         IF hb_HHasKey( hConversion, "cPlantillaFisica" )
            cConverted := hConversion[ "cPlantillaFisica" ]
         ELSEIF hb_HHasKey( hConversion, "cPlantilla" )
            cConverted := hConversion[ "cPlantilla" ]
         ENDIF
      ELSEIF HB_ISHASH( hConversion ) .AND. hb_HHasKey( hConversion, "cError" )
         cError := hConversion[ "cError" ]
      ELSE
         cError := :cLastError
      ENDIF
      :End()
   END

   IF Empty( cConverted )
      IF Empty( cError )
         cError := "No se ha podido convertir la plantilla LL12."
      ENDIF
      RETURN ::SetError( cError, .T. )
   ENDIF

   // La conversion es una preparacion en memoria del trabajo. No se debe
   // sobrescribir aqui el fichero LL12 de origen: impediria detectarlo e
   // importarlo de nuevo en ejecuciones posteriores. El disenador guarda el
   // resultado solamente en cDesignerOutputFile (o en el origen si el
   // programador lo ha dejado vacio expresamente).
   ::cTemplate := cConverted

RETURN .T.

//------------------------------------------------------------------------------

METHOD SendData() CLASS TLLDominusPrintJob

   LOCAL oMem
   LOCAL oRuptura
   LOCAL hRecord
   LOCAL hPlan
   LOCAL cMode
   LOCAL aProjection := {}
   LOCAL nProjectionIndex := 0
   LOCAL nOldRecno := 0
   LOCAL nStarted
   LOCAL nDetailStarted
   LOCAL lDiagnostics := LLDetailDiagnosticsEnabled()

   FOR EACH oMem IN ::aDataVar
      IF ! HB_ISOBJECT( oMem )
         LOOP
      ENDIF

      hRecord := ::MemToHash( oMem, 1 )
      IF ! HB_ISHASH( hRecord )
         RETURN ::SetError( "No se han podido obtener los datos globales del grupo " + oMem:cName + ".", .T. )
      ENDIF

      ::oEngine:SendDataVar( oMem:cName, hRecord )
   NEXT

   IF ! HB_ISOBJECT( ::oDetail )
      RETURN .T.
   ENDIF

   ::ResetDetailStats()
   nDetailStarted := 0.0
   IF lDiagnostics
      nDetailStarted := LLDetailMilliseconds()
   ENDIF
   nOldRecno := ::oDetail:RecNo()
   ::oDetail:GoTop()

   IF lDiagnostics
      nStarted := LLDetailMilliseconds()
   ENDIF
   hPlan := ::BuildDetailPlan( ::oDetail )
   IF lDiagnostics
      ::hDetailStats[ "nPrepareMs" ] := LLDetailMilliseconds() - nStarted
   ENDIF
   cMode := ::ResolveDetailMode( hPlan )
   ::hDetailStats[ "cMode" ] := cMode
   ::hDetailStats[ "cFallback" ] := iif( hb_HHasKey( hPlan, "cReason" ), hPlan[ "cReason" ], "" )
   ::hDetailStats[ "nOriginalFields" ] := Len( ::oDetail:aFields )
   ::hDetailStats[ "nSelectedFields" ] := iif( hb_HHasKey( hPlan, "aFields" ), Len( hPlan[ "aFields" ] ), 0 )

   IF cMode == "B"
      IF lDiagnostics
         nStarted := LLDetailMilliseconds()
      ENDIF
      aProjection := ::BuildDetailProjection( ::oDetail, hPlan )
      IF lDiagnostics
         ::hDetailStats[ "nProjectionMs" ] := LLDetailMilliseconds() - nStarted
      ENDIF
      IF ! HB_ISARRAY( aProjection )
         cMode := "FULL"
         ::hDetailStats[ "cMode" ] := cMode
         ::hDetailStats[ "cFallback" ] := "projection-error"
         aProjection := {}
      ENDIF
      ::oDetail:GoTop()
   ENDIF

   IF cMode == "FULL"
      hRecord := ::MemStructureToHash( ::oDetail )
   ELSE
      hRecord := ::MemStructureSelectedToHash( hPlan )
   ENDIF
   IF HB_ISHASH( hRecord ) .AND. Len( hRecord ) > 0
      ::oEngine:SendDetalleSchema( ::oDetail:cName, hRecord )
   ENDIF

   IF ::lExistoRuptura
      ::lCapturandoRuptura := .T.
      ::nIndiceRuptura     := 0
      oRuptura             := TRuptura():New( ::oQueryRuptura, Self, ::cClaveRuptura )
   ENDIF

   DO WHILE ! ::oDetail:Eof()
      IF lDiagnostics
         nStarted := LLDetailMilliseconds()
      ENDIF
      DO CASE
      CASE cMode == "A"
         hRecord := ::MemToHashSelected( ::oDetail, hPlan )
      CASE cMode == "B"
         nProjectionIndex++
         hRecord := NIL
         IF nProjectionIndex <= Len( aProjection )
            hRecord := aProjection[ nProjectionIndex ]
         ENDIF
      OTHERWISE
         hRecord := ::MemToHash( ::oDetail, ::oDetail:RecNo() )
      ENDCASE
      IF lDiagnostics
         ::hDetailStats[ "nHashMs" ] += LLDetailMilliseconds() - nStarted
      ENDIF

      IF ! HB_ISHASH( hRecord )
         IF nOldRecno > 0
            ::oDetail:GoTo( nOldRecno )
         ENDIF
         ::lCapturandoRuptura := .F.
         IF lDiagnostics
            ::hDetailStats[ "nTotalDetailMs" ] := LLDetailMilliseconds() - nDetailStarted
            ::LogDetailStats()
         ENDIF
         RETURN ::SetError( "No se han podido obtener los datos de detalle del grupo " + ::oDetail:cName + ".", .T. )
      ENDIF

      IF lDiagnostics
         nStarted := LLDetailMilliseconds()
      ENDIF
      ::oEngine:SendDetalle( ::oDetail:cName, hRecord )
      IF lDiagnostics
         ::hDetailStats[ "nSendMs" ] += LLDetailMilliseconds() - nStarted
         ::hDetailStats[ "nRecords" ]++
         ::hDetailStats[ "nValuesProcessed" ] += Len( hRecord )
         ::hDetailStats[ "nPayloadBytes" ] += Len( hb_jsonEncode( hRecord ) )
      ENDIF

      IF ::nOutputType == LLDOMINUS_OUTPUT_DESIGN
         EXIT
      ENDIF

      ::oDetail:Skip()

      IF ::lExistoRuptura
         ::nIndiceRuptura++
         oRuptura:CheckRuptura()
      ENDIF
   ENDDO

   ::lCapturandoRuptura := .F.

   IF nOldRecno > 0
      ::oDetail:GoTo( nOldRecno )
   ENDIF
   IF lDiagnostics
      ::hDetailStats[ "nTotalDetailMs" ] := LLDetailMilliseconds() - nDetailStarted
      ::LogDetailStats()
   ENDIF

RETURN .T.

//------------------------------------------------------------------------------

METHOD MemToHash( oMem, nRecno ) CLASS TLLDominusPrintJob

   DEFAULT nRecno TO 1

   IF ! HB_ISOBJECT( oMem )
      RETURN NIL
   ENDIF

RETURN ::NormalizeMemHash( oMem:GetRowToHash( nRecno, .T. ) )

//------------------------------------------------------------------------------

METHOD MemToHashSelected( oMem, hPlan ) CLASS TLLDominusPrintJob

   LOCAL hRecord := {=>}
   LOCAL hField
   LOCAL aRow

   IF ! HB_ISOBJECT( oMem ) .OR. ! HB_ISHASH( hPlan ) .OR. ;
      ! hb_HHasKey( hPlan, "aFields" ) .OR. ! HB_ISARRAY( hPlan[ "aFields" ] )
      RETURN NIL
   ENDIF
   aRow := oMem:GetRow( oMem:RecNo(), .F. )
   IF ! HB_ISARRAY( aRow )
      RETURN NIL
   ENDIF

   FOR EACH hField IN hPlan[ "aFields" ]
      IF hField[ "nIndex" ] > Len( aRow )
         RETURN NIL
      ENDIF
      hRecord[ hField[ "cName" ] ] := aRow[ hField[ "nIndex" ] ]
   NEXT

RETURN hRecord

//------------------------------------------------------------------------------

METHOD MemStructureToHash( oMem ) CLASS TLLDominusPrintJob

   LOCAL hStructure := {=>}
   LOCAL oField
   LOCAL cType
   LOCAL uValue

   IF ! HB_ISOBJECT( oMem ) .OR. ! HB_ISARRAY( oMem:aFields )
      RETURN hStructure
   ENDIF

   FOR EACH oField IN oMem:aFields
      IF ! HB_ISOBJECT( oField ) .OR. Empty( oField:cName )
         LOOP
      ENDIF

      cType := Upper( AllTrim( oField:cType ) )
      DO CASE
      CASE cType == "N" .OR. cType == "I" .OR. cType == "INTEGER" .OR. cType == "SHORT" .OR. ;
         cType == "SHORTINT" .OR. cType == "DOUBLE" .OR. cType == "CURDOUBLE" .OR. ;
         cType == "AUTOINCREMENT" .OR. cType == "AUTOINC"
         uValue := 0
      CASE cType == "L"
         uValue := .F.
      CASE cType == "D"
         uValue := CToD( "" )
      OTHERWISE
         uValue := ""
      ENDCASE
      hStructure[ ::NormalizeMemFieldName( oField:cName ) ] := uValue
   NEXT

RETURN hStructure

//------------------------------------------------------------------------------

METHOD MemStructureSelectedToHash( hPlan ) CLASS TLLDominusPrintJob

   LOCAL hStructure := {=>}
   LOCAL hField

   IF ! HB_ISHASH( hPlan ) .OR. ! hb_HHasKey( hPlan, "aFields" ) .OR. ;
      ! HB_ISARRAY( hPlan[ "aFields" ] )
      RETURN hStructure
   ENDIF

   FOR EACH hField IN hPlan[ "aFields" ]
      hStructure[ hField[ "cName" ] ] := hField[ "uDefault" ]
   NEXT

RETURN hStructure

//------------------------------------------------------------------------------

METHOD BuildDetailPlan( oMem ) CLASS TLLDominusPrintJob

   LOCAL hPlan := { ;
      "lSafe" => .F., ;
      "cReason" => "optimization-disabled", ;
      "cGroup" => "", ;
      "nOriginalFields" => 0, ;
      "aStructureNames" => {}, ;
      "aFields" => {} ;
   }
   LOCAL cGroup
   LOCAL cCacheKey
   LOCAL cFieldName
   LOCAL cType
   LOCAL cUsed
   LOCAL cUsedNormalized
   LOCAL cPrefix
   LOCAL cRequiredField
   LOCAL aUsed
   LOCAL aSelected := {}
   LOCAL aStructureNames := {}
   LOCAL oField
   LOCAL hField
   LOCAL uDefault
   LOCAL nIndex
   LOCAL cSelectedNames := ""

   IF ! ::lDetailOptimization
      RETURN hPlan
   ENDIF
   IF ! HB_ISOBJECT( ::oEngine ) .OR. ! HB_ISOBJECT( oMem ) .OR. ;
      ! HB_ISARRAY( oMem:aFields ) .OR. ! __objHasMsg( ::oEngine, "GETVARIABLESUSADAS" ) .OR. ;
      ! __objHasMsg( ::oEngine, "ISUSEDVAR" ) .OR. ! __objHasMsg( oMem, "GETROW" )
      hPlan[ "cReason" ] := "unsupported-source"
      RETURN hPlan
   ENDIF

   IF ::cDetailTemplateCache != ::cTemplate
      ::cDetailTemplateCache := ::cTemplate
      ::hDetailPlanCache := {=>}
   ENDIF

   cGroup := Upper( AllTrim( oMem:cName ) )
   cCacheKey := cGroup
   IF hb_HHasKey( ::hDetailPlanCache, cCacheKey )
      hPlan := ::hDetailPlanCache[ cCacheKey ]
      IF ::ValidateDetailPlan( oMem, hPlan )
         RETURN hPlan
      ENDIF
      hPlan[ "lSafe" ] := .F.
      hPlan[ "cReason" ] := "structure-changed"
      RETURN hPlan
   ENDIF

   aUsed := ::oEngine:GetVariablesUsadas()
   IF ! HB_ISARRAY( aUsed )
      hPlan[ "cReason" ] := "template-analysis-error"
      RETURN hPlan
   ENDIF

   FOR nIndex := 1 TO Len( oMem:aFields )
      oField := oMem:aFields[ nIndex ]
      IF ! HB_ISOBJECT( oField ) .OR. Empty( oField:cName )
         hPlan[ "cReason" ] := "invalid-field-structure"
         RETURN hPlan
      ENDIF

      cFieldName := ::NormalizeMemFieldName( oField:cName )
      AAdd( aStructureNames, cFieldName )
      IF ! ::oEngine:IsUsedVar( cGroup + "." + cFieldName )
         LOOP
      ENDIF

      cType := Upper( AllTrim( oField:cType ) )
      DO CASE
      CASE cType == "N" .OR. cType == "I" .OR. cType == "INTEGER" .OR. cType == "SHORT" .OR. ;
         cType == "SHORTINT" .OR. cType == "DOUBLE" .OR. cType == "CURDOUBLE" .OR. ;
         cType == "AUTOINCREMENT" .OR. cType == "AUTOINC"
         uDefault := 0
      CASE cType == "L"
         uDefault := .F.
      CASE cType == "D"
         uDefault := CToD( "" )
      OTHERWISE
         uDefault := ""
      ENDCASE

      AAdd( aSelected, { ;
         "cName" => cFieldName, ;
         "cSourceName" => oField:cName, ;
         "nIndex" => nIndex, ;
         "uDefault" => uDefault ;
      } )
   NEXT

   cPrefix := cGroup + "."
   FOR EACH cUsed IN aUsed
      cUsedNormalized := Upper( StrTran( AllTrim( cUsed ), " ", "" ) )
      IF Left( cUsedNormalized, Len( cPrefix ) ) != cPrefix
         LOOP
      ENDIF
      cRequiredField := SubStr( cUsedNormalized, Len( cPrefix ) + 1 )
      IF AScan( aSelected, {|hSelected| Upper( hSelected[ "cName" ] ) == cRequiredField } ) == 0
         hPlan[ "cReason" ] := "required-field-not-found:" + cUsed
         RETURN hPlan
      ENDIF
   NEXT

   hPlan := { ;
      "lSafe" => .T., ;
      "cReason" => "", ;
      "cGroup" => cGroup, ;
      "nOriginalFields" => Len( oMem:aFields ), ;
      "aStructureNames" => aStructureNames, ;
      "aFields" => aSelected ;
   }
   ::hDetailPlanCache[ cCacheKey ] := hPlan
   IF LLDetailDiagnosticsEnabled()
      FOR EACH hField IN aSelected
         cSelectedNames += iif( Empty( cSelectedNames ), "", "," ) + hField[ "cName" ]
      NEXT
      LLDetailLog( "plan group=" + cGroup + ;
         " original_fields=" + hb_NToS( Len( oMem:aFields ) ) + ;
         " selected_fields=" + hb_NToS( Len( aSelected ) ) + ;
         " fields=" + cSelectedNames )
   ENDIF

RETURN hPlan

//------------------------------------------------------------------------------

METHOD ValidateDetailPlan( oMem, hPlan ) CLASS TLLDominusPrintJob

   LOCAL nIndex

   IF ! HB_ISOBJECT( oMem ) .OR. ! HB_ISHASH( hPlan ) .OR. ;
      ! hb_HHasKey( hPlan, "lSafe" ) .OR. ! hPlan[ "lSafe" ] .OR. ;
      ! hb_HHasKey( hPlan, "nOriginalFields" ) .OR. ;
      ! hb_HHasKey( hPlan, "aStructureNames" ) .OR. ;
      Len( oMem:aFields ) != hPlan[ "nOriginalFields" ] .OR. ;
      Len( hPlan[ "aStructureNames" ] ) != Len( oMem:aFields )
      RETURN .F.
   ENDIF

   FOR nIndex := 1 TO Len( oMem:aFields )
      IF ! HB_ISOBJECT( oMem:aFields[ nIndex ] ) .OR. ;
         ::NormalizeMemFieldName( oMem:aFields[ nIndex ]:cName ) != hPlan[ "aStructureNames" ][ nIndex ]
         RETURN .F.
      ENDIF
   NEXT

RETURN .T.

//------------------------------------------------------------------------------

METHOD BuildDetailProjection( oMem, hPlan ) CLASS TLLDominusPrintJob

   LOCAL aProjection := {}
   LOCAL hRecord
   LOCAL nOldRecno
   LOCAL nStarted
   LOCAL lDiagnostics := LLDetailDiagnosticsEnabled()

   IF ! ::ValidateDetailPlan( oMem, hPlan )
      RETURN NIL
   ENDIF

   nOldRecno := oMem:RecNo()
   oMem:GoTop()
   DO WHILE ! oMem:Eof()
      IF lDiagnostics
         nStarted := LLDetailMilliseconds()
      ENDIF
      hRecord := ::MemToHashSelected( oMem, hPlan )
      IF lDiagnostics
         ::hDetailStats[ "nHashMs" ] += LLDetailMilliseconds() - nStarted
      ENDIF
      IF ! HB_ISHASH( hRecord )
         IF nOldRecno > 0
            oMem:GoTo( nOldRecno )
         ENDIF
         RETURN NIL
      ENDIF
      AAdd( aProjection, hRecord )
      IF lDiagnostics
         ::hDetailStats[ "nProjectionBytes" ] += Len( hb_jsonEncode( hRecord ) )
      ENDIF
      oMem:Skip()
   ENDDO
   IF nOldRecno > 0
      oMem:GoTo( nOldRecno )
   ENDIF

RETURN aProjection

//------------------------------------------------------------------------------

METHOD ResolveDetailMode( hPlan ) CLASS TLLDominusPrintJob

   LOCAL cMode := Upper( AllTrim( ::cDetailMode ) )

   IF ! ::lDetailOptimization .OR. ! HB_ISHASH( hPlan ) .OR. ;
      ! hb_HHasKey( hPlan, "lSafe" ) .OR. ! hPlan[ "lSafe" ]
      RETURN "FULL"
   ENDIF
   IF cMode == "B"
      RETURN "B"
   ENDIF
   IF cMode == "FULL"
      RETURN "FULL"
   ENDIF

   // Los modos A y B son experimentales: TMemDataset:GetRow() no ha resultado
   // equivalente a GetRowToHash() en conjuntos grandes. AUTO conserva siempre
   // el recorrido completo y estable; A/B solo se activan de forma explicita.
RETURN iif( cMode == "A", "A", "FULL" )

//------------------------------------------------------------------------------

METHOD ResetDetailStats() CLASS TLLDominusPrintJob

   ::hDetailStats := { ;
      "cMode" => "FULL", ;
      "cFallback" => "", ;
      "nPrepareMs" => 0.0, ;
      "nProjectionMs" => 0.0, ;
      "nHashMs" => 0.0, ;
      "nSendMs" => 0.0, ;
      "nNormalizeMs" => 0.0, ;
      "nAccumulateMs" => 0.0, ;
      "nTotalDetailMs" => 0.0, ;
      "nEngineMs" => 0.0, ;
      "nJobToIRMs" => 0.0, ;
      "nRecords" => 0, ;
      "nOriginalFields" => 0, ;
      "nSelectedFields" => 0, ;
      "nValuesProcessed" => 0, ;
      "nPayloadBytes" => 0, ;
      "nProjectionBytes" => 0 ;
   }

RETURN Self

//------------------------------------------------------------------------------

METHOD LogDetailStats() CLASS TLLDominusPrintJob

   LOCAL hEngineStats

   IF ! LLDetailDiagnosticsEnabled() .OR. ! HB_ISHASH( ::hDetailStats )
      RETURN Self
   ENDIF
   IF HB_ISOBJECT( ::oEngine ) .AND. __objHasMsg( ::oEngine, "GETINPUTDIAGNOSTICS" )
      hEngineStats := ::oEngine:GetInputDiagnostics()
      IF HB_ISHASH( hEngineStats )
         ::hDetailStats[ "nNormalizeMs" ] := hEngineStats[ "nNormalizeMs" ]
         ::hDetailStats[ "nAccumulateMs" ] := hEngineStats[ "nAccumulateMs" ]
      ENDIF
   ENDIF

   LLDetailLog( ;
      "detail" + ;
      " mode=" + ::hDetailStats[ "cMode" ] + ;
      " fallback=" + ::hDetailStats[ "cFallback" ] + ;
      " records=" + hb_NToS( ::hDetailStats[ "nRecords" ] ) + ;
      " fields_original=" + hb_NToS( ::hDetailStats[ "nOriginalFields" ] ) + ;
      " fields_selected=" + hb_NToS( ::hDetailStats[ "nSelectedFields" ] ) + ;
      " values=" + hb_NToS( ::hDetailStats[ "nValuesProcessed" ] ) + ;
      " prepare_ms=" + hb_NToS( ::hDetailStats[ "nPrepareMs" ] ) + ;
      " projection_ms=" + hb_NToS( ::hDetailStats[ "nProjectionMs" ] ) + ;
      " hash_ms=" + hb_NToS( ::hDetailStats[ "nHashMs" ] ) + ;
      " send_ms=" + hb_NToS( ::hDetailStats[ "nSendMs" ] ) + ;
      " normalize_ms=" + hb_NToS( ::hDetailStats[ "nNormalizeMs" ] ) + ;
      " accumulate_ms=" + hb_NToS( ::hDetailStats[ "nAccumulateMs" ] ) + ;
      " total_detail_ms=" + hb_NToS( ::hDetailStats[ "nTotalDetailMs" ] ) + ;
      " payload_bytes=" + hb_NToS( ::hDetailStats[ "nPayloadBytes" ] ) + ;
      " projection_bytes=" + hb_NToS( ::hDetailStats[ "nProjectionBytes" ] ) ;
   )

RETURN Self

//------------------------------------------------------------------------------

METHOD NormalizeMemHash( hRecord ) CLASS TLLDominusPrintJob

   LOCAL hNormalized := {=>}
   LOCAL cFieldName

   IF ! HB_ISHASH( hRecord )
      RETURN hRecord
   ENDIF

   FOR EACH cFieldName IN hb_HKeys( hRecord )
      hNormalized[ ::NormalizeMemFieldName( cFieldName ) ] := hRecord[ cFieldName ]
   NEXT

RETURN hNormalized

//------------------------------------------------------------------------------

METHOD NormalizeMemFieldName( cFieldName ) CLASS TLLDominusPrintJob

   LOCAL cUtf8
   LOCAL cNormalized := ""
   LOCAL cChar
   LOCAL nIndex
   LOCAL nLength
   LOCAL lValid

   IF ! HB_ISSTRING( cFieldName )
      RETURN cFieldName
   ENDIF

   cUtf8   := hb_StrToUTF8( cFieldName, "ESWIN" )
   cUtf8   := StrTran( cUtf8, "á", "a" )
   cUtf8   := StrTran( cUtf8, "é", "e" )
   cUtf8   := StrTran( cUtf8, "í", "i" )
   cUtf8   := StrTran( cUtf8, "ó", "o" )
   cUtf8   := StrTran( cUtf8, "ú", "u" )
   cUtf8   := StrTran( cUtf8, "Á", "A" )
   cUtf8   := StrTran( cUtf8, "É", "E" )
   cUtf8   := StrTran( cUtf8, "Í", "I" )
   cUtf8   := StrTran( cUtf8, "Ó", "O" )
   cUtf8   := StrTran( cUtf8, "Ú", "U" )
   cUtf8   := StrTran( cUtf8, "ü", "u" )
   cUtf8   := StrTran( cUtf8, "Ü", "U" )
   nLength := hb_UTF8Len( cUtf8 )

   FOR nIndex := 1 TO nLength
      cChar  := hb_UTF8SubStr( cUtf8, nIndex, 1 )
      lValid := ( cChar >= "A" .AND. cChar <= "Z" ) .OR. ;
         ( cChar >= "a" .AND. cChar <= "z" ) .OR. ;
         ( cChar >= "0" .AND. cChar <= "9" ) .OR. ;
         cChar == "_" .OR. cChar == "ñ" .OR. cChar == "Ñ"
      cNormalized += iif( lValid, cChar, "_" )
   NEXT

RETURN hb_UTF8ToStr( cNormalized, "ESWIN" )

//------------------------------------------------------------------------------

METHOD ResolveAskStrings() CLASS TLLDominusPrintJob

   LOCAL hAskStrings
   LOCAL aVariables
   LOCAL cVariable
   LOCAL hDefinition
   LOCAL hResult
   LOCAL oDialog
   LOCAL nIndex

   IF ! HB_ISOBJECT( ::oEngine )
      RETURN .F.
   ENDIF
   hAskStrings := ::oEngine:GetAskStrings()
   IF Len( hAskStrings ) == 0
      RETURN .T.
   ENDIF

   aVariables := hb_HKeys( hAskStrings )
   ASort( aVariables )
   FOR nIndex := 1 TO Len( aVariables )
      cVariable := aVariables[ nIndex ]
      hDefinition := hAskStrings[ cVariable ]
      IF hb_HHasKey( ::hAskStringValues, cVariable )
         ::oEngine:SetAskStringValue( cVariable, ::hAskStringValues[ cVariable ] )
         LOOP
      ENDIF

      oDialog := TFormLLDominusAskString():New( ::oOwner )
      hResult := oDialog:Execute( hDefinition, nIndex, Len( aVariables ) )
      oDialog:End()
      IF ! HB_ISHASH( hResult ) .OR. ! hb_HHasKey( hResult, "lAccepted" ) .OR. ! hResult[ "lAccepted" ]
         ::lCanceled := .T.
         RETURN .F.
      ENDIF
      ::SetAskStringValue( cVariable, iif( hb_HHasKey( hResult, "cValue" ), hResult[ "cValue" ], "" ) )
   NEXT

RETURN .T.

//------------------------------------------------------------------------------

METHOD ResolveLabelStart() CLASS TLLDominusPrintJob

   LOCAL hLayout
   LOCAL hResult
   LOCAL oDialog

   IF ! HB_ISOBJECT( ::oEngine )
      RETURN .F.
   ENDIF

   hLayout := ::oEngine:GetLabelPrintLayout()
   IF ! HB_ISHASH( hLayout ) .OR. ;
      ! hb_HHasKey( hLayout, "lLabelDocument" ) .OR. ;
      ! hLayout[ "lLabelDocument" ] .OR. ;
      ! hb_HHasKey( hLayout, "nCapacity" ) .OR. ;
      hLayout[ "nCapacity" ] <= 1
      RETURN .T.
   ENDIF

   oDialog := TFormLLDominusLabelStart():New( ::oOwner )
   hResult := oDialog:Execute( hLayout )
   oDialog:End()

   IF ! HB_ISHASH( hResult ) .OR. ;
      ! hb_HHasKey( hResult, "lAccepted" ) .OR. ;
      ! hResult[ "lAccepted" ]
      ::lCanceled := .T.
      RETURN .F.
   ENDIF

   ::oEngine:SetEtiquetaInicial( iif( ;
      hb_HHasKey( hResult, "nEtiquetaInicial" ), ;
      hResult[ "nEtiquetaInicial" ], ;
      1 ) )

RETURN .T.

//------------------------------------------------------------------------------

METHOD ExecuteOutput() CLASS TLLDominusPrintJob

   LOCAL oPreviewForm
   LOCAL oPreviewCache
   LOCAL oFirstEmf
   LOCAL nEngineStarted
   LOCAL nEngineElapsed
   LOCAL aIRPages := {}

   IF ::nOutputType == LLDOMINUS_OUTPUT_DESIGN
      RETURN ::OpenDesigner()
   ENDIF

   Printer:cJobTitle := ::cTitle
   ::oRenderer := TRenderTPrinter():New( Printer )
   // CloseJob() devuelve una vista de los buffers del Engine. Ya no se
   // necesita al ejecutar y conservarla impediria liberar aRegLine.
   ::hJob := {=>}
   nEngineStarted := LLDetailMilliseconds()

   DO CASE
   CASE ::nOutputType == LLDOMINUS_OUTPUT_PREVIEW
      ::oDocument := RunLLEngineWithProgress( ::oEngine, ::oOwner )

   CASE ::nOutputType == LLDOMINUS_OUTPUT_PRINT
      ::oDocument := RunLLEngineAndPrintWithProgress( ::oEngine, ::oRenderer, ::cTitle, ::oOwner )

   OTHERWISE
      ::SetError( "Tipo de impresion no valido.", .T. )
      ::ReleaseRuntimeOutput()
      RETURN .F.
   ENDCASE

   IF ::oEngine:WasCanceled() .OR. ::oRenderer:WasCanceled()
      ::lCanceled := .T.
      ::ReleaseRuntimeOutput()
      RETURN .F.
   ENDIF

   IF ! HB_ISOBJECT( ::oDocument )
      ::SetError( ::ResolveError(), .T. )
      ::ReleaseRuntimeOutput()
      RETURN .F.
   ENDIF
   nEngineElapsed := LLDetailMilliseconds() - nEngineStarted
   ::hDetailStats[ "nEngineMs" ] := nEngineElapsed

   IF ! ::oDocument:IsPageSpooling()
      aIRPages := ::oDocument:GetPaginas()
   ENDIF
   ::hIR := { ;
      "nAncho"   => ::oDocument:nAncho, ;
      "nAlto"    => ::oDocument:nAlto, ;
      "cPaperHeightMode" => ::oDocument:cPaperHeightMode, ;
      "aPaginas" => aIRPages ;
   }
   ::hDetailStats[ "nJobToIRMs" ] := LLDetailMilliseconds() - ::nRunStartedMs
   ::LogIRDiagnostic()

   IF ::nOutputType == LLDOMINUS_OUTPUT_PREVIEW
      WITH OBJECT oPreviewCache := TLLPreviewCache():New( ::oDocument, ::oRenderer )
         :Create()
      END
      IF oPreviewCache:TotalPages() < 1
         oPreviewCache:End()
         ::SetError( "El documento no contiene paginas para previsualizar.", .T. )
         ::ReleaseRuntimeOutput()
         RETURN .F.
      ENDIF
      oFirstEmf := oPreviewCache:GetPage( 1, .T. )
      IF ! HB_ISOBJECT( oFirstEmf )
         oPreviewCache:End()
         ::SetError( ::ResolveError(), .T. )
         ::ReleaseRuntimeOutput()
         RETURN .F.
      ENDIF

      oPreviewForm := TFormLLPreview():New( NIL, oPreviewCache, ::oRenderer, ::oDocument, ;
         ::cTitle, ::oDocument:cPaperHeightMode == "content", LLPreviewMilliseconds() )
      oPreviewForm:ShowModal( SW_MAXIMIZE )
      oPreviewForm:ReleasePreview()
      oPreviewForm:OnClose := NIL
      oPreviewForm := NIL
      oPreviewCache := NIL
      oFirstEmf := NIL
   ENDIF

   ::ReleaseRuntimeOutput()

RETURN .T.

//------------------------------------------------------------------------------

METHOD ReleaseRuntimeOutput() CLASS TLLDominusPrintJob

   IF HB_ISOBJECT( ::oRenderer )
      ::oRenderer:End()
   ENDIF
   IF HB_ISOBJECT( ::oEngine )
      ::oEngine:End()
   ELSEIF HB_ISOBJECT( ::oDocument ) .AND. __objHasMsg( ::oDocument, "END" )
      ::oDocument:End()
   ENDIF

   ::oRenderer := NIL
   ::oEngine   := NIL
   ::oDocument := NIL
   ::hJob      := {=>}
   ::hIR       := {=>}
   hb_gcAll( .T. )

RETURN Self

//------------------------------------------------------------------------------

METHOD OpenDesigner() CLASS TLLDominusPrintJob

   LOCAL oApi
   LOCAL hVariables := ::BuildDesignerVariables()
   LOCAL cOutputFile
   LOCAL lSaved      := .F.

   IF ! HB_ISHASH( hVariables )
      RETURN .F.
   ENDIF

   cOutputFile := iif( Empty( ::cDesignerOutputFile ), ::cTemplateFile, ::cDesignerOutputFile )

   WITH OBJECT oApi := TApiDisenadorLLDominus():New( ::cApiHost, ::cApiPath, ::lApiSecure, ::cApiKey ):Create()
      :SetEncodingUTF8()

      IF ! :CrearSesion( ::cTemplate, hVariables, ::cTemplateFile )
         ::SetError( :cLastError, .T. )
         :End()
         RETURN .F.
      ENDIF

      IF ! :AbrirDisenador()
         ::SetError( :cLastError, .T. )
         :CerrarSesion()
         :End()
         RETURN .F.
      ENDIF

      DO WHILE ! lSaved
         MsgInfo( ;
            "Edite la plantilla en el navegador y pulse Guardar." + hb_eol() + ;
            "Despues cierre este mensaje para recuperar el resultado.", ;
            "LL-Dominus" ;
         )

         lSaved := :GuardarResultado( cOutputFile )
         IF ! lSaved .AND. ! MsgYesNo( :cLastError + hb_eol() + hb_eol() + "Desea volver a intentarlo?", "LL-Dominus" )
            ::cLastError := :cLastError
            EXIT
         ENDIF
      ENDDO

      IF lSaved
         MsgInfo( "Plantilla recibida: " + cOutputFile, "LL-Dominus" )
      ENDIF

      :CerrarSesion()
      :End()
   END

   IF ! lSaved .AND. Empty( ::cLastError )
      ::cLastError := "No se ha recuperado la plantilla editada."
   ENDIF

RETURN lSaved

//------------------------------------------------------------------------------

METHOD BuildDesignerVariables() CLASS TLLDominusPrintJob

   LOCAL oConnector
   LOCAL hConnectorData
   LOCAL hVariables
   LOCAL aDetail := {}

   IF ! HB_ISHASH( ::hJob ) .OR. ;
      ! hb_HHasKey( ::hJob, "aStructVar" ) .OR. ;
      ! hb_HHasKey( ::hJob, "aRegVar" ) .OR. ;
      ! hb_HHasKey( ::hJob, "aStructLine" ) .OR. ;
      ! hb_HHasKey( ::hJob, "aRegLine" )
      ::SetError( "El motor no ha devuelto los datos necesarios para el disenador.", .T. )
      RETURN NIL
   ENDIF

   IF HB_ISARRAY( ::hJob[ "aRegLine" ] ) .AND. Len( ::hJob[ "aRegLine" ] ) > 0
      AAdd( aDetail, ::hJob[ "aRegLine" ][ 1 ] )
   ENDIF

   WITH OBJECT oConnector := TConectorDatosReg():New():Create()
      :SendDataVar( ::hJob[ "aStructVar" ], ::hJob[ "aRegVar" ] )
      :SendDataLine( ::hJob[ "aStructLine" ], aDetail )
      hConnectorData := :GetData()
      :End()
   END

   IF ! HB_ISHASH( hConnectorData ) .OR. ;
      ! hb_HHasKey( hConnectorData, "DataVar" ) .OR. ;
      ! hb_HHasKey( hConnectorData, "DetalleVar" )
      ::SetError( "No se han podido preparar las variables para el disenador.", .T. )
      RETURN NIL
   ENDIF

   hVariables := { ;
      "DataVar"    => hb_HClone( hConnectorData[ "DataVar" ] ), ;
      "DetalleVar" => hb_HClone( hConnectorData[ "DetalleVar" ] ) ;
   }

RETURN hVariables

//------------------------------------------------------------------------------

METHOD LogIRDiagnostic() CLASS TLLDominusPrintJob

   LOCAL cIR

   IF ! LLDetailDiagnosticsEnabled() .OR. ! HB_ISHASH( ::hIR )
      RETURN Self
   ENDIF

   cIR := hb_jsonEncode( ::hIR )
   LLDetailLog( ;
      "ir" + ;
      " mode=" + ::hDetailStats[ "cMode" ] + ;
      " pages=" + hb_NToS( iif( HB_ISOBJECT( ::oDocument ), ::oDocument:TotalPaginas(), Len( ::hIR[ "aPaginas" ] ) ) ) + ;
      " bytes=" + hb_NToS( Len( cIR ) ) + ;
      " checksum=" + LLDetailChecksum( cIR ) + ;
      " engine_ms=" + hb_NToS( ::hDetailStats[ "nEngineMs" ] ) + ;
      " job_to_ir_ms=" + hb_NToS( ::hDetailStats[ "nJobToIRMs" ] ) ;
   )

RETURN Self

//------------------------------------------------------------------------------

METHOD ResolveError() CLASS TLLDominusPrintJob

   LOCAL cError := ::oEngine:cLastError

   IF Empty( cError ) .AND. HB_ISOBJECT( ::oRenderer ) .AND. Len( ::oRenderer:GetAvisos() ) > 0
      cError := ATail( ::oRenderer:GetAvisos() )
   ENDIF

   IF Empty( cError )
      cError := "No se ha podido generar o procesar el documento."
   ENDIF

RETURN cError

//------------------------------------------------------------------------------

METHOD SetError( cError, lShow ) CLASS TLLDominusPrintJob

   DEFAULT lShow TO .F.

   ::cLastError := cError
   IF lShow .AND. ! Empty( cError )
      MsgStop( cError, "LL-Dominus" )
   ENDIF

RETURN .F.

//------------------------------------------------------------------------------

FUNCTION LLDetailDiagnosticsEnabled()

RETURN LLDOMINUS_DETAIL_DIAGNOSTICS == 1

//------------------------------------------------------------------------------

FUNCTION LLDetailMilliseconds()

RETURN hb_TToN( hb_DateTime() ) * 86400000.0

//------------------------------------------------------------------------------

FUNCTION LLDetailLogPath()

   LOCAL cBase := hb_DirBase()

   IF Empty( cBase )
      cBase := CurDir()
   ENDIF
   IF ! Right( cBase, 1 ) $ "/\"
      cBase += hb_ps()
   ENDIF

RETURN cBase + LLDOMINUS_DETAIL_LOG_NAME

//------------------------------------------------------------------------------

FUNCTION LLDetailLog( cText )

   LOCAL hFile

   IF ! LLDetailDiagnosticsEnabled()
      RETURN NIL
   ENDIF

   hFile := FOpen( LLDetailLogPath(), 2 )
   IF hFile < 0
      hFile := FCreate( LLDetailLogPath() )
   ENDIF
   IF hFile >= 0
      FSeek( hFile, 0, 2 )
      FWrite( hFile, hb_TToC( hb_DateTime(), "YYYY-MM-DD", "HH:MM:SS.FFF" ) + ;
         " | " + iif( HB_ISSTRING( cText ), cText, "" ) + hb_eol() )
      FClose( hFile )
   ENDIF

RETURN NIL

//------------------------------------------------------------------------------

FUNCTION LLDetailChecksum( cText )

   LOCAL nA := 1
   LOCAL nB := 0
   LOCAL nIndex

   IF ! HB_ISSTRING( cText )
      RETURN "0"
   ENDIF
   FOR nIndex := 1 TO Len( cText )
      nA := ( nA + Asc( SubStr( cText, nIndex, 1 ) ) ) % 65521
      nB := ( nB + nA ) % 65521
   NEXT

RETURN hb_NToS( ( nB * 65536 ) + nA )
