/*
 * ************************************************************************************************************
 * Author: Marcin Załęczny
 * Copyright (C)2011 Marcin Załęczny <mzaleczny@hotmail.com>
 * License: The GNU Lesser General Public License, version 3.0 (LGPLv3)
 * Library Name: mzstdprint
 * Version: 1.02
 * First release date:   2011-02-03
 * Current release date: 2011-03-04
 *
 *
 * This file is part of mzstdprint library
 * 
 * mzstdprint library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * mzstdprint library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * ************************************************************************************************************
 */

#include "mzPrinter.h"
#include "mzPrinterInfo.h"
#include "mzPrinterDriverInfo.h"
#include "mzstd.h"
#include "mzBitmap.h"
#include "mzCanvas.h"
//#include <commdlg.h>

#define CPRINTER_PRINTER_CAPABILITY_PAPERBIN_ITEM_SIZE      24
#define CPRINTER_PRINTER_DEVMODE_SIZE_MULTIPLIER            2

VOID CPrinter::_commonInit(WCHAR* printerName) {
    this->setRealClassName(L"CPrinter");

    this->canvas = NULL;
    this->_hPrinter = NULL;
    this->_hdc = NULL;
    this->_devmode = NULL;
    this->_devmodeSize = 0;
    this->_devmodeApplyChanges = FALSE;
    this->_printJobId = 0;
    this->_docInfo = NULL;
    this->_docInfo1 = NULL;
    this->_printerInfo = NULL;
    this->_printTask = NULL;

    if (printerName == NULL || printerName[0] == NULL) {
        WCHAR defPrinterName[256];
        DWORD bufsize = 256;
        if (GetDefaultPrinter(defPrinterName, &bufsize)) {
            this->_name = defPrinterName;
        }
    } else {
        if (this->isPrinterInstalled(printerName)) {
            this->_name = printerName;
        }
    }

    if (this->_name != COMMON_EMPTY_STRING) {
        this->_getPrinterInfo(1, 1, 1, 1);
    }
}

CPrinter::CPrinter(WCHAR* printerName) {
    this->_commonInit(printerName);
}

CPrinter::~CPrinter() {
    if (this->isPrintingStarted()) {
        this->_endPrint();
    }

    if (this->_hPrinter) {
        this->close();
    }

    this->_cleanupDevmode();

    if (this->_printerInfo) {
        delete this->_printerInfo;
        this->_printerInfo = NULL;
    }

    this->_deleteDocInfo();
    this->_deleteDocInfo1();
}

VOID CPrinter::_deleteDocInfo() {
    if (this->_docInfo) {
        if (this->_docInfo->lpszDocName) {
            delete[] this->_docInfo->lpszDocName;
        }
        if (this->_docInfo->lpszDatatype) {
            delete[] this->_docInfo->lpszDatatype;
        }
        delete this->_docInfo;
        this->_docInfo = NULL;
    }
}

VOID CPrinter::_deleteDocInfo1() {
    if (this->_docInfo1) {
        if (this->_docInfo1->pDocName) {
            delete[] this->_docInfo1->pDocName;
        }
        if (this->_docInfo1->pDatatype) {
            delete[] this->_docInfo1->pDatatype;
        }
        delete this->_docInfo1;
        this->_docInfo1 = NULL;
    }
}
DOCINFO* CPrinter::_createDocInfo(WCHAR* docName, WCHAR* dataType) {
    DOCINFO* di = new DOCINFO;
    if (di) {
        ZeroMemory(di, sizeof(DOCINFO));
        di->cbSize = sizeof(DOCINFO);
        if (!docName) {
            docName = L"Document";
        }

        DWORD docNameSize = wcslen(docName)+1;
        DWORD dataTypeSize = 0;
        if (dataType) {
            dataTypeSize = wcslen(dataType)+1;
            di->lpszDatatype = new WCHAR[dataTypeSize];
            wcscpy_s((WCHAR*)di->lpszDatatype, dataTypeSize, dataType);
        } else {
            di->lpszDatatype = NULL;
        }
        di->lpszDocName = new WCHAR[docNameSize];
        di->lpszOutput = NULL;
        di->fwType = 0;
        wcscpy_s((WCHAR*)di->lpszDocName, docNameSize, docName);
        return di;
    }

    return NULL;
}

DOC_INFO_1* CPrinter::_createDocInfo1(WCHAR* docName, WCHAR* dataType) {
    DOC_INFO_1* di = new DOC_INFO_1;
    if (di) {
        if (!docName) {
            docName = L"Document";
        }

        DWORD docNameSize = wcslen(docName)+1;
        DWORD dataTypeSize = 0;
        if (dataType) {
            dataTypeSize = wcslen(dataType)+1;
            di->pDatatype = new WCHAR[dataTypeSize];
            wcscpy_s(di->pDatatype, dataTypeSize, dataType);
        } else {
            di->pDatatype = NULL;
        }
        di->pDocName = new WCHAR[docNameSize];
        di->pOutputFile = NULL;
        wcscpy_s(di->pDocName, docNameSize, docName);
        return di;
    }

    return NULL;
}

CObjectList* WINAPI CPrinter::_enumPrinters(DWORD flags, WCHAR* name, DWORD detailsLevel, CObjectList* listForUpdate) {
    BYTE* buffer = NULL;
    DWORD bufsize = 0, bytesNeeded = 0, count = 0;
    CObjectList* printerList = NULL;

    if (detailsLevel != 1 && detailsLevel != 2 && detailsLevel != 4 && detailsLevel != 5) {
        detailsLevel = 2;
    }
    EnumPrinters(flags, NULL, detailsLevel, buffer, bufsize, &bytesNeeded, &count);
    if (bytesNeeded > 0) {
        bufsize = bytesNeeded;
        buffer = new BYTE[bufsize];
        if (buffer) {
            if (EnumPrinters(flags, NULL, detailsLevel, buffer, bufsize, &bytesNeeded, &count)) {
                if (bufsize == bytesNeeded) {
                    CPrinterInfo* pi;
                    for (ULONG i = 0; i < count; i++) {
                        pi = NULL;
                        if (detailsLevel == 1) {
                            pi = new CPrinterInfo(&(((PRINTER_INFO_1*)buffer)[i]));
                        } else if (detailsLevel == 2) {
                            pi = new CPrinterInfo(&(((PRINTER_INFO_2*)buffer)[i]));
                        } else if (detailsLevel == 4) {
                            pi = new CPrinterInfo(&(((PRINTER_INFO_4*)buffer)[i]));
                        } else if (detailsLevel == 5) {
                            pi = new CPrinterInfo(&(((PRINTER_INFO_5*)buffer)[i]));
                        }

                        if (pi) {
                            if (pi->name != name) {
                                delete pi;
                                pi = NULL;
                            } else {
                                if (listForUpdate) {
                                    CPrinterInfo* item;
                                    for (ULONG j = 0; j < listForUpdate->size(); j++) {
                                        item = (CPrinterInfo*)listForUpdate->getAt(j);
                                        if (item->name == pi->name) {
                                            delete pi;
                                            pi = NULL;
                                            if (detailsLevel == 1) {
                                                item->set(&(((PRINTER_INFO_1*)buffer)[i]));
                                            } else if (detailsLevel == 2) {
                                                item->set(&(((PRINTER_INFO_2*)buffer)[i]));
                                            } else if (detailsLevel == 4) {
                                                item->set(&(((PRINTER_INFO_4*)buffer)[i]));
                                            } else if (detailsLevel == 5) {
                                                item->set(&(((PRINTER_INFO_5*)buffer)[i]));
                                            }
                                            break;
                                        }
                                    }
                                    if (!printerList) {
                                        printerList = listForUpdate;
                                    }
                                } else {
                                    if (!printerList) {
                                        printerList = new CObjectList();
                                    }
                                    if (printerList) {
                                        printerList->append(pi);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            delete[] buffer;
        }
    }

    return printerList;
}

CObjectList* WINAPI CPrinter::getInstalledLocalPrinters(BOOL details1, BOOL details2, BOOL details4, BOOL details5) {
    CObjectList* printerList = NULL;

    if (details1) {
        printerList = CPrinter::_enumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, printerList);
    }
    if (details2) {
        printerList = CPrinter::_enumPrinters(PRINTER_ENUM_LOCAL, NULL, 2, printerList);
    }
    if (details4) {
        printerList = CPrinter::_enumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, printerList);
    }
    if (details5) {
        printerList = CPrinter::_enumPrinters(PRINTER_ENUM_LOCAL, NULL, 5, printerList);
    }

    return printerList;
}

VOID CPrinter::_getPrinterInfo(BOOL details1, BOOL details2, BOOL details4, BOOL details5) {
    if (this->_name != COMMON_EMPTY_STRING) {
        BOOL opened = FALSE;
        if (!this->_hPrinter) {
            this->open();
            opened = TRUE;
        }
        if (!this->_hPrinter) {
            return;
        }

        if (details1) {
            this->_getPrinterInfo(1);
        }
        if (details2) {
            this->_getPrinterInfo(2);
        }
        if (details4) {
            this->_getPrinterInfo(4);
        }
        if (details5) {
            this->_getPrinterInfo(5);
        }

        if (opened) {
            this->close();
        }
    }
}

VOID CPrinter::_getPrinterInfo(DWORD detailsLevel) {
    if (this->_name != COMMON_EMPTY_STRING) {
        BOOL opened = FALSE;
        if (!this->_hPrinter) {
            this->open();
            opened = TRUE;
        }
        if (!this->_hPrinter) {
            return;
        }
        if (!this->_printerInfo) {
            this->_printerInfo = new CPrinterInfo();
        }
        if (!this->_printerInfo) {
            if (opened) {
                this->close();
            }
            return;
        }

        BYTE* buffer = NULL;
        DWORD bufsize = 0, bytesNeeded = 0;
        if (detailsLevel != 1 && detailsLevel != 2 && detailsLevel != 4 && detailsLevel != 5) {
            detailsLevel = 2;
        }
        GetPrinter(this->_hPrinter, detailsLevel, buffer, bufsize, &bytesNeeded);
        if (bytesNeeded > 0) {
            bufsize = bytesNeeded;
            buffer = new BYTE[bufsize];
            if (buffer) {
                if (GetPrinter(this->_hPrinter, detailsLevel, buffer, bufsize, &bytesNeeded)) {
                    if (bufsize == bytesNeeded) {
                        if (detailsLevel == 1) {
                            this->_printerInfo->set((PRINTER_INFO_1*)buffer);
                        } else if (detailsLevel == 2) {
                            this->_printerInfo->set((PRINTER_INFO_2*)buffer);
                        } else if (detailsLevel == 4) {
                            this->_printerInfo->set((PRINTER_INFO_4*)buffer);
                        } else if (detailsLevel == 5) {
                            this->_printerInfo->set((PRINTER_INFO_5*)buffer);
                        }
                    }
                }
                delete[] buffer;
            }
        }

        if (opened) {
            this->close();
        }
    }
}

BOOL CPrinter::isPrinterInstalled(WCHAR* name) {
    BOOL result = FALSE;
    CObjectList* printers = this->getInstalledLocalPrinters();
    if (printers) {
        CPrinterInfo* item;
        for (ULONG i = 0; i < printers->size(); i++) {
            item = dynamic_cast<CPrinterInfo*>(printers->getAt(i));
            if (item) {
                if (item->name == name) {
                    result = TRUE;
                    break;
                }
            }
        }
        delete printers;
    }

    return result;
}

BOOL CPrinter::getDriverInfo(CPrinterDriverInfo* pdi) {
    BOOL result = FALSE;

    if (this->_hPrinter) {
        pdi->reset();

        BYTE* buf = NULL;
        DWORD bufsize = 0;
        DWORD bufsizeNeeded = 0;

        BOOL gpdResult = GetPrinterDriver(this->_hPrinter, NULL, 6, buf, bufsize, &bufsizeNeeded);
        if (!gpdResult) {
            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
                buf = new BYTE[bufsizeNeeded];
                if (buf) {
                    bufsize = bufsizeNeeded;
                    result = GetPrinterDriver(this->_hPrinter, NULL, 6, buf, bufsize, &bufsizeNeeded);
                    if (result) {
                        pdi->set((DRIVER_INFO_6*)buf);
                        result = TRUE;
                    }
                    delete[] buf;
                }
            }
        }
    }

    return result;
}

BOOL CPrinter::open() {
    return OpenPrinter(this->_name.toWCHAR(), &this->_hPrinter, NULL);
}

BOOL CPrinter::close() {
    if (this->_hPrinter) {
        BOOL result = ClosePrinter(this->_hPrinter);
        if (result) {
            this->_hPrinter = NULL;
        }
        return result;
    }
    return FALSE;
}

// TODO: Do function below to run from a thread, because of:
// DocumentProperties is blocking function.
BOOL CPrinter::showPrinterPropertiesDialogBox(HWND hwnd, BOOL applyDevMode, LONG* clickedButton) {
    BOOL result = FALSE;

    if (this->_hPrinter) {
        BYTE* buf;
        DWORD bufsize;

        LONG dpResult = DocumentProperties(hwnd, this->_hPrinter, this->_name.toWCHAR(), NULL, NULL, 0);
        if (dpResult > 0) {
            bufsize = dpResult * CPRINTER_PRINTER_DEVMODE_SIZE_MULTIPLIER;
            buf = new BYTE[bufsize];
            BOOL deleteBuf = TRUE;

            if (buf) {
                ZeroMemory(buf, bufsize);
                PDEVMODE outDevmode = (PDEVMODE)buf;
                PDEVMODE inDevmode = NULL;
                DWORD mode = DM_OUT_BUFFER | DM_IN_PROMPT;
                if (applyDevMode) {
                    if (!this->_devmode) {
                        this->setDefaultDevmode();
                    }
                    if (this->_devmode) {
                        if (IsValidDevmode((PDEVMODE)this->_devmode, this->_devmodeSize)) {
                            inDevmode = (PDEVMODE)this->_devmode;
                            mode |= DM_IN_BUFFER;
                        } else {
                            ErrorBox(L"Invalid devmode");
                        }
                    }
                }

                dpResult = DocumentProperties(hwnd, this->_hPrinter, this->_name.toWCHAR(), outDevmode, inDevmode, mode);
                if (dpResult >= 0) {
                    result = TRUE;
                    if (clickedButton) {
                        *clickedButton = dpResult;
                    }
                    if (applyDevMode && dpResult == IDOK) {
                        this->_cleanupDevmode();
                        this->_devmode = buf;
                        this->_devmodeSize = bufsize;
                        deleteBuf = FALSE;
                    }
                }

                if (deleteBuf) {
                    delete[] buf;
                }
            }
        }
    } else {
        ErrorBox(CString(L"Printer [") + this->_name + L"] not opened");
    }

    return result;
}

BOOL CPrinter::showCompactedPrinterPropertiesDialogBox(HWND hwnd) {
    BOOL result = FALSE;

    if (this->_hPrinter) {
        result = PrinterProperties(hwnd, this->_hPrinter);
    }
    return result;
}

BOOL CPrinter::setDefaultDevmode() {
    BOOL result = FALSE;

    this->_cleanupDevmode();

    BOOL closePrinter = FALSE;
    if (!this->_hPrinter) {
        if (this->open()) {
            closePrinter = TRUE;
        }
    }

    if (this->_hPrinter) {
        BYTE* buf;
        DWORD bufsize;

        LONG dpResult = DocumentProperties(NULL, this->_hPrinter, this->_name.toWCHAR(), NULL, NULL, 0);
        if (dpResult > 0) {
            bufsize = dpResult * CPRINTER_PRINTER_DEVMODE_SIZE_MULTIPLIER;
            buf = new BYTE[bufsize];
            if (buf) {
                ZeroMemory(buf, bufsize);
                PDEVMODE outDevmode = (PDEVMODE)buf;
                DWORD mode = DM_OUT_BUFFER;
                dpResult = DocumentProperties(NULL, this->_hPrinter, this->_name.toWCHAR(), outDevmode, NULL, mode);
                if (dpResult >= 0) {
                    this->_devmode = buf;
                    this->_devmodeSize = bufsize;
                    result = TRUE;
                }
            }
        } else {
            ShowLastSystemError();
        }
    }

    if (closePrinter) {
        this->close();
    }

    return result;
}

BOOL CPrinter::showPrintDialogBox(HWND hwnd) {
    PRINTDLGEX pPDX;
    HRESULT hResult;
    
    pPDX.lStructSize = sizeof(PRINTDLGEX);
    pPDX.hwndOwner = hwnd;
    pPDX.hDevMode = NULL;
    pPDX.hDevNames = NULL;
    pPDX.hDC = NULL;
    pPDX.Flags = PD_RETURNDC | PD_COLLATE;
    pPDX.Flags2 = 0;
    pPDX.ExclusionFlags = 0;
    pPDX.nPageRanges = 0;
    pPDX.nMaxPageRanges = 10;
    pPDX.lpPageRanges = NULL;
    pPDX.nMinPage = 1;
    pPDX.nMaxPage = 1000;
    pPDX.nCopies = 1;
    pPDX.hInstance = 0;
    pPDX.lpPrintTemplateName = NULL;
    pPDX.lpCallback = NULL;
    pPDX.nPropertyPages = 0;
    pPDX.lphPropertyPages = NULL;
    pPDX.nStartPage = START_PAGE_GENERAL;
    pPDX.dwResultAction = 0;
    hResult = PrintDlgEx(&pPDX);
    if ((hResult == S_OK) && pPDX.dwResultAction == PD_RESULT_PRINT) {
        // User clicked the Print button, so
        // use the DC and other information returned in the
        // PRINTDLGEX structure to print the document
    }
    if (pPDX.hDC != NULL) {
        DeleteDC(pPDX.hDC);
    }
    if (pPDX.hDevMode != NULL) {
        GlobalFree(pPDX.hDevMode);
    }
    if (pPDX.hDevNames != NULL) {
        GlobalFree(pPDX.hDevNames);
    }

    return hResult == S_OK;

    return TRUE;
}

CStringList* CPrinter::getPaperBins() {
    if (this->_name != COMMON_EMPTY_STRING) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }

        if (this->_devmode) {
            WCHAR* paperbins = NULL;
            DWORD result = DeviceCapabilities(this->_name.toWCHAR(), NULL, DC_BINNAMES, paperbins, (PDEVMODE)this->_devmode);
            if (result > 0) {
                DWORD itemSize = CPRINTER_PRINTER_CAPABILITY_PAPERBIN_ITEM_SIZE;
                paperbins = new WCHAR[result*itemSize]; // each item is 24-characters long
                if (paperbins) {
                    ZeroMemory(paperbins, result*itemSize*2);
                    if (paperbins) {
                        result = DeviceCapabilities(this->_name.toWCHAR(), NULL, DC_BINNAMES, paperbins, (PDEVMODE)this->_devmode);
                        if (result) {
                            CStringList* lst = new CStringList(result);
                            ULONG offset;
                            WCHAR* item;
                            WCHAR pb[CPRINTER_PRINTER_CAPABILITY_PAPERBIN_ITEM_SIZE+1];
                            BOOL isNull;
                            for (ULONG i = 0; i < result; i++) {
                                offset = i * itemSize;
                                item = &paperbins[offset];
                                isNull = FALSE;
                                for (LONG j = 0; j < itemSize; j++) {
                                    if (item[j] == NULL) {
                                        isNull = TRUE;
                                        break;
                                    }
                                }
                                if (!isNull) {
                                    CopyMemory(pb, item, itemSize*2);
                                    pb[CPRINTER_PRINTER_CAPABILITY_PAPERBIN_ITEM_SIZE] = NULL;
                                    lst->append(CString(&pb[0]));
                                } else {
                                    lst->append(CString(item));
                                }
                            }

                            return lst;
                        }
                    }
                    delete[] paperbins;
                }
            }
        }
    }

    return NULL;
}

CString CPrinter::_paperSourceWordToString(WORD papersource) {
    switch(papersource) {
        case DMBIN_AUTO:
            return L"DMBIN_AUTO";
        case DMBIN_CASSETTE:
            return L"DMBIN_CASSETTE";
        case DMBIN_ENVELOPE:
            return L"DMBIN_ENVELOPE";
        case DMBIN_ENVMANUAL:
            return L"DMBIN_ENVMANUACString(L";
        case DMBIN_FIRST:
        //case DMBIN_UPPER:
        //case DMBIN_ONLYONE:
            return L"DMBIN_FIRST (DMBIN_UPPER, DMBIN_ONLYONE)";
        case DMBIN_LARGECAPACITY:
            return L"DMBIN_LARGECAPACITY";
        case DMBIN_LARGEFMT:
            return L"DMBIN_LARGEFMT";
        case DMBIN_LAST:
            return L"DMBIN_LAST (DMBIN_FORMSOURCE)";
        case DMBIN_LOWER:
            return L"DMBIN_LOWER";
        case DMBIN_MANUAL:
            return L"DMBIN_MANUACString(L";
        case DMBIN_MIDDLE:
            return L"DMBIN_MIDDLE";
        case DMBIN_TRACTOR:
            return L"DMBIN_TRACTOR";
        case DMBIN_SMALLFMT:
            return L"DMBIN_SMALLFMT";
    }

    return L"INVALID";
}

CStringList* CPrinter::getPaperSources() {
    if (this->_name != COMMON_EMPTY_STRING) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }

        if (this->_devmode) {
            WORD* paperbinsources = NULL;
            DWORD result = DeviceCapabilities(this->_name.toWCHAR(), NULL, DC_BINS, (LPWSTR)paperbinsources, (PDEVMODE)this->_devmode);
            if (result > 0) {
                paperbinsources = new WORD[result];
                if (paperbinsources) {
                    ZeroMemory(paperbinsources, result*sizeof(WORD));
                    if (paperbinsources) {
                        result = DeviceCapabilities(this->_name.toWCHAR(), NULL, DC_BINS, (LPWSTR)paperbinsources, (PDEVMODE)this->_devmode);
                        if (result) {
                            CStringList* lst = new CStringList(result);
                            ULONG offset;
                            for (ULONG i = 0; i < result; i++) {
                                lst->append(this->_paperSourceWordToString(paperbinsources[i]));
                            }

                            return lst;
                        }
                    }
                    delete[] paperbinsources;
                }
            }
        }
    }

    return NULL;
}

HDC CPrinter::getDC() {
    BOOL result = FALSE;

    if (this->_name.length() == 0) {
        return NULL;
    }

    if (!this->_hdc) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }

        if (this->_devmode) {
            this->_hdc = CreateDC(L"WINSPOOL", this->_name.toWCHAR(), NULL, (PDEVMODE)this->_devmode);
            if (this->_hdc) {
                if (!(GetDeviceCaps(this->_hdc, RASTERCAPS) & RC_STRETCHDIB)) {
                    DeleteDC(this->_hdc);
                    ErrorBox("Printer does not support StretchDIBits.");
                    this->_hdc = NULL;
                }
            }
        }
    }

    return this->_hdc;
}

VOID CPrinter::deleteDC() {
    if (this->canvas) {
        delete this->canvas;
        this->canvas = NULL;
    }

    if (this->_hdc) {
        DeleteDC(this->_hdc);
        this->_hdc = NULL;
    }
}

BOOL CPrinter::_beginPrint(WCHAR* documentName) {
    BOOL result = FALSE;

    if (this->_name.length() == 0) {
        return result;
    }

    if (!this->_hPrinter){
        if (!this->open()) {
            return result;
        }
    }

    this->getDC();
    if (!this->_hdc) {
        return result;
    }

    if (this->_devmode) {
        if (!this->canvas) {
            SIZE hdcSize = this->getPageSize();
            if (hdcSize.cx > 0 && hdcSize.cy > 0) {
                this->canvas = new CCanvas(this->_hdc, hdcSize.cx, hdcSize.cy, RGB(0xff, 0xff, 0xff), TRUE);
                if (this->canvas) {
                    this->_docInfo = this->_createDocInfo(documentName, NULL);
                    if (this->_docInfo) {
                        //if (SetAbortProc(this->_hdc, CPrinter::AbortProc) == SP_ERROR) {
                        //    ShowLastSystemError();
                        //}
                        this->_printJobId = StartDoc(this->_hdc, this->_docInfo);
                        if (this->_printJobId > 0) {
                            result = TRUE;
                        } else {
                            ShowLastSystemError();
                        }
                    }
                    if (!result) {
                        this->deleteDC();
                    }
                }
            }
        }
        if (!this->canvas) {
            this->deleteDC();
        }
    }

    return result;
}

BOOL CPrinter::_beginRawPrint(WCHAR* documentName) {
    BOOL result = FALSE;

    if (this->_name.length() == 0) {
        return result;
    }

    if (!this->_hPrinter){
        if (!this->open()) {
            return result;
        }
    }

    this->getDC();
    if (!this->_hdc) {
        return result;
    }

    if (this->_devmode) {
        this->_docInfo1 = this->_createDocInfo1(L"Document", L"RAW");
        if (this->_docInfo1) {
            this->_printJobId = StartDocPrinter(this->_hPrinter, 1, (BYTE*)this->_docInfo1);
            if (this->_printJobId) {
                result = TRUE;
            } else {
                ShowLastSystemError();
            }
        }
        if (!result) {
            this->deleteDC();
        }
    }

    return result;
}

BOOL CPrinter::_endPrint() {
    BOOL result = FALSE;
    if (this->_name.length() == 0) {
        return result;
    }

    if (this->_printJobId && this->_hPrinter && this->_hdc) {
        EndDoc(this->_hdc);
        this->_deleteDocInfo();
        this->_printJobId = 0;
    }

    this->deleteDC();

    if (this->canvas == NULL && this->_hdc == NULL) {
        result = TRUE;
    }

    return result;
}

BOOL CPrinter::_endRawPrint() {
    BOOL result = FALSE;
    if (this->_name.length() == 0) {
        return result;
    }

    if (this->_printJobId && this->_hPrinter && this->_hdc) {
        EndDocPrinter(this->_hPrinter);
        this->_deleteDocInfo1();
        this->_printJobId = 0;
    }

    this->deleteDC();
    return result;
}

BOOL CPrinter::print(CPrintTask* printTask) {
    BOOL result = FALSE;

    if (this->_hPrinter && printTask) {
        if (!this->getDevmode()) {
            this->setDefaultDevmode();
        }
        CString docName;
        if (printTask->getDocumentName() != COMMON_EMPTY_STRING) {
            docName = printTask->getDocumentName();
        }

        if (this->_beginPrint(docName.toWCHAR())) {
            printTask->setPrinterProperties(this->_name.toWCHAR(), this->_hPrinter, this->_devmode,
                                            this->_devmodeSize, this->_hdc,  this->_printJobId,
                                            this->_docInfo);
            this->_printTask = printTask;
            BOOL pagesPrinted = 0;
            LONG startPage = 1;
            LONG pageCount = this->_printTask->getPageCount();
            COLORREF white = RGB(0xff, 0xff, 0xff);
            BOOL pagePrinted;
            BOOL winapiPageStarted;

            for (LONG pageNumber = startPage; pageNumber <= pageCount; pageNumber++) {
                if (StartPage(this->_hdc)) {
                    winapiPageStarted = TRUE;
                    // clear print canvas
                    pagePrinted = printTask->printPage(pageNumber, this->canvas);
                    if (pagePrinted) {
                        if (EndPage(this->_hdc)) {
                            winapiPageStarted = FALSE;
                            result = TRUE;
                        }
                    }
                }
            }

            if (winapiPageStarted) {
                if (EndPage(this->_hdc)) {
                    winapiPageStarted = FALSE;
                    result = TRUE;
                }
            }

            this->_endPrint();
        }
    }

    return result;
}

BOOL CPrinter::printFile(CString fname, WCHAR* documentName) {
    if (!this->_hPrinter || !FileExists(fname)) {
        return FALSE;
    }

    if (this->_beginRawPrint(documentName)) {
        if (StartPagePrinter(this->_hPrinter)) {
            DWORD fsize = GetFileSize(fname);
            HANDLE fp = CreateFile(fname.getBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
            if (fp == INVALID_HANDLE_VALUE) {
                return FALSE;
            }

            DWORD read, written;
            DWORD fileBytesRead = 0;
            BYTE buf[1024];

            while (fileBytesRead < fsize) {
                if (ReadFile(fp, buf, 1024, &read, NULL)) {
                    if (read > 0) {
                        fileBytesRead += read;
                        WritePrinter(this->_hPrinter, buf, read, &written);
                    } else {
                        break;
                    }
                }
            }
            CloseHandle(fp);
            EndPagePrinter(this->_hPrinter);
        }
        this->_endRawPrint();
        return TRUE;
    }
}

SIZE CPrinter::getPixelsPerInch() {
    SIZE size;

    if (!this->_hdc) {
        size.cx = 0;
        size.cy = 0;
    } else {
        size.cx = GetDeviceCaps(this->_hdc, LOGPIXELSX);
        size.cy = GetDeviceCaps(this->_hdc, LOGPIXELSY);
    }

    return size;
}

SIZE CPrinter::getPageSize(WCHAR* unit) {
    SIZE size;

    if (!this->_hdc) {
        size.cx = 0;
        size.cy = 0;
    } else {
        size.cx = GetDeviceCaps(this->_hdc, HORZRES);
        size.cy = GetDeviceCaps(this->_hdc, VERTRES);
        if (unit) {
            if (wcscmp(unit, L"in") == 0) {
                SIZE pxPerInch = this->getPixelsPerInch();
                size.cx = (LONG)((float)size.cx / pxPerInch.cx);
                size.cy = (LONG)((float)size.cy / pxPerInch.cy);
                return size;
            }
            if (wcscmp(unit, L"mm") == 0) {
                SIZE pxPerInch = this->getPixelsPerInch();
                // 1 inch = 2.54 cm == 25.4 mm
                size.cx = (LONG)((float)size.cx / pxPerInch.cx * 25.4);
                size.cy = (LONG)((float)size.cy / pxPerInch.cy * 25.4);
                return size;
            }
            if (wcscmp(unit, L"cm") == 0) {
                SIZE pxPerInch = this->getPixelsPerInch();
                // 1 inch = 2.54 cm == 25.4 mm
                size.cx = (LONG)((float)size.cx / pxPerInch.cx * 2.54);
                size.cy = (LONG)((float)size.cy / pxPerInch.cy * 2.54);
                return size;
            }
        }
    }

    return size;
}

CString CPrinter::getOrientation() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_ORIENTATION) {
        if (dm->dmOrientation == DMORIENT_PORTRAIT) {
            return L"portrait";
        }
        if (dm->dmOrientation == DMORIENT_LANDSCAPE) {
            return L"landscape";
        }
    }

    return L"invalid";
}

VOID CPrinter::setOrientation(SHORT value) {
    if (value == DMORIENT_PORTRAIT || value == DMORIENT_LANDSCAPE) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_ORIENTATION;
        dm->dmOrientation = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

CString CPrinter::getPaperSize() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_PAPERSIZE) {
        switch (dm->dmPaperSize) {
            case DMPAPER_A4:
                return L"A4 [210mm x 297mm]";
            case DMPAPER_A5:
                return L"A5 [148mm x 210mm]";
            case DMPAPER_A6:
                return L"A6 [105mm x 148mm]";
            case DMPAPER_B4:
                return L"B4 [250mm x 354mm]";
            case DMPAPER_B5:
                return L"B5 [182mm x 257mm]";
        }
    }

    return L"INVALID";
}

VOID CPrinter::setPaperSize(SHORT value) {
    if (value == DMPAPER_A4 || value == DMPAPER_A5 || value == DMPAPER_A6 || value == DMPAPER_B4 || value == DMPAPER_B5) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_PAPERSIZE;
        dm->dmPaperSize = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

VOID CPrinter::setPaperSize(WCHAR* value) {    
    SHORT sValue;
    BOOL applyChanges = FALSE;
    
    if (!applyChanges && wcscmp(value, L"A4") == 0) {
        sValue = DMPAPER_A4;
        applyChanges = TRUE;
    }
    if (!applyChanges && wcscmp(value, L"A5") == 0) {
        sValue = DMPAPER_A5;
        applyChanges = TRUE;
    }
    if (!applyChanges && wcscmp(value, L"A6") == 0) {
        sValue = DMPAPER_A6;
        applyChanges = TRUE;
    }
    if (!applyChanges && wcscmp(value, L"B4") == 0) {
        sValue = DMPAPER_B4;
        applyChanges = TRUE;
    }
    if (!applyChanges && wcscmp(value, L"B5") == 0) {
        sValue = DMPAPER_B5;
        applyChanges = TRUE;
    }
    
    if (applyChanges) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_PAPERSIZE;
        dm->dmPaperSize = sValue;
        this->_devmodeApplyChanges = TRUE;
    }
}

LONG CPrinter::getPaperLength() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_PAPERLENGTH) {
        return dm->dmPaperLength;
    }

    return 0;
}

VOID CPrinter::setPaperLength(SHORT value) {
    if (value > 0) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_PAPERLENGTH;
        dm->dmPaperLength = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

LONG CPrinter::getPaperWidth() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_PAPERWIDTH) {
        return dm->dmPaperWidth;
    }

    return 0;
}

VOID CPrinter::setPaperWidth(SHORT value) {
    if (value > 0) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_PAPERWIDTH;
        dm->dmPaperWidth = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

LONG CPrinter::getScale() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_SCALE) {
        return dm->dmScale;
    }

    return 0;
}

VOID CPrinter::setScale(SHORT value) {
    if (value > 0) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_SCALE;
        dm->dmScale = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

LONG CPrinter::getCopies() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_COPIES) {
        return dm->dmCopies;
    }

    return 0;
}

VOID CPrinter::setCopies(SHORT value) {
    if (value > 0) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_COPIES;
        dm->dmCopies = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

CString CPrinter::getDefaultSource() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_DEFAULTSOURCE) {
        return this->_paperSourceWordToString(dm->dmDefaultSource);
    }

    return L"INVALID";
}

VOID CPrinter::setDefaultSource(SHORT value) {
    if (value == DMBIN_AUTO || value == DMBIN_CASSETTE || value == DMBIN_ENVELOPE || value == DMBIN_ENVMANUAL ||
        value == DMBIN_FIRST || value == DMBIN_FORMSOURCE || value == DMBIN_LARGECAPACITY || value == DMBIN_LARGEFMT ||
        value == DMBIN_LAST || value == DMBIN_LOWER || value == DMBIN_MANUAL || value == DMBIN_MIDDLE ||
        value == DMBIN_ONLYONE || value == DMBIN_TRACTOR || value == DMBIN_SMALLFMT || value == DMBIN_UPPER ||
        value >= DMBIN_USER) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_DEFAULTSOURCE;
        dm->dmDefaultSource = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

CString CPrinter::getPrintQuality() {
    if (!this->_devmode) {
        this->setDefaultDevmode();
    }

    PDEVMODE dm = (PDEVMODE)this->_devmode;
    if (dm->dmFields & DM_PRINTQUALITY) {
        if (dm->dmPrintQuality < 0) {
            switch (dm->dmPrintQuality) {
                case DMRES_HIGH:
                    return L"high";
                case DMRES_MEDIUM:
                    return L"medium";
                case DMRES_LOW:
                    return L"low";
                case DMRES_DRAFT:
                    return L"draft";
            }
        } else {
            return FormatMessage(L"DPI: %dx%d", dm->dmPrintQuality, dm->dmYResolution);
        }
    }

    return L"INVALID";
}

VOID CPrinter::setPrintQuality(SHORT value) {
    if (value == DMRES_HIGH || value == DMRES_MEDIUM || value == DMRES_LOW || value == DMRES_DRAFT || value > 0) {
        if (!this->_devmode) {
            this->setDefaultDevmode();
        }
        PDEVMODE dm = (PDEVMODE)this->_devmode;
        dm->dmFields |= DM_PRINTQUALITY;
        dm->dmPrintQuality = value;
        this->_devmodeApplyChanges = TRUE;
    }
}

VOID CPrinter::showInformation() {
    SIZE logSize = this->getPixelsPerInch();
    SIZE pageSize = this->getPageSize();
    SIZE pageSizeIN = this->getPageSize(L"in");
    SIZE pageSizeCM = this->getPageSize(L"cm");
    SIZE pageSizeMM = this->getPageSize(L"mm");
    CString s = FormatMessage(L"Printer name: %s: beginPrint OK\n"
                              L"   pixelsPerInch [X: %ud, Y: %ud]\n"
                              L"   pageSize (%udx%ud px)\n"
                              L"   pageSize (%udx%ud in)\n"
                              L"   pageSize (%udx%ud cm)\n"
                              L"   pageSize (%udx%ud mm)\n"
                              L"   Properties:\n"
                              L"       Orientation: %s\n"
                              L"       PaperSize: %s\n"
                              L"       PaperLength: %d\n"
                              L"       PaperScale: %d\n"
                              L"       PaperCopies: %d\n"
                              L"       DefaultSource: %s\n"
                              L"       PrintQuality: %s\n",
                                this->_name.toWCHAR(),
                                logSize.cx, logSize.cy,
                                pageSize.cx, pageSize.cy,
                                pageSizeIN.cx, pageSizeIN.cy,
                                pageSizeCM.cx, pageSizeCM.cy,
                                pageSizeMM.cx, pageSizeMM.cy,
                                this->getOrientation().toWCHAR(),
                                this->getPaperSize().toWCHAR(),
                                this->getPaperLength(),
                                this->getScale(),
                                this->getCopies(),
                                this->getDefaultSource().toWCHAR(),
                                this->getPrintQuality().toWCHAR()
                             );
    InformationBox(s);
}

CString CPrinter::getPortName() {
    if (this->_printerInfo) {
        return this->_printerInfo->portName;
    }
    return COMMON_EMPTY_STRING;
}

BOOL CPrinter::canPrintBitmaps() {
    BOOL opened = FALSE;
    if (!this->_hPrinter) {
        this->open();
        opened = TRUE;
    }
    if (!this->_hPrinter) {
        return FALSE;
    }

    BOOL dcCreated = FALSE;
    if (!this->_hdc) {
        this->getDC();
        dcCreated = TRUE;
    }

    BOOL result = FALSE;
    if ((GetDeviceCaps(this->_hdc, RASTERCAPS) & RC_STRETCHDIB) == RC_STRETCHDIB) {
        result = TRUE;
    }

    if (dcCreated) {
        this->deleteDC();
    }

    if (opened) {
        this->close();
    }

    return result;
}

VOID CPrinter::_cleanupDevmode() {
    if (this->_devmode) {
        delete[] this->_devmode;
        this->_devmode = NULL;
        this->_devmodeSize = 0;
        this->_devmodeApplyChanges = FALSE;
    }
}

// Should return FALSE if printing should be aborted.
BOOL CALLBACK CPrinter::AbortProc(HDC hdc, int nCode) {
    InformationBox(L"AbortProc called");
    return TRUE;
}
