/*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.                                */
/*                                                                           */
/*****************************************************************************/
// helper.c  helper functions including INI file access helpers

#define INCL_DEV
#define INCL_DOS
#define INCL_GPI
#define INCL_SPL
#define INCL_SPLFSE
#include <os2.h>


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

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

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


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



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



//---------------------------------------------------------------------------------------------------------------------
// build the application name used in INI file access

BOOL DRVENTRY BuildAppName( PSZ pszAppname, ULONG ulAppnameSize, PSZ pszPrintername, PSZ pszDrivername, PSZ pszDevicename)
{

  assert( pszPrintername );
  assert( strlen( pszPrintername ));

  assert( pszDrivername );
  assert( strlen( pszDrivername ));

  assert( pszDevicename );
  assert( strlen( pszDevicename ));

  assert( ulAppnameSize > 8 + strlen( pszDevicename ) + strlen( pszPrintername ) + strlen( pszDrivername ));

  sprintf( pszAppname, "PM_DD_%s,%s.%s", pszPrintername, pszDrivername, pszDevicename );

  return TRUE;
}







//---------------------------------------------------------------------------------------------------------------------
// try to make the supplied driver data into something that this version of the driver can use
// do not modify any data past ulLength


BOOL DRVENTRY SetDriverDataDefaults( PDRIVERDATA pdd, ULONG ulLength, PSZ pszDeviceName )
{

  BOOL  fRet;


  assert( pdd );
  assert( pdd->cb ? pdd->cb >= sizeof( DRIVERDATA ) : TRUE );
  assert( pszDeviceName );
  assert( strlen( pszDeviceName ));



  if( pdd->cb == 0 ) {
    // use this version's values for defaults
    assert( ulLength == sizeof( DRIVERDATA ));
    pdd->cb          = sizeof( DRIVERDATA );
    pdd->lVersion    = DRIVERDATA_VERSION;
    strcpy( pdd->szDeviceName, pszDeviceName );
  }


  switch( pdd->lVersion ) {
  // room to add cases for other versions...
  case DRIVERDATA_VERSION:
  default:
    pdd->ulOrientation = ORIENTATION_PORTRAIT;
    strcpy( pdd->szFormName, "Letter" );
    pdd->cCopies = 1;
    fRet = TRUE;
    break;
  }

  return fRet;
}





//---------------------------------------------------------------------------------------------------------------------
// return the pointer to the global structure PSUPPORTEDDEVICE given a device name

PSUPPORTEDDEVICE DRVENTRY PSDFromDeviceName( PSZ pszDeviceName )
{
  CHAR                 szDeviceName[ 40 ];
  PCHAR                pch;
  PCHAR                pchComma;
  PSUPPORTEDDEVICE     psd;
  PSUPPORTEDDEVICE     psdDesired;
  ULONG                ul;



  assert( pszDeviceName );
  assert( strlen( pszDeviceName ));



  psdDesired = NULL;
  pch = pszDeviceName;

  while( NULL == psdDesired && pch  ) {

    pchComma = strchr( pch, ',' );
    if( pchComma ) {
      *pchComma = 0;
      strcpy( szDeviceName, pch );
      *pchComma = ',';
      pch = pchComma + 1;
    } else {
      strcpy( szDeviceName, pch );
      pch = NULL;
    }

    // find the pointer to the supporteddevice struct given this portion of pszDeviceName supplied
    for( psd = globals.paDevice, ul = 0;   ul < globals.ulDeviceCount;  psd++, ul++ ) {
      if( 0 == strcmp( psd->szDeviceName, szDeviceName )) {
        // this is it
        psdDesired = psd;
        break;
      }
    }
  }

  return psdDesired;
}


//---------------------------------------------------------------------------------------------------------------------
// return the pointer to FORMINFO given the ASCIIZ representation of the form name and a pointer to the supported device info

PFORMINFO DRVENTRY FormInfoFromFormName( PSUPPORTEDDEVICE pSupportedDevice, PSZ pszFormName )
{
  PFORMINFO pfi;
  LONG      i;


  for( pfi = pSupportedDevice->paFormInfo, i = 0; i < pSupportedDevice->cForms; i++, pfi++ ) {
    if( 0 == strcmp( pfi->hcinfo.szFormname, pszFormName )) {
      // found it
      return pfi;
    }
  }
  return NULL;
}


//---------------------------------------------------------------------------------------------------------------------
// strip from the ends of string given in parameter 1 all characters in string in parameter 2
// strip left, right, or both according to pszOption which can be "LEFT", "RIGHT", or "BOTH"
// similar in syntax and function to Rexx strip(), except this function modifies the string at psz
// where rexx returns a new string and leaves the one passed as an argument intact.

PSZ DRVENTRY Strip( PSZ psz, PSZ pszOption, PSZ pszStripSet )
{


  assert( psz );
  assert( pszOption );
  assert( strchr( "LRBlrb", *pszOption ));
  assert( pszStripSet );
  assert( strlen( pszStripSet ));


  if( strchr( "LlBb", *pszOption )) {
    // strip on left
    while(  *psz && strchr( pszStripSet, *psz )) {
      strcpy( psz, psz+1 );
    }
  }

  if( strchr( "RrBb", *pszOption )) {
    // strip on right
    while(  *psz &&  strchr( pszStripSet, *(psz+strlen( psz )-1))  ) {
      *(psz+strlen(psz)-1) = 0;
    }
  }

  return psz;
}


// -----------------------------------------------------------------------------------------------------------------------
// Required call to lock and unlock the device; NOP for printer drivers

LONG APIENTRY LockDevice( )
{
  // return success
  return 1;
}


//---------------------------------------------------------------------------------------------------------------------
// init printer

BOOL DRVENTRY InitPrinter( PDDC pddc )
{
  ULONG     ul;


  assert( pddc->fGenOutput );


  // needs work: may need to switch printer emulations



  // DEVICE_SPECIFIC: reset printer
  prtputs( pddc, "\033E" );

  // DEVICE_SPECIFIC: set resolution
  assert( pddc->pSupportedDevice->ulXResolution ==  pddc->pSupportedDevice->ulYResolution );
  ul = pddc->pSupportedDevice->ulXResolution;
  switch( ul ) {
  case 300:
    prtprintf( pddc, "\033*t%dR", ul );
    break;
  case 600:
    prtprintf( pddc, "\033*t%dR\033&u%dD", ul, ul );
    break;
  }

  // DEVICE_SPECIFIC: set top margin
  prtputs( pddc, "\033&l0E" );

  return TRUE;
}


//---------------------------------------------------------------------------------------------------------------------
// init document

BOOL DRVENTRY InitDocument( PDDC pddc )
{

  assert( pddc->fGenOutput );


  // DEVICE_SPECIFIC: do some start-of-document processing given current device
  switch( pddc->pSupportedDevice->cPhysicalColors ) {
  case 2:
    // monochrome printer
    break;
  case 256:
  case 0x1000000:
  default:
    assert( FALSE );
    break;
  }

  return TRUE;
}



//---------------------------------------------------------------------------------------------------------------------
// init this page

BOOL DRVENTRY InitPage( PDDC pddc )
{
  assert( pddc->fGenOutput );


  // DEVICE_SPECIFIC: orientation
  switch( pddc->DriverData.ulOrientation ) {
  case ORIENTATION_PORTRAIT:
    prtputs( pddc, "\033&l0O" );
    break;
  case ORIENTATION_LANDSCAPE:
    prtputs( pddc, "\033&l1O" );
    break;
  default:
    assert( FALSE );
    break;
  }

  // DEVICE_SPECIFIC: presentation mode
  prtputs( pddc, "\033*r0F" );

  // DEVICE_SPECIFIC: set top margin
  prtputs( pddc, "\033&l0E" );

  return TRUE;
}



//-------------------------------------------------------------------------------------------------------------------------
/* A routine that calls PrtWrite() or SplQmWrite(), depending on if
OD_DIRECT or OD_QUEUED, with error checking and retry. Called
by prtprintf(), prtput(), and prtputs().

Needs work: this would be a good place to hand off the data to a
2nd thread created by the driver. With two threads, thread 1
gives the data to thread 2 and returns right away to generate
more output. Thread 2 buffers the data and writes it to the port
as fast as the port can receive it. */


ULONG DRVENTRY WriteOutput( PDDC pddc, PCHAR pch, ULONG cb )
{
  BOOL        fInitPrinter;
  BOOL        fInitDocument;
  BOOL        fInitPage;
  BOOL        fOK;
  ULONG       rc;
  ULONG       cWritten;
  ULONG       ulWork;
  ULONG       ulErrorInfo;
  ULONG       ulErrorData;
  PCHAR       pchMessage;




  // lazy init mechanism (see DEVESC_STARTDOC case in escape.c)

  if( pddc->fInitPrinter || pddc->fInitDocument || pddc->fInitPage )
  {

    fInitPrinter = pddc->fInitPrinter;
    pddc->fInitPrinter = FALSE;

    fInitDocument = pddc->fInitDocument;
    pddc->fInitDocument = FALSE;

    fInitPage = pddc->fInitPage;
    pddc->fInitPage = FALSE;


    if( fInitPrinter )
    {
      // need to send down printer init
      InitPrinter( pddc );
    }

    if( fInitDocument )
    {
      // need to send down document initialization
      InitDocument( pddc );
    }


    if( fInitPage )
    {
      // need to send down page init
      InitPage( pddc );
    }

  }




  switch( pddc->ulType ) {
  case OD_QUEUED:
    // The target of SplQmWrite is a file in the \spool subdirectory.
    fOK = SplQmWrite( pddc->hspl, cb, pch );
    assert( fOK );
    rc = 0;
    break;



  case OD_DIRECT:
#ifndef NDEBUG
    // debug versions write output to \mdriver.out
    // see where this file was opened in escape.c
    if( pddc->hOutput )
    {
      rc = DosWrite( pddc->hOutput, pch, cb, &ulWork );
    }
#endif

    cWritten   = 0;
    rc         = 0;

    // if error, it is usually these
    ulErrorInfo = SPLINFO_DDERROR | SPLINFO_ERROR;
    ulErrorData = SPLDATA_PRINTERJAM;

    while( 0 == rc  &&  cWritten < cb ) {

      // init pchMessage to null; set to non-null upon finding a problem
      pchMessage = NULL;

      // PrtWrite() expects the output buffer to be fully-committed
      // memory. Suballocated memory does not always work.
      rc = PrtWrite( pddc->hspl, pch, cb-cWritten, &ulWork );
      switch( rc ) {
      case 0:
        cWritten += ulWork;
        pch += ulWork;
        break;
      case 20:
        // needs work: English messages that need translation.
        //     unit
        pchMessage = "RC20: bad unit";
        break;
      case 28:
        // off line
        pchMessage = "RC28: off line";
        break;
      case 29:
        // device fault
        pchMessage = "RC29: device fault";
        break;
      default:
        pchMessage = "RC??: unexpected";
        ulErrorData |= SPLDATA_UNEXPECTERROR;
        DBPRINTF(( "Unexpected PrtWrite rc %d\n", rc ));
        assert( FALSE );
        break;
      }

      if( pchMessage )
      {
        // Problem happened on the write.
        assert( rc != 0 );
        assert( cWritten < cb );

        ulWork = SplMessageBox( pddc->szLogAddress, ulErrorInfo, ulErrorData, pchMessage, pddc->szDriverName, 0, MB_RETRYCANCEL );
        switch( ulWork ) {
        case MBID_RETRY:
          // force the continuation of while() above
          rc = 0;
          break;
        default:
          break;
        }
      }
    }
    break;


  default:
    assert( FALSE );
    break;
  }

  return rc;
}





//-------------------------------------------------------------------------------------------------------------------------
// writes x number of bytes

BOOL DRVENTRY prtput( PDDC pddc, PCHAR pch, ULONG cb )
{
  APIRET   rc;

  assert( pddc );

  rc = WriteOutput( pddc, pch, cb );
  assert( 0 == rc );

  return TRUE;
}


//-------------------------------------------------------------------------------------------------------------------------
// write a zero-delimited string

BOOL DRVENTRY prtputs( PDDC pddc, PCHAR pch )
{
  APIRET   rc;
  ULONG    cb;

  assert( pddc );
  assert( pddc->fGenOutput );

  cb = strlen( pch );

  rc = WriteOutput( pddc, pch, cb );
  assert( 0 == rc );

  return TRUE;
}

//-------------------------------------------------------------------------------------------------------------------------
// formats and writes in printf() fashion

BOOL DRVENTRY prtprintf( PDDC pddc, PSZ pszFormat, ...)
{
  APIRET   rc;
  CHAR     szTemp[250];
  LONG     cb;
  va_list  list;

  // use the va* functions to implement prtprintf with vsprintf
  va_start (list, pszFormat);
  cb = vsprintf (szTemp, pszFormat, list);
  assert( 0 <= cb  &&  cb < sizeof( szTemp ));
  va_end (list);

  assert( pddc );
  assert( pddc->fGenOutput );

  rc = WriteOutput( pddc, szTemp,  cb );
  assert( 0 == rc );

  return TRUE;
}


