/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
// escape.c

#define INCL_DOS
#define INCL_SPL
#define INCL_SPLFSE
#define INCL_PM
#include <os2.h>


#define INCL_32
#define INCL_VMANDDI
#include <ddi.h>

#define INCL_GREALL
#define INCL_DDIMISC
#define INCL_GRE_DEVICESURFACE
#include <pmddi.h>

// c includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>


#define INCL_GENPLIB_JOURNAL
#define INCL_GENPLIB_MEMORY
#include <genplib.h>



#include "def.h"
#include "driver.h"
#include "funcs.h"




// maximum band size in bytes
//#define MAX_BYTESFORBAND     0x040000         // 4 bands on 300dpi, 8 on 600 (letter)
#define MAX_BYTESFORBAND     0x100000           // no bands 300dpi, 4 on 600       "
//#define MAX_BYTESFORBAND  0x400000              // no bands on 600 dpi letter




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

LONG APIENTRY Escape(              HDC          hdc,
                                   LONG         lEscape,
                                   LONG         cInCount,
                                   PBYTE        pInData,
                                   PLONG        pcOutCount,
                                   PBYTE        pOutData,
                                   PDDC         pddc,
                                   ULONG        ulFunction )
{
  APIRET          rc;
  BOOL            fOK;
  HSTD            hstd;
  LONG            cBytesPerLine;
  LONG            lLength;
  LONG            lStart;
  LONG            lStdLength;
  LONG            lWork;
  LONG            lrc;
  LONG            yPelsOnBand;
  PCHAR           pchSplBuffer = NULL;
  PDEVICESURFACE  pds;
  REGREC          regrec;
  ULONG           ul;
  ULONG           ulException;
  ULONG           ulWork;



  assert( pddc );
  assert( pddc->hdc == hdc );

  // assume success
  lrc = DEV_OK;



  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up
    if( pchSplBuffer ) {
      GplMemoryFree( pchSplBuffer );
    }
    CheckForTermination( &regrec, ulException, pddc );
    // error result
    lrc = DEVESC_ERROR;
    goto depart;
  }



  // if abortdoc set flag before blocking on sem
  if(  DEVESC_ABORTDOC ==  lEscape ) {
    pddc->fAbortDocCalled  = TRUE;
  }

  rc = DosRequestMutexSem( pddc->hmtxDCSem, SEM_INDEFINITE_WAIT );
  assert( rc == 0 );

  switch( lEscape ) {
  case DEVESC_STARTDOC:
    DBPRINTF(( "DEVESC_STARTDOC\n" ));

    // new document for this DC
    pddc->fAbortDocCalled  = FALSE;
    pddc->fEndDocCalled    = FALSE;

    // ignore multiple startdocs; it's not an error
    if( pddc->fStartDocCalled ) break;

    // pInData is document name; copy if non null
    assert( cInCount ? (BOOL)pInData:TRUE );
    strncpy( pddc->szDocumentName,  pInData ? pInData:"",  sizeof( pddc->szDocumentName )-1 );

    pds = pddc->pDeviceSurface;
    assert( pds );


    // Decide to generate printer-specific output, or create a metafile.
    switch( pddc->ulType ) {
    case OD_QUEUED:
      DBPRINTF(( "  OD_QUEUED case\n" ));
      assert( pddc->ulDataType == PM_Q_RAW  ||  pddc->ulDataType == PM_Q_STD );

      // this allocates a queued print job; need only be done once per DC instance
      // closed in enable.c at BEGIN_CLOSE_DC time
      if( !pddc->hspl ) {
        pddc->hspl = SplQmOpen( "*", 9, (PQMOPENDATA)&pddc->dop );
      }
      assert( pddc->hspl );

      // Queue manager start document.
      fOK = SplQmStartDoc( pddc->hspl, pddc->szDocumentName  );
      assert( fOK );

      switch( pddc->ulDataType ) {
      case PM_Q_RAW:
        // We will generate some printer-specific output.
        pddc->fGenOutput = TRUE;
        break;
      case PM_Q_STD:
        // Start recording GPI calls into a metafile.
        fOK = SplStdStart( pddc->hdc );
        assert( fOK );
        break;
      }
      break;

    case OD_DIRECT:
      DBPRINTF(( "  OD_DIRECT case\n" ));
      assert( pddc->ulDataType == PM_Q_RAW );

      // We always generate printer-specific output for an OD_DIRECT DC.
      pddc->fGenOutput = TRUE;
      rc  =  PrtOpen( pddc->szLogAddress, &pddc->hspl, &ulWork, 0, FILE_NORMAL,
                      OPEN_ACTION_REPLACE_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW,
                      OPEN_ACCESS_WRITEONLY         | OPEN_SHARE_DENYREADWRITE, 0 );
      assert( 0 == rc );
      assert( pddc->hspl );
      break;

    case OD_MEMORY:
    case OD_METAFILE:
    case OD_INFO:
      // don't expect to see these
      assert( FALSE );
      break;
    }



    if( pddc->fGenOutput ) {
      // generate printer-specific output

#ifndef NDEBUG
      // debug versions write a disk file. see DosWrite() in WriteOutput() in helper.c
      rc = DosOpen( "\\MDRIVER.OUT", &pddc->hOutput, &ul, 0, FILE_NORMAL, 0x12, 0x2192, NULL );
      if( rc ) {
        pddc->hOutput = 0;
      }
#endif

      // Compute count of bytes per line for an internal, byte-aligned bitmap.
      ulWork = pds->DevCaps[ CAPS_WIDTH ]  * pds->SurfaceBmapInfo.ulBpp;
      cBytesPerLine = ( ulWork / 8 ) + ( ulWork % 8 ? 1 : 0 );
      assert( 10 < cBytesPerLine && cBytesPerLine < 30000 );

      // place bytes per scan line in the BMAPINFO
      pds->SurfaceBmapInfo.ulBytesPerLine = cBytesPerLine;

      // compute the number of scanlines in a band
      yPelsOnBand = MAX_BYTESFORBAND / cBytesPerLine;

      // count of scan lines needed is the lesser of scan lines per band vs scan lines per page
      yPelsOnBand = min( yPelsOnBand, pds->DevCaps[ CAPS_HEIGHT ] );
      pds->SurfaceBmapInfo.ulHeight = yPelsOnBand;

      // set band counters; when cBandsRequired == 1, no journaling is required
      pddc->cBandsRequired = ( pds->DevCaps[ CAPS_HEIGHT ] / yPelsOnBand ) + ( pds->DevCaps[ CAPS_HEIGHT ] % yPelsOnBand ? 1:0 );

      DBPRINTF(( "x,y pagesize=%d,%d; cBytesPerLine=%d; yPelsOnBand=%d\n", pds->DevCaps[ CAPS_WIDTH ], pds->DevCaps[ CAPS_HEIGHT ], cBytesPerLine, yPelsOnBand ));
      DBPRINTF(( "%d bands required\n", pddc->cBandsRequired ));

      // decide if I have to journal/band for this job
      pddc->fMustJournal = pddc->cBandsRequired > 1 && !pddc->fRawDataOnly;

      if( !pddc->fRawDataOnly ) {
        // fRawDataOnly is true when the queue processor (and PMSPOOL.EXE)
        // told us ahead of time that only DEVESC_RAWDATA would be
        // coming for this DC. See where this flags is set in enable.c

        // Compute how many bytes of memory for the drawing
        ulWork = pds->SurfaceBmapInfo.ulHeight * cBytesPerLine;
        assert( ulWork <=  MAX_BYTESFORBAND  );

        // Allocate memory for the surface bitmap.
        pds->SurfaceBmapInfo.pBits = GplMemoryAlloc( pddc->hmcbHeap, ulWork );
        assert( pds->SurfaceBmapInfo.pBits );
      }


      // may or may not journal, but store this good data anyway
      pddc->JournalInput.ulSize             = sizeof( pddc->JournalInput );
      pddc->JournalInput.hModule            = globals.hModule;
      pddc->JournalInput.hdc                = hdc;
      pddc->JournalInput.usXLength          = pds->DevCaps[ CAPS_WIDTH ];
      pddc->JournalInput.usYLength          = pds->DevCaps[ CAPS_HEIGHT ];
      pddc->JournalInput.bPortrait          = TRUE;            // DEVICE_SPECIFIC: always say portrait
      pddc->JournalInput.usBandLength       = yPelsOnBand;
      pddc->JournalInput.pfunArg            = (PVOID)pddc;

      switch( pddc->pSupportedDevice->cPhysicalColors ) {
      case 2:
        // monochrome printer
        pddc->JournalInput.pfunBandCallback   = (PJFUN)SendMonoBits;
        break;
      case 256:
      case 0x1000000:
      default:
        // don't have any color printers
        assert( FALSE );
        break;
      }


      if( pddc->fMustJournal )
      {
        // must journal; query size required for HJOURNAL (really a pointer to some instance data)
        ul = (ULONG)GplJournalCreateInstance( NULL, NULL, 0 );
        assert( ul );
        pddc->hJournal = (HJOURNAL)GplMemoryAlloc( pddc->hmcbHeap, ul );
        assert( pddc->hJournal );
        // create it for real
        rc = GplJournalCreateInstance( pddc->hJournal, &pddc->JournalInput, 0 );
        assert( rc );
      }

      // flags to defer printer initialization. see WriteOutput()
      pddc->fInitPrinter  = TRUE;
      pddc->fInitDocument = TRUE;
      pddc->fInitPage     = TRUE;

      // Clear the surface bitmap.
      fOK = GreErasePS( hdc );
      assert( fOK );
    }

    // set the surface in the GRE
    assert( globals.pfnSetDeviceSurface );
    ul = globals.pfnSetDeviceSurface( pddc->hdc, pddc->pDeviceSurface );
    assert( ul  );

    pddc->fStartDocCalled = TRUE;
    break;






  case DEVESC_RAWDATA:
    DBPRINTF(( "DEVESC_RAWDATA: " ));

    if( !pddc->fStartDocCalled ) {
      // error to do DEVESC_RAWDATA before DEVESC_STARTDOC
      RAISEEXCEPTION( XCPT_USER );
    }

    // Record that this escape happened.
    // Not nice to mix GPI printing and raw data. Code in newframe.c might be offended.
    pddc->fRawDataEscape = TRUE;

    // This is raw printer data, so page/doc init not needed.
    pddc->fInitDocument = FALSE;
    pddc->fInitPage     = FALSE;

    // must have called abortdoc if hspl is zero
    assert( pddc->hspl ? TRUE : pddc->fAbortDocCalled );

    if( pddc->hspl ) {
      fOK = prtput( pddc, pInData, cInCount );
      assert( fOK );
    }
    break;









  case DEVESC_ENDDOC:
    DBPRINTF(( "DEVESC_ENDDOC\n" ));

    // ignore enddoc if no startdoc; it's not an error
    if( ! pddc->fStartDocCalled ) break;

    pddc->fEndDocCalled    = TRUE;

    if( pddc->fGenOutput ) {

      if( !pddc->fRawDataEscape && !pddc->fRawDataOnly )
      {
        fOK = NewFrame( pddc );
        assert( fOK );
      }

      // free surface memory for bits
      if( pddc->pDeviceSurface->SurfaceBmapInfo.pBits )
      {
        rc = GplMemoryFree( pddc->pDeviceSurface->SurfaceBmapInfo.pBits );
        assert( 0 == rc );
        pddc->pDeviceSurface->SurfaceBmapInfo.pBits = NULL;
      }

      if( pddc->fMustJournal )
      {
        // close journal
        assert( pddc->hJournal );
        rc = GplJournalDeleteInstance( pddc->hJournal );
        assert( rc );
        GplMemoryFree( pddc->hJournal );
        pddc->hJournal = 0;
      }

      // needs work: should probably reset the printer here
      // or other final output generation.


#ifndef NDEBUG
      // debug versions wrote output to \MDRIVER.OUT
      if( pddc->hOutput ) {
        rc = DosClose( pddc->hOutput );
        assert( rc == 0 );
        pddc->hOutput = 0;
      }
#endif
    }


    switch( pddc->ulType ) {
    case OD_DIRECT:
      DBPRINTF(( "  OD_DIRECT case\n" ));

      switch( pddc->ulDataType ) {
      case PM_Q_RAW:
        assert(  pddc->hspl );
        rc = PrtClose( pddc->hspl );
        assert( 0 == rc );
        pddc->hspl = 0;
        break;
      case PM_Q_STD:
        // impossible
        assert( FALSE );
        break;
      }
      break;

    case OD_QUEUED:
      DBPRINTF(( "  OD_QUEUED case\n" ));
      switch( pddc->ulDataType ) {
      case PM_Q_RAW:
        lWork = (LONG)SplQmEndDoc( pddc->hspl );
        assert( lWork != SPL_ERROR );
        break;
      case PM_Q_STD:
        // stop recording and get handle to job
        hstd = SplStdStop( pddc->hdc );
        assert( hstd );

        // get length of metafile as a count of bytes
        lStdLength = SplStdQueryLength( hstd );

        // allocate some DDC heap for use as a buffer
        #define MAX_LEN_SPLBITBUFFER    0x1000
        lLength = min( MAX_LEN_SPLBITBUFFER, lStdLength );
        pchSplBuffer = (PCHAR) GplMemoryAlloc( pddc->hmcbHeap,  lLength );
        assert( pchSplBuffer );

        // loop as necessary
        lStart = 0;
        while( lStart < lStdLength ) {
          fOK = SplStdGetBits( hstd, lStart, lLength, pchSplBuffer );
          assert( fOK );

          fOK = prtput( pddc, pchSplBuffer, lLength  );
          assert( fOK );

          lStart += lLength;
          lLength = min( lLength, lStdLength-lStart );
        }

        // delete the spooler metafile data
        fOK = SplStdDelete( hstd );
        assert( fOK );

        // returns job ID
        lWork = (LONG)SplQmEndDoc( pddc->hspl );
        assert( lWork != SPL_ERROR );

        // if there is room at the pOutData pointer, then give the caller the job id (lWork)
        if( pcOutCount &&  *pcOutCount >= sizeof( lWork )) {
          // there is room
          assert( pOutData );
          *(PLONG)pOutData = lWork;
          *pcOutCount = sizeof( lWork );
        }

        GplMemoryFree( pchSplBuffer );
        pchSplBuffer = NULL;

        // in begin-close-dc, check for non-null and clean up there if need be
        pddc->hJournal = 0;
        break;
      }
      break;

    default:
      assert( FALSE );
      break;
    }

    // this doc is over
    // clean up this ddc in case there is another startdoc/enddoc pair down the road
    pddc->fStartDocCalled  = FALSE;
    pddc->fAbortDocCalled  = FALSE;
    pddc->fMustJournal     = FALSE;
    pddc->ulPage           = 0;
    memset( pddc->szDocumentName, 0, sizeof( pddc->szDocumentName ));
    break;







  case DEVESC_NEWFRAME:
    DBPRINTF(( "DEVESC_NEWFRAME\n" ));

    // call routine to dump bitmap bits to printer
    if( pddc->fGenOutput ) {
      fOK = NewFrame( pddc );
      assert( fOK );
    }

    switch( pddc->ulType ) {
    case OD_DIRECT:
      break;
    case OD_QUEUED:
      // this is being recorded or metafiled
      break;
    default:
      assert( FALSE );
      break;
    }
    break;





  case DEVESC_ABORTDOC:
    DBPRINTF(( "DEVESC_ABORTDOC\n" ));
    pddc->fStartDocCalled = FALSE;

    // abortdocs can arrive more than once
    if( !pddc->fAbortDocCalled ) {
      pddc->fAbortDocCalled  = TRUE;
      switch( pddc->ulType ) {
      case OD_DIRECT:
        // DEVICE_SPECIFIC: send escape to reset printer
        rc = PrtClose( pddc->hspl );
        assert( rc ==  0 );
        pddc->hspl = 0;
        break;
      case OD_QUEUED:
        fOK = SplQmAbortDoc( pddc->hspl );
        assert( fOK );
        switch( pddc->ulDataType ) {
        case PM_Q_STD:
          hstd = SplStdStop( hdc );
          assert( hstd );
          fOK = SplStdDelete( hstd );
          assert( fOK );
          break;
        case PM_Q_RAW:
          break;
        }
        break;
      }
    }
    break;



  // devescapes not implemented
  case DEVESC_BREAK_EXTRA:
  case DEVESC_CHAR_EXTRA:
  case DEVESC_DBE_FIRST:
  case DEVESC_DBE_LAST:
  case DEVESC_DRAFTMODE:
  case DEVESC_ERROR:
  case DEVESC_FLUSHOUTPUT:
  case DEVESC_GETCP:
  case DEVESC_GETSCALINGFACTOR:
  case DEVESC_NEXTBAND:
  case DEVESC_QUERYVIOCELLSIZES:
  case DEVESC_SETMODE:
  case DEVESC_STD_JOURNAL:
    // assert( FALSE );
    lrc = DEVESC_NOTIMPLEMENTED;
    break;


  case DEVESC_QUERYESCSUPPORT:
    assert( pInData );
    assert( cInCount == sizeof( LONG ));
    switch( *(PLONG)pInData  ) {
    case DEVESC_BREAK_EXTRA:
    case DEVESC_CHAR_EXTRA:
    case DEVESC_DBE_FIRST:
    case DEVESC_DBE_LAST:
    case DEVESC_DRAFTMODE:
    case DEVESC_ERROR:
    case DEVESC_FLUSHOUTPUT:
    case DEVESC_GETCP:
    case DEVESC_GETSCALINGFACTOR:
    case DEVESC_NEXTBAND:
    case DEVESC_QUERYVIOCELLSIZES:
    case DEVESC_SETMODE:
    case DEVESC_STD_JOURNAL:
      lrc = DEVESC_NOTIMPLEMENTED;
      break;
    default:
      lrc = DEV_OK;
      break;
    }
    break;

  default:
    assert( FALSE );
    break;
  }

depart:
  rc = DosReleaseMutexSem( pddc->hmtxDCSem );
  assert( rc == 0 );
  UNREGISTERHANDLER( regrec );
  return lrc;
}

