/*
//
//   ADOBE SYSTEMS INCORPORATED
//   Copyright (C) 2000-2003 Adobe Systems Incorporated
//   All rights reserved.
//
//   NOTICE: Adobe permits you to use, modify, and distribute this file
//   in accordance with the terms of the Adobe license agreement
//   accompanying it. If you have received this file from a source other
//   than Adobe, then your use, modification, or distribution of it
//   requires the prior written permission of Adobe.
//
// Project: - In-memory file system used in a multithreaded workflow.
// 
// Demonstrates usage of in-memory file system for a simple workflow 
// in a multithreaded context.
//
// The sample shows multiple threads creating PDF files simultaneously. The 
// individual PDF files are then merged into one and written to a disk file
// by the main thread.
// 
// PDFL Init and Term must be performed for each thread
// 
// Steps:
// 
// MainProc:
// * Initialize PDF Library
// * Read in text from an ASCII file as the source for PDF file creation
// * Spawn a few threads and pass each a chunk of text for one-page PDF creation
// * When all threads are done with creation of PDF files, merge them into one and write to disk
// * Terminate PDF Library
//
// ThreadProc:
// * Initialize PDF Library
// * Create a new doc with one-page content from the text passed in
// * Terminate PDF Library
// 
// Note:
// Use of the input file "input.txt" in the "ExampleFiles" folder of this sample is required.
//
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sstream>

#ifdef WIN32
#include <direct.h>
#include <process.h>
#endif

#include "PDFInit.h"
#include "PERCalls.h"
#include "PEWCalls.h"
#include "PSFCalls.h"
#include "PagePDECntCalls.h"
#include "MyPDFLibUtils.h"
#include "Threads.h"

#ifdef MAC_PLATFORM
#include "macUtils.h"
#endif

const   int   sizeThreadPool  = 3;                      // maximum number of simultaneous worker threads 
const   int   numJobs  = 10;                                                  // number of jobs for worker threads
const   int   fontSize  = 12;                                                // point size of font used for content text
const   float   indentation  = 0.75;                  // used to roughly control word spacing
const   int   charsPerLine  = 55;                        // make sure words don't run over page margin
const   int   maxLineSize  = 3072;                      // text buffer size per line read from input file

// data structure stroing per-job information for use by worker threads
typedef   struct  {
                unsigned  JobID;
                char  text[maxLineSize];
}  JobData;

// forward declaration of functions
void   MainProc(  void  );
ThreadFuncReturnType   ThreadProc(ThreadFuncArgType  threadParams );
void   CreatePDFProc(  JobData*  JobData  );
void   MergeDocsProc(  JobData*  JobData  );
void   AddText(PDEContent  pdeContent,  char * text,  ASSize_t  x,  ASSize_t  y);

char   inputFile[256],  outputPath[256];

void   MainProc(  void  ) 
{
                JobData   JobData[numJobs];
                ThreadHandle  threadHandles[sizeThreadPool] = {0};
                ThreadHandle  retVal;
               FILE *sourceFile =  NULL;
                int  taskID;
#ifdef WIN32
               DWORD startTime, elapsedTime;
#else
               time_t startTime, elapsedTime;
#endif

                // Open up the source text file
                if  ((sourceFile = fopen(inputFile,  "r" )) ==  NULL)
               {
                               printf( "Failed to open source file %s.\n" ,  inputFile);
                                return ;
               }

                // Read in text from source file into buffer to be passed to threads as content text
                for  ( int  i=0; i<numJobs; i++) 
               {
                               JobData[i].JobID  = i;
                                if  (fgets(JobData[i].text,  maxLineSize, sourceFile) ==  NULL
                               {
                                                if  (feof(sourceFile) != 0)
                                                               printf( "End-Of-File reached.\n" );
                                                else  {
                                                               printf( "[fgets]:Error reading from sourec file.\n" );
                                                                return ;
                                               }
                               }
               }

#ifdef WIN32
               startTime = GetTickCount();
#else
               time(&startTime);
#endif

               taskID = 0;

               srand(( unsigned )time(NULL)*1000);

                // limit number of active worker threads
                for  ( int  j=0; j<numJobs; j++)
               {
                                // wait for a thread to complete if the threads in the pool are used up
                                if  (j >=  sizeThreadPool)
                               {
                                                int  myId;

                                               myId = rand()%3;

                                                if (waitThread(threadHandles[myId]))
                                                                destroyThread(threadHandles[myId]);
                                                                                               
                                               taskID = myId;
                                               printf( "Thread%d terminated.\n" , taskID);
                               }

                                // Spawn sizeThreadPool number of threads, passing each the chunk of text to create PDF docs with
                                if (createThread(ThreadProc, ( void *) &JobData[j], &retVal))
                                               printf( "Thread%d created successfully.\n" , taskID);
                                if (taskID <  sizeThreadPool)
                                               threadHandles[taskID] = retVal;
                               taskID++;
               }

               printf(  "\nWaiting on threads to terminate..\n"  );

                int  m;
                for  (m=0; m<sizeThreadPool; m++)
               {
                                if  (waitThread(threadHandles[m]))
                                                destroyThread(threadHandles[m]);
                                else  
                                                break ;
               }

                if  (m == sizeThreadPool)
                               printf( "\nAll threads terminated successfully.\n" );

               fclose(sourceFile);

                // use memory-based temporary file
                ASFileSys  memFileSys =  ASGetRamFileSys();
                ASSetTempFileSys(memFileSys);

                // merge individual PDFs
                MergeDocsProc(JobData);

#ifdef WIN32
               elapsedTime = GetTickCount() - startTime;
               printf( "\nWorkflow turnaround time: %d.%03d seconds\n\n" , elapsedTime/1000, elapsedTime%1000);
#else
               elapsedTime = time(NULL) - startTime;
               printf( "\nWorkflow turnaround time: %ld seconds\n\n" , elapsedTime);
#endif
}


ThreadFuncReturnType   ThreadProc(  ThreadFuncArgType  threadParams )
{
                INIT_AUTO_POOL(autoReleasePool);                /* Required only on MAC platform */
               
               JobData* threadInfo;
                int  err;

                if ( threadParams ==  NULL  )
#ifdef WIN32
                                return  1;
#else
                                return   NULL;
#endif

               threadInfo = (JobData*) threadParams;

               err =  MyPDFLInit();                          // initialize the PDFLib
                if  (err != 0)                                      // check for error after initialization
               {
                               fprintf(stderr,  "Initialization error. See \"AcroErr.h\" for more info.\n" );
                               fprintf(stderr,  "Error system: %d\nError Severity: %d\nError Code: %d\n" ,
                                                                                                ErrGetSystem(err),  ErrGetSeverity(err),  ErrGetCode(err));
#ifdef WIN32
                                return  1;
#else
                                return   NULL;
#endif
               }

                // Use in-memory file system for temporary files
                ASFileSys  memFileSys =  ASGetRamFileSys();
                ASSetTempFileSys(memFileSys);

                CreatePDFProc( threadInfo );

                MyPDFLTerm();

                RELEASE_AUTO_POOL(autoReleasePool);          /* Required only on MAC platform */
               
                return  0;
}

void   CreatePDFProc( JobData* JobData )
{
                ASFixedRect  mediaBox;                      // dimensions of page
                PDDoc  pdDoc;                                                        // reference to a PDF document
                PDPage  pdPage;                                                    // reference to a page in doc
                PDEContent  pdeContent;                    // container for page content
                char  * str;                                                                          // reference to each word read
                char  buf[256] =  "" ;
                char  outFile[256] =  "" ;                  // output file name
                char * strPtr =  NULL;
                ASSize_t  charCount, xPos, yPos;  // character count per line and location to add text on page
                PDDocSaveParamsRec  saveParams;
               memset(&saveParams, 0,  sizeof (PDDocSaveParamsRec));

DURING

               pdDoc =  PDDocCreate();                                        // create new document

               mediaBox.left      =  fixedZero;                            // dimensions of page
               mediaBox.top        =  Int16ToFixed(11*72);        // letter-size
               mediaBox.right    =  Int16ToFixed(8.5*72);
               mediaBox.bottom  =  fixedZero;

                // create page with those dimensions and insert as first page 
               pdPage =  PDDocCreatePage(pdDoc,  PDBeforeFirstPage, mediaBox);

                // Acquire PDEContent container for page
               pdeContent =  PDPageAcquirePDEContent(pdPage,  NULL);         

               strPtr = JobData->text;
               charCount = xPos = yPos = 0;

               std::stringstream stream(strPtr);
               std::string token;
               
                // Read one word at a time to add to the content object
                while  (stream>>token)
               {
                               str = ( char  *) token.c_str();

                                AddText(pdeContent, str, xPos, yPos );

                                // estimate text str position on a page
                               strPtr += strlen(str) + 1;
                               charCount += strlen(str) + 1;

                                // add roughly charsPerLine characters on a line
                                if  (charCount >=  charsPerLine)
                               {
                                               charCount = 0;
                                               xPos = 0;
                                               yPos++;
                               }
                                else
                                               xPos += strlen(str) + 1;

               }

                // set the PDEContent for the page
                PDPageSetPDEContent(pdPage,  NULL);

                // save document to a file
                sprintf_safe( outFile,  sizeof (outFile),  "%sout_%d.pdf" ,  outputPath, JobData->JobID  );
               printf(  "Job %d: Saving PDDoc as %s\n" , JobData->JobID, outFile );
               
               saveParams.size  =  sizeof (PDDocSaveParamsRec);
               saveParams.saveFlags  =  PDSaveFull  |  PDSaveCollectGarbage;
#ifdef MAC_PLATFORM
               saveParams.newPath  =  GetMacPath(outFile);
#else
               saveParams.newPath  =  ASFileSysCreatePathName  (ASGetDefaultFileSys(), 
                                                                                                ASAtomFromString( "Cstring" ), outFile,  NULL);
#endif

               saveParams.fileSys  =  ASGetDefaultFileSys();

                PDDocSaveWithParams(pdDoc, &saveParams);
               
HANDLER
                ASGetErrorString(ERRORCODE, buf,  sizeof (buf));
               fprintf(stderr,  "Error code: %d, Error Message: %s\n" ,  ERRORCODE, buf);
END_HANDLER

                // remember to release all objects that were created
                if  (pdPage)
               {
                                PDPageReleasePDEContent(pdPage,  NULL);
                                PDPageRelease(pdPage);
               }
                if  (pdDoc)            PDDocClose(pdDoc);
                if  (saveParams.newPathASFileSysReleasePath(saveParams.fileSys, saveParams.newPath);
}


void   MergeDocsProc(JobData* JobData)
{
                char  buf[256], inFile[128], outFile[128];
                PDDoc  inPDDoc, outPDDoc;
                PDDocOpenParamsRec  openParams;
                PDDocSaveParamsRec  saveParams;
               memset(&openParams, 0,  sizeof (PDDocOpenParamsRec));
               memset(&saveParams, 0,  sizeof (PDDocSaveParamsRec));

               outPDDoc =  PDDocCreate();

DURING

                // Insert first page of each document into the output document
                for  ( int  i=0; i<numJobs; i++)
               {
                                sprintf_safe(inFile,  sizeof (inFile),  "%sout_%d.pdf" ,  outputPath, JobData[i].JobID);

                               memset(&openParams, 0,  sizeof (PDDocOpenParamsRec));
                               openParams.size  =  sizeof (PDDocOpenParamsRec);
#ifdef MAC_PLATFORM
                               openParams.fileName  =  GetMacPath(inFile);
#else
                               openParams.fileName  =  ASFileSysCreatePathName  (ASGetDefaultFileSys(), 
                                                                                                ASAtomFromString( "Cstring" ), inFile,  NULL);
#endif

                               openParams.fileSys  =  ASGetDefaultFileSys();
                               inPDDoc =  PDDocOpenWithParams(&openParams);

                                PDDocInsertPages(outPDDoc, 
                                                                                                PDLastPage
                                                                                               inPDDoc, 0, 1, 
                                                                                                PDInsertAll
                                                                                               0, 0, 0, 0
                                                                                               );
                                PDDocClose(inPDDoc);
                                ASFileSysReleasePath(openParams.fileSys, openParams.fileName);
                               inPDDoc =  NULL;
                               openParams.fileName  =  NULL;
               }
               
                // save the merged document
               saveParams.size  =  sizeof (PDDocSaveParamsRec);
               saveParams.saveFlags  =  PDSaveFull  |  PDSaveCollectGarbage;
                sprintf_safe(outFile,  sizeof (outFile),  "%sMerged.pdf" ,  outputPath);
               
               printf( "Output file=%s\n" , outFile);
#ifdef MAC_PLATFORM
               saveParams.newPath  =  GetMacPath(outFile);
#else
               saveParams.newPath  =  ASFileSysCreatePathName  (ASGetDefaultFileSys(), 
                                                                                                ASAtomFromString( "Cstring" ), outFile,  NULL);
#endif
               saveParams.fileSys  =  ASGetDefaultFileSys();

                PDDocSaveWithParams(outPDDoc, &saveParams);
               
HANDLER
                ASGetErrorString(ERRORCODE, buf,  sizeof (buf));
               fprintf(stderr,  "Error code: %d, Error Message: %s\n" ,  ERRORCODE, buf);
END_HANDLER

                if  (outPDDoc)      PDDocClose(outPDDoc);
                if  (saveParams.newPathASFileSysReleasePath(saveParams.fileSys, saveParams.newPath);
                if  (inPDDoc)        PDDocClose(inPDDoc);
                if  (openParams.fileName)                ASFileSysReleasePath(openParams.fileSys, openParams.fileName);
}

void   AddText(PDEContent  pdeContent,  char * text,  ASSize_t  x,  ASSize_t  y)
{
                PDEFont  pdeFont;                                                                // reference to a font used on a page
                PDEFontAttrs  pdeFontAttrs;                            // font attributes 
                PDEText  pdeText;                                                                // container for text
                ASDoubleMatrix  textMatrix;                            // transformation matrix for text
                PDEColorSpace  pdeColorSpace;        // ColorSpace
                PDEGraphicState  gState;                                  // graphic state to apply to operation

                //
        //   Acquire Font, Add Text, Insert Into Page Content Container 
        //

               memset(&pdeFontAttrs, 0,  sizeof (pdeFontAttrs));
               pdeFontAttrs.name  =  ASAtomFromString( "CourierStd" );
               pdeFontAttrs.type  =  ASAtomFromString( "Type1" );

                PDSysFont  sysFont;
               sysFont =  PDFindSysFont(&pdeFontAttrs,  sizeof (PDEFontAttrs), 0);
                PDSysFontGetAttrs(sysFont, &pdeFontAttrs,  sizeof (pdeFontAttrs));
               pdeFont =  PDEFontCreateFromSysFont(sysFont,  kPDEFontDoNotEmbed);

                //
                // The following code sets up the default Graphics state.   
                // We do this so that we can free the PDEColorSpace objects
                //
               pdeColorSpace =  PDEColorSpaceCreateFromName(ASAtomFromString( "DeviceGray" ));
               memset(&gState, 0,  sizeof (PDEGraphicState));
               gState.strokeColorSpec.space  = gState.fillColorSpec.space  = pdeColorSpace;
               gState.miterLimit  =  fixedTen;
               gState.flatness  =  fixedOne;
               gState.lineWidth  =  fixedOne;

               memset(&textMatrix, 0,  sizeof (textMatrix));          // clear structure
               textMatrix.a  =  fontSize;                                                                                // set font width and height
               textMatrix.d  =  fontSize;                                                                                // to 12 point size
               textMatrix.h  =  indentation*72 + x*8;                        // x,y coordinate on page
               textMatrix.v  = 11*72 -  indentation*72 - y*15;     
               pdeText =  PDETextCreate();                            // create new text run
                PDETextAddEx(pdeText,                                                      // text container to add to
                                kPDETextRun,                                                        // kPDETextRun, kPDETextChar
                               0,                                                                                                            // index
                               (Uns8  *)text,                                                      // text to add
                               strlen(text),                                                      // length of text
                               pdeFont,                                                                                // font to apply to text
                               &gState,  sizeof (gState),                // graphic state to apply to text
                                NULL, 0,                                                                                // text state and size of structure
                               &textMatrix,                                                        // transformation matrix for text
                                NULL);                                                                                    // stroke matrix

                // insert text into page content
                PDEContentAddElem(pdeContent,  kPDEAfterLast, (PDEElement) pdeText);

                PDERelease((PDEObject) pdeColorSpace);
                PDERelease((PDEObject) pdeFont);
                PDERelease((PDEObject) pdeText);
}

int   main( int  argc,  char  *argv[],  char  *envp[])
{
                if  (argc != 3) 
               {
                               printf( "Usage: %s InputFile OutputPath\n" , argv[0]);
                               printf( "Use absolute paths for input file and output path.\n" );
                               printf( "Use of the input file \"input.txt\" in the \"ExampleFiles\" folder of this sample is required.\n" );
                                return  0;
               }

                strcpy_safe(inputFile,  sizeof (inputFile), argv[1]);
                strcpy_safe(outputPath,  sizeof (outputPath), argv[2]);

#ifdef WIN32
                //An issue was found out on Windows 
        //that an output path of "someoutpath/" ( as user input ) becomes someoutpath".
        //So, we'll check if outputPath is ended with a ", and correct it if so.
                ASSize_t  l=strlen(outputPath);
                if (outputPath[l-1]== '\"' ) 
                                outputPath[l-1]= '\\' ;

                // add a / to the end of outputPath if there is not one. 
                if (outputPath[l-1]!= '\\'  &&  outputPath[l-1]!= '/'  ) {
                outputPath[l]= '/' ;
                outputPath[l+1]=NULL;
               }
#endif
               
                INIT_AUTO_POOL(autoReleasePool);                /* Required only on MAC platform */

                int  err =  MyPDFLInit();  /* initialize the PDFLib */
                if  (err != 0)                                      /* check for error after initialization */
               {
                               fprintf(stderr,  "Initialization error. See \"AcroErr.h\" for more info.\n" );
                               fprintf(stderr,  "Error system: %d\nError Severity: %d\nError Code: %d\n" ,
                                                                                                ErrGetSystem(err),  ErrGetSeverity(err),  ErrGetCode(err));
               }
                else
               {
                                MainProc();
                                MyPDFLTerm();      /* terminate the PDFLib */
               }

                RELEASE_AUTO_POOL(autoReleasePool);          /* Required only on MAC platform */
                return  0;
}