/*	$Id: faxcover.c++ 823 2008-04-26 22:34:29Z faxguy $ */
/*
 * Copyright (c) 1990-1996 Sam Leffler
 * Copyright (c) 1991-1996 Silicon Graphics, Inc.
 * Copyright (c) 2018 Patrice Fournier
 * Copyright (c) 2018 iFAX Solutions, Inc.
 * HylaFAX is a trademark of Silicon Graphics
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */
#include "StrArray.h"
#include "FaxDB.h"
#include "config.h"
#include "PageSize.h"
#include "Sys.h"

#include <iostream>
#include <stdarg.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/file.h>
#ifdef HAVE_PODOFO
#include <podofo/podofo.h>
#endif

class faxCoverApp {
private:
    fxStr	appName;	// for error messages
    fxStr	cover;		// prototype cover sheet
    fxStr	dbName;		// .faxdb filename
    FaxDB*	db;		// fax machine database
    fxStr	toName;		// to person's name
    fxStr	toFaxNumber;	// to's fax number
    fxStr	toVoiceNumber;	// to's voice number
    fxStr	toLocation;	// to's geographical location
    fxStr	toCompany;	// to's company/institution
    fxStr	fromFaxNumber;	// sender's fax number
    fxStr	fromVoiceNumber;	// sender's voice number
    fxStr	fromLocation;	// sender's geographical location
    fxStr	fromCompany;	// sender's company/institution
    fxStr	fromMailAddr;	// sender's email address
    fxStr	regarding;	// fax is regarding...
    fxStr	comments;	// general comments
    fxStr	sender;		// sender's identity
    fxStr	pageCount;	// # pages, not counting cover page
    fxStr	dateFmt;	// strftime format string
    fxStr	date;		// formatted date string
    float	pageWidth;	// page width (mm)
    float	pageLength;	// page length (mm)
    int		maxcomments;	// max # of comment lines
    int		maxlencomments; // max length of comment lines

    static const char* prologue;

    fxStr tildeExpand(const fxStr& filename);
    void setupPageSize(const char* name);
    void prepareTo(const char* to, FaxDBRecord* rec);
    void prepareFrom(FaxDBRecord* rec);
    void prepareComment();
    void prepareDate();
    void emitToDefs(const char* to, FaxDBRecord* rec);
    void emitFromDefs(FaxDBRecord* rec);
    void emitCommentDefs();
    void emitDateDefs();
    void coverDef(const char* tag, const char* value);
    void makePSCoverSheet(int fd);
    void makePDFCoverSheet(const char* templateFilename);
    void makeCoverSheet();
    void usage();
    void printError(const char* va_alist ...);
public:
    faxCoverApp();
    ~faxCoverApp();

    void initialize(int argc, char** argv);
    void open();
};

faxCoverApp::faxCoverApp()
    : cover(FAX_COVER)
    , dbName("~/.faxdb")
    , dateFmt("%a %b %d %Y, %H:%M %Z")
{
    db = 0;
    maxcomments = 20;
    maxlencomments = 35;
}

faxCoverApp::~faxCoverApp()
{
}

void
faxCoverApp::initialize(int argc, char** argv)
{
    extern char* optarg;
    int c;

    appName = argv[0];
    u_int l = appName.length();
    appName = appName.tokenR(l, '/');

    const char* cp = getenv("FAXCOVER");
    if (cp && *cp)
	cover = cp;

    setupPageSize("default");
    while ((c = Sys::getopt(argc, argv, "C:D:L:M:N:V:X:n:t:f:c:p:l:m:r:s:v:x:z:")) != -1)
	switch (c) {
	case 's':			// page size
	    setupPageSize(optarg);
	    break;
	case 'C':			// cover sheet
	    cover = optarg;
	    break;
	case 'D':			// date format string
	    dateFmt = optarg;
	    break;
	case 'L':			// sender's geographic location
	    fromLocation = optarg;
	    break;
	case 'M':			// sender's email address
	    fromMailAddr = optarg;
	    break;
	case 'N':			// sender's fax number
	    fromFaxNumber = optarg;
	    break;
	case 'V':			// sender's voice number
	    fromVoiceNumber = optarg;
	    break;
	case 'X':			// sender's company/institution
	    fromCompany = optarg;
	    break;
	case 'm':			// max # comment lines
	    maxcomments = atoi(optarg);
	    break;
	case 'z':			// max length comment lines
	    maxlencomments = atoi(optarg);
	    break;
	case 'n':			// fax number
	    toFaxNumber = optarg;
	    break;
	case 'r':			// regarding
	    regarding = optarg;
	    break;
	case 't':			// to identity
	    toName = optarg;
	    break;
	case 'f':			// from identity
	    sender = optarg;
	    break;
	case 'c':			// comments
	    comments = optarg;
	    break;
	case 'p':			// page count
	    pageCount = optarg;
	    break;
	case 'l':			// to's location
	    toLocation = optarg;
	    break;
	case 'v':			// to's voice phone number
	    toVoiceNumber = optarg;
	    break;
	case 'x':			// to's company
	    toCompany = optarg;
	    break;
	case '?':
	    usage();
	}
    if (sender == "" || toFaxNumber == "")
	usage();
}

extern void fxFatal(const char* fmt, ...);

void
faxCoverApp::setupPageSize(const char* name)
{
    PageSizeInfo* info = PageSizeInfo::getPageSizeByName(name);
    if (!info)
	fxFatal("Unknown page size \"%s\"", name);
    pageWidth = info->width();
    pageLength = info->height();
    delete info;
}

void
faxCoverApp::open()
{
    if (!db)
	db = new FaxDB(tildeExpand(dbName));
    makeCoverSheet();
}

void
faxCoverApp::usage()
{
    fxFatal("usage: %s"
	" [-t to]"
	" [-c comments]"
	" [-p #pages]"
	" [-l to-location]"
	" [-m maxcomments]"
	" [-z maxlencomments]"
	" [-r regarding]"
	" [-v to-voice-number]"
	" [-x to-company]"
	" [-C template-file]"
	" [-D date-format]"
	" [-L from-location]"
	" [-M from-mail-address]"
	" [-N from-fax-number]"
	" [-V from-voice-number]"
	" [-X from-company]"
	" [-s pagesize]"
	" -f from"
	" -n fax-number"
	, (const char*) appName);
}

const char* faxCoverApp::prologue = "\
/wordbreak ( ) def\n\
/linebreak (\\n) def\n\
/doLine {\n\
% <line> <width> <height> <x> <y> doLine <width> <height> <x> <y>\n\
    2 copy moveto 5 -1 roll\n\
    wordbreak\n\
    {\n\
        search {\n\
            dup stringwidth pop currentpoint pop add 7 index 6 index add gt {\n\
                6 3 roll 2 index sub 2 copy moveto 6 3 roll\n\
            } if\n\
            show wordbreak show\n\
        }{\n\
            dup stringwidth pop currentpoint pop add 5 index 4 index add gt {\n\
                3 1 roll 3 index sub 2 copy moveto 3 -1 roll\n\
            } if\n\
            show exit\n\
        } ifelse\n\
    } loop\n\
    2 index sub 2 copy moveto\n\
} def\n\
/BreakIntoLines {\n\
% <width> <height> <x> <y> <text> BreakIntoLines\n\
    linebreak\n\
    {\n\
         search {\n\
             7 3 roll doLine 6 -2 roll\n\
         }{\n\
             5 1 roll doLine exit\n\
         } ifelse\n\
    } loop\n\
    pop pop pop pop\n\
} def\n\
/BreakIntoCommentX {\n\
% <maxlines> <text> BreakIntoCommentX -\n\
    /cbuf (Comment ) def\n\
    0 exch\n\
    linebreak { search { 4 -1 roll 1 add 4 2 roll }{ exch 1 add exit } ifelse } loop\n\
    dup dup 2 add 1 roll\n\
    -1 1 { cbuf exch 7 exch 48 add put cbuf cvn exch def } for\n\
    1 add exch 1 exch { cbuf exch 7 exch 48 add put cbuf cvn () def } for\n\
} def\n\
/XtoCommentsX {\n\
% <X> XtoCommentsX <commentsX>\n\
    3 string cvs (comments) dup length dup 4 1 roll\n\
    2 index length add string dup 0 4 -1 roll\n\
    putinterval dup 4 -2 roll putinterval\n\
} def\n\
/BreakIntoCommentsX {\n\
% <maxlines> <text> BreakIntoCommentsX -\n\
    exch 1 1 3 2 roll\n\
    { XtoCommentsX cvn () def } for\n\
    dup length string copy 0 1 index 0 4 1 roll\n\
    {   linebreak 0 get eq {\n\
            exch dup 0 3 index getinterval 4 -1 roll 1 add dup 5 1 roll\n\
            XtoCommentsX cvn exch def dup length 2 index sub 1 sub\n\
            2 index 1 add exch getinterval exch pop 0\n\
        }{ 1 add } ifelse\n\
        dup MaxLenComments gt {\n\
            exch MaxLenComments 1 sub -1 0 {\n\
                2 copy get wordbreak 0 get eq {\n\
                    mark 4 1 roll\n\
                    {   2 copy 1 add 1 index length 1 index 1 add sub\n\
                        getinterval 5 -1 roll search { 3 -2 roll pop pop } if\n\
                        length MaxLenComments gt { 4 -1 roll exec\n\
                        }{ false } ifelse\n\
                    }\n\
                    { true }\n\
                    5 1 roll linebreak 1 index wordbreak 7 3 roll exec\n\
                    counttomark 1 add 4 roll cleartomark { pop exit } if\n\
                    2 copy 1 add 0 exch getinterval 5 -1 roll\n\
                    1 add dup 6 1 roll XtoCommentsX cvn exch def\n\
                    2 copy 1 add 1 index length 1 index sub getinterval\n\
                    3 -1 roll pop 3 -2 roll 1 add sub exch exit\n\
                } if\n\
                pop\n\
            } for\n\
            exch dup MaxLenComments gt {\n\
                pop dup 0 MaxLenComments getinterval 3 -1 roll\n\
                1 add dup 4 1 roll XtoCommentsX cvn exch def\n\
                dup length MaxLenComments sub\n\
                MaxLenComments exch getinterval 1\n\
            } if\n\
        }if\n\
    } forall\n\
    pop exch 1 add XtoCommentsX cvn exch def\n\
} def\n\
";

void
faxCoverApp::makePSCoverSheet(int fd)
{
    if (fd < 0) {
	printError("Missing cover sheet template");
	return;
    }
    printf("%%!PS-Adobe-2.0 EPSF-2.0\n");
    printf("%%%%Creator: faxcover\n");
    printf("%%%%Title: HylaFAX Cover Sheet\n");
    time_t t = time(0);
    printf("%%%%CreationDate: %s", ctime(&t));
    printf("%%%%Origin: 0 0\n");
    printf("%%%%BoundingBox: 0 0 %.0f %.0f\n",
	(pageWidth/25.4)*72, (pageLength/25.4)*72);
    printf("%%%%Pages: 1 +1\n");
    printf("%%%%EndComments\n");
    printf("%%%%BeginProlog\n");
    printf("%i dict begin\n", maxcomments*2 + 80);
    printf("%s", prologue);
    emitToDefs(toName, toName != "" ? db->find(toName) : (FaxDBRecord*) NULL);
    printf("/pageWidth %.2f def\n", pageWidth);
    printf("/pageLength %.2f def\n", pageLength);
    emitFromDefs(db->find(sender));
    coverDef("page-count", pageCount);
    emitDateDefs();
    coverDef("regarding", regarding);
    emitCommentDefs();
    printf("/MaxComments %i def\n", maxcomments);
    printf("MaxComments comments BreakIntoCommentX\n");
    printf("/MaxLenComments %i def\n", maxlencomments);
    printf("MaxComments comments BreakIntoCommentsX\n");
    printf("%%%%EndProlog\n");
    printf("%%%%Page: \"1\" 1\n");
    // copy prototype cover page
    char buf[16*1024];
    int n;
    while (((n = read(fd, buf, sizeof (buf))) > 0) && (fwrite(buf, n, 1, stdout) == 1));
    printf("\nend\n");
}

void
faxCoverApp::makePDFCoverSheet(const char* templateFilename)
{
#ifdef HAVE_PODOFO
    PoDoFo::PdfMemDocument pdfTemplate(templateFilename);

//    PoDoFo::PdfInfo* info = pdfTemplate.GetInfo();
//    info->SetProducer('faxcover');
//    info->SetTitle('HylaFAX Cover Sheet');
//    pdfTemplate.SetInfo(info);

    prepareTo(toName, toName != "" ? db->find(toName) : (FaxDBRecord*) NULL);
    prepareFrom(db->find(sender));
    prepareComment();
    prepareDate();

    for (int p=0; p < pdfTemplate.GetPageCount(); p++) {
	PoDoFo::PdfPage* page = pdfTemplate.GetPage(p);
	for (int f=0; f < page->GetNumFields(); f++) {
	    PoDoFo::PdfTextField field = (PoDoFo::PdfTextField)page->GetField(f);
	    if (field.GetType() != PoDoFo::ePdfField_TextField)
		continue;
	    if (field.GetFieldName() == "to")
		field.SetText((const char *)toName);
	    else if (field.GetFieldName() == "to-company")
		field.SetText((const char *)toCompany);
	    else if (field.GetFieldName() == "to-location")
		field.SetText((const char *)toLocation);
	    else if (field.GetFieldName() == "to-voice-number")
		field.SetText((const char *)toVoiceNumber);
	    else if (field.GetFieldName() == "to-fax-number")
		field.SetText((const char *)toFaxNumber);
	    else if (field.GetFieldName() == "from")
		field.SetText((const char *)sender);
	    else if (field.GetFieldName() == "from-fax-number")
		field.SetText((const char *)fromFaxNumber);
	    else if (field.GetFieldName() == "from-voice-number")
		field.SetText((const char *)fromVoiceNumber);
	    else if (field.GetFieldName() == "from-company")
		field.SetText((const char *)fromCompany);
	    else if (field.GetFieldName() == "from-location")
		field.SetText((const char *)fromLocation);
	    else if (field.GetFieldName() == "from-mail-address")
		field.SetText((const char *)fromMailAddr);
	    else if (field.GetFieldName() == "page-count")
		field.SetText((const char *)pageCount);
	    else if (field.GetFieldName() == "todays-date")
		field.SetText((const char *)date);
	    else if (field.GetFieldName() == "regarding")
		field.SetText((const char *)regarding);
	    else if (field.GetFieldName() == "comments")
		field.SetText((const char *)comments);
	}
    }
    PoDoFo::PdfOutputDevice outStream(&std::cout);
    pdfTemplate.Write(&outStream);
#else
    printError("PDF Cover Page support not available");
    return;
#endif
}

void
faxCoverApp::makeCoverSheet()
{
    int fd;
    fxStr templateFilename;
    if (cover.length() > 0 && cover[0] != '/') {
	templateFilename = tildeExpand("~/" | cover);
	fd = Sys::open(templateFilename, O_RDONLY);
	if (fd < 0) {
	    templateFilename = fxStr(FAX_LIBDATA) | "/" | cover;
	    fd = Sys::open(fxStr(templateFilename), O_RDONLY);
	}
    } else {
	templateFilename = cover;
	fd = Sys::open(templateFilename, O_RDONLY);
    }
    if (fd < 0) {
	printError("Could not locate prototype cover sheet \"%s\"",
	    (const char*) cover);
	return;
    }
    if (templateFilename.tail(4) == ".pdf") {
	Sys::close(fd);
	makePDFCoverSheet(templateFilename);
    } else {
	makePSCoverSheet(fd);
	Sys::close(fd);
    }
}

void
faxCoverApp::prepareTo(const char* to, FaxDBRecord* rec)
{
    if (rec) {
	toName = rec->find(FaxDB::nameKey);
	if (toCompany == "")
	    toCompany = rec->find("Company");
	if (toLocation == "")
	    toLocation = rec->find("Location");
	if (toVoiceNumber == "") {
	    toVoiceNumber = rec->find("Voice-Number");
	    if (toVoiceNumber != "") {
		fxStr areaCode(rec->find("Area-Code"));
		if (areaCode != "")
		    toVoiceNumber.insert("1-" | areaCode | "-");
	    }
	}
    }
}

void
faxCoverApp::prepareFrom(FaxDBRecord* rec)
{
    if (rec) {
	fromCompany = rec->find("Company");
	fromLocation = rec->find("Location");
	fromFaxNumber = rec->find(FaxDB::numberKey);
	fromVoiceNumber = rec->find("Voice-Number");
	fromMailAddr = rec->find("Mail-Address");
	fxStr areaCode(rec->find("Area-Code"));
	if (areaCode != "") {
	    if (fromFaxNumber != "")
		fromFaxNumber.insert("1-" | areaCode | "-");
	    if (fromVoiceNumber != "")
		fromVoiceNumber.insert("1-" | areaCode | "-");
	}
    }
}

void
faxCoverApp::prepareComment()
{
    /*
     * Fix up new line characters.
     */
    u_int crlf = comments.find(0, "\\n", (u_int) 0);
    while ( crlf < comments.length()) {
        comments.remove(crlf, 2);
        comments.insert('\n', crlf);
        crlf = comments.find(0, "\\n", (u_int) 0);
    }
}

void
faxCoverApp::prepareDate()
{
    time_t t = time(0);
    char buf[128];
    strftime(buf, sizeof (buf), dateFmt, localtime(&t));
    date = buf;
}

void
faxCoverApp::emitToDefs(const char* to, FaxDBRecord* rec)
{
    prepareTo(to, rec);
    coverDef("to",		toName);
    coverDef("to-company",	toCompany);
    coverDef("to-location",	toLocation);
    coverDef("to-voice-number",	toVoiceNumber);
    coverDef("to-fax-number",	toFaxNumber);
}

void
faxCoverApp::emitFromDefs(FaxDBRecord* rec)
{
    prepareFrom(rec);
    coverDef("from",		sender);
    coverDef("from-fax-number",	fromFaxNumber);
    coverDef("from-voice-number",fromVoiceNumber);
    coverDef("from-company",	fromCompany);
    coverDef("from-location",	fromLocation);
    coverDef("from-mail-address",	fromMailAddr);
}

void
faxCoverApp::emitCommentDefs()
{
    prepareComment();
    coverDef("comments", comments);
}

void
faxCoverApp::emitDateDefs()
{
    prepareDate();
    coverDef("todays-date", date);
}

void
faxCoverApp::coverDef(const char* tag, const char* value)
{
    printf("/%s (", tag);
    for (const char* cp = value; *cp; cp++) {
	if (*cp == '(' || *cp == ')' || *cp == '\\')
	    putchar('\\');
	putchar(*cp);
    }
    printf(") def\n");
}

fxStr
faxCoverApp::tildeExpand(const fxStr& filename)
{
    fxStr path(filename);
    if (filename.length() > 1 && filename[0] == '~') {
        path.remove(0);
        struct passwd* pwd = getpwuid(getuid());
        if (!pwd) {
            fxFatal("Can not figure out who you are.");
        }
        char* cp = pwd->pw_dir;
	    path.insert(cp);
    }
    return (path);
}

void
faxCoverApp::printError(const char* va_alist ...)
#define	fmt va_alist
{
    va_list ap;
    va_start(ap, va_alist);
    fprintf(stderr, "%s: ", (const char*) appName);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, ".\n");
}
#undef fmt

int
main(int argc, char** argv)
{
    faxCoverApp app;
    app.initialize(argc, argv);
    app.open();
    return 0;
}
