/************************************************************************************* 
This file is a part of CrashRpt library.

Copyright (c) 2003, Michael Carruth
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.

* Neither the name of the author nor the names of its contributors 
may be used to endorse or promote products derived from this software without 
specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***************************************************************************************/

#include "stdafx.h"
#include "FilePreviewCtrl.h"
#include "png.h"
#include "jpeglib.h"
#include "strconv.h"

#pragma warning(disable:4611)
// DIBSIZE calculates the number of bytes required by an image

#define WIDTHBYTES(bits) ((DWORD)(((bits)+31) & (~31)) / 8)
#define DIBWIDTHBYTES(bi) (DWORD)WIDTHBYTES((DWORD)(bi).biWidth * (DWORD)(bi).biBitCount)
#define _DIBSIZE(bi) (DIBWIDTHBYTES(bi) * (DWORD)(bi).biHeight)
#define DIBSIZE(bi) ((bi).biHeight < 0 ? (-1)*(_DIBSIZE(bi)) : _DIBSIZE(bi))

//-----------------------------------------------------------------------------
// CFileMemoryMapping implementation
//-----------------------------------------------------------------------------

CFileMemoryMapping::CFileMemoryMapping()  
{
    // Set member vars to the default values
    m_hFile = INVALID_HANDLE_VALUE;
    m_uFileLength = 0;
    m_hFileMapping = NULL;  

    SYSTEM_INFO si;  
    GetSystemInfo(&si);
    m_dwAllocGranularity = si.dwAllocationGranularity;  
}

CFileMemoryMapping::~CFileMemoryMapping()
{
    Destroy();
}


BOOL CFileMemoryMapping::Init(LPCTSTR szFileName)
{
    if(m_hFile!=INVALID_HANDLE_VALUE)
    {
        // If a file mapping already created, destroy it
        Destroy();    
    }

    // Open file handle
    m_hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    if(m_hFile == INVALID_HANDLE_VALUE)
        return FALSE;

    // Create file mapping
    m_hFileMapping = CreateFileMapping(m_hFile, 0, PAGE_READONLY, 0, 0, 0);
    LARGE_INTEGER size;
    GetFileSizeEx(m_hFile, &size);	
    m_uFileLength = size.QuadPart; 

    return TRUE;
}

BOOL CFileMemoryMapping::Destroy()
{
    // Unmap all views
    std::map<DWORD, LPBYTE>::iterator it;
    for(it=m_aViewStartPtrs.begin(); it!=m_aViewStartPtrs.end(); it++)
    {
        if(it->second != NULL)
            UnmapViewOfFile(it->second);    
    }
    m_aViewStartPtrs.clear();

    // Close file mapping handle
    if(m_hFileMapping!=NULL)
    {
        CloseHandle(m_hFileMapping);    
    }

    // Close file handle
    if(m_hFile!=INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hFile);    
    }

    m_hFileMapping = NULL;
    m_hFile = INVALID_HANDLE_VALUE;		
    m_uFileLength = 0;  

    return TRUE;
}

ULONG64 CFileMemoryMapping::GetSize()
{
    return m_uFileLength;
}

LPBYTE CFileMemoryMapping::CreateView(DWORD dwOffset, DWORD dwLength)
{
    DWORD dwThreadId = GetCurrentThreadId();
    DWORD dwBaseOffs = dwOffset-dwOffset%m_dwAllocGranularity;
    DWORD dwDiff = dwOffset-dwBaseOffs;
    LPBYTE pPtr = NULL;

    CAutoLock lock(&m_csLock);

    std::map<DWORD, LPBYTE>::iterator it = m_aViewStartPtrs.find(dwThreadId);
    if(it!=m_aViewStartPtrs.end())
    {
        UnmapViewOfFile(it->second);
    }

    pPtr = (LPBYTE)MapViewOfFile(m_hFileMapping, FILE_MAP_READ, 0, dwBaseOffs, dwLength+dwDiff);
    if(it!=m_aViewStartPtrs.end())
    {
        it->second = pPtr;
    }
    else
    {
        m_aViewStartPtrs[dwThreadId] = pPtr;
    }

    return (pPtr+dwDiff);
}

CImage::CImage()
{
    m_hBitmap = NULL;
    m_hPalette = NULL;
    m_bLoadCancelled = FALSE;
}

CImage::~CImage()
{
    Destroy();
}

BOOL CImage::IsBitmap(FILE* f)
{
    rewind(f);

    BITMAPFILEHEADER bfh;
    size_t n = fread(&bfh, sizeof(bfh), 1, f);
    if(n!=1)
        return FALSE;

    if(memcmp(&bfh.bfType, "BM", 2)!=0)
        return FALSE;

    return TRUE;  
}

BOOL CImage::IsPNG(FILE* f)
{
    png_byte header[8];
    rewind(f);

    fread(header, 1, 8, f);
    if(png_sig_cmp(header, 0, 8))  
        return FALSE;

    return TRUE;
}

BOOL CImage::IsJPEG(FILE* f)
{
    rewind(f);

    // Read first two bytes (SOI marker).
    // Each JPEG file begins with SOI marker (0xD8FF).

    WORD wSOIMarker;
    int n = (int)fread(&wSOIMarker, 1, 2, f);
    if(n!=2)
        return FALSE;

    if(wSOIMarker==0xD8FF)
        return TRUE; // This is a JPEG file

    return FALSE;
}

BOOL CImage::IsImageFile(CString sFileName)
{
    FILE* f = NULL;
    _TFOPEN_S(f, sFileName.GetBuffer(0), _T("rb"));
    if(f==NULL)
        return FALSE;

    if(IsBitmap(f) || IsPNG(f) || IsJPEG(f))
    {
        fclose(f);
        return TRUE;
    }

    fclose(f);
    return FALSE;
}

void CImage::Destroy()
{
    CAutoLock lock(&m_csLock);

    if(m_hBitmap)
    {
        DeleteObject(m_hBitmap);
        m_hBitmap = NULL;
    }

    if(m_hPalette)
    {
        DeleteObject(m_hPalette);
        m_hPalette = NULL;
    }

    m_bLoadCancelled = FALSE;
}

BOOL CImage::IsValid()
{
    CAutoLock lock(&m_csLock);
    return m_hBitmap!=NULL;
}

BOOL CImage::Load(CString sFileName)
{
    Destroy();

    FILE* f = NULL;
    _TFOPEN_S(f, sFileName.GetBuffer(0), _T("rb"));
    if(f==NULL)
        return FALSE;
    if(IsBitmap(f))
    {
        fclose(f);
        return LoadBitmapFromBMPFile(sFileName.GetBuffer(0));
    }
    else if(IsPNG(f))
    {
        fclose(f);
        return LoadBitmapFromPNGFile(sFileName.GetBuffer(0));
    }
    else if(IsJPEG(f))
    {
        fclose(f);
        return LoadBitmapFromJPEGFile(sFileName.GetBuffer(0));
    }

    fclose(f);
    return FALSE;  
}

void CImage::Cancel()
{
    CAutoLock lock(&m_csLock);
    m_bLoadCancelled = TRUE;
}

// The following code was taken from http://support.microsoft.com/kb/158898
BOOL CImage::LoadBitmapFromBMPFile(LPTSTR szFileName)
{
    CAutoLock lock(&m_csLock);

    BITMAP  bm;

    // Use LoadImage() to get the image loaded into a DIBSection
    m_hBitmap = (HBITMAP)LoadImage( NULL, szFileName, IMAGE_BITMAP, 0, 0,
        LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE );
    if( m_hBitmap == NULL )
        return FALSE;

    // Get the color depth of the DIBSection
    GetObject(m_hBitmap, sizeof(BITMAP), &bm );
    // If the DIBSection is 256 color or less, it has a color table
    if( ( bm.bmBitsPixel * bm.bmPlanes ) <= 8 )
    {
        HDC           hMemDC = NULL;
        HBITMAP       hOldBitmap = NULL;
        RGBQUAD       rgb[256];
        LPLOGPALETTE  pLogPal = NULL;
        WORD          i = 0;

        // Create a memory DC and select the DIBSection into it
        hMemDC = CreateCompatibleDC( NULL );
        hOldBitmap = (HBITMAP)SelectObject( hMemDC, m_hBitmap );

        // Get the DIBSection's color table
        GetDIBColorTable( hMemDC, 0, 256, rgb );

        // Create a palette from the color tabl
        pLogPal = (LOGPALETTE *)malloc( sizeof(LOGPALETTE) + (256*sizeof(PALETTEENTRY)) );
        pLogPal->palVersion = 0x300;
        pLogPal->palNumEntries = 256;

        for(i=0;i<256;i++)
        {
            pLogPal->palPalEntry[i].peRed = rgb[i].rgbRed;
            pLogPal->palPalEntry[i].peGreen = rgb[i].rgbGreen;
            pLogPal->palPalEntry[i].peBlue = rgb[i].rgbBlue;
            pLogPal->palPalEntry[i].peFlags = 0;
        }

        m_hPalette = CreatePalette( pLogPal );

        // Clean up
        free( pLogPal );
        SelectObject( hMemDC, hOldBitmap );
        DeleteDC( hMemDC );
    }
    else   // It has no color table, so use a halftone palette
    {
        HDC    hRefDC = NULL;

        hRefDC = GetDC( NULL );
        m_hPalette = CreateHalftonePalette( hRefDC );
        ReleaseDC( NULL, hRefDC );
    }
    return TRUE;
}

BOOL CImage::LoadBitmapFromPNGFile(LPTSTR szFileName)
{
    BOOL bStatus = FALSE;

    FILE *fp = NULL;
    const int number = 8;
    png_byte header[number];
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    png_infop end_info = NULL;
    png_uint_32 rowbytes = 0;
    png_uint_32 width = 0;
    png_uint_32 height = 0;
    png_bytep row = NULL;
    int y = 0;
    BITMAPINFO* pBMI = NULL;
    HDC hDC = NULL;

    _TFOPEN_S(fp, szFileName, _T("rb"));
    if (!fp)
    {
        return FALSE;
    }

    fread(header, 1, number, fp);
    if(png_sig_cmp(header, 0, number))  
    {
        goto cleanup;
    }  

    png_ptr = png_create_read_struct
        (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr)
        goto cleanup;

    if (setjmp(png_ptr->jmpbuf)) 
        goto cleanup;

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
        goto cleanup;

    end_info = png_create_info_struct(png_ptr);
    if (!end_info)
        goto cleanup;

    png_init_io(png_ptr, fp);
    png_set_sig_bytes(png_ptr, number);

    // Read PNG information
    png_read_info(png_ptr, info_ptr);

    // Get count of bytes per row
    rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    row = new png_byte[rowbytes];

    width = png_get_image_width(png_ptr, info_ptr);
    height = png_get_image_height(png_ptr, info_ptr);

    if(info_ptr->channels==3)
    {
        png_set_strip_16(png_ptr);
        png_set_packing(png_ptr); 
        png_set_bgr(png_ptr);
    }

    hDC = GetDC(NULL);

    {
        CAutoLock lock(&m_csLock);
        if(m_bLoadCancelled)
            goto cleanup;
        m_hBitmap = CreateCompatibleBitmap(hDC, width, height);  
    }

    pBMI = (BITMAPINFO*)new BYTE[sizeof(BITMAPINFO)+256*4];
    memset(pBMI, 0, sizeof(BITMAPINFO)+256*4);  
    pBMI->bmiHeader.biSize = sizeof(BITMAPINFO);
    pBMI->bmiHeader.biBitCount = 8*info_ptr->channels;
    pBMI->bmiHeader.biWidth = width;
    pBMI->bmiHeader.biHeight = height;
    pBMI->bmiHeader.biPlanes = 1;
    pBMI->bmiHeader.biCompression = BI_RGB;
    pBMI->bmiHeader.biSizeImage = rowbytes*height;

    if( info_ptr->channels == 1 )
    {
        RGBQUAD* palette = pBMI->bmiColors;

        int i;
        for( i = 0; i < 256; i++ )
        {
            palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
            palette[i].rgbReserved = 0;
        }

        palette[256].rgbBlue = palette[256].rgbGreen = palette[256].rgbRed = 255;
    }

    for(y=height-1; y>=0; y--)
    {
        png_read_rows(png_ptr, &row, png_bytepp_NULL, 1); 

        {
            CAutoLock lock(&m_csLock);
            int n = SetDIBits(hDC, m_hBitmap, y, 1, row, pBMI, DIB_RGB_COLORS);
            if(n==0)
                goto cleanup;
        }
    }

    /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */
    png_read_end(png_ptr, info_ptr);

    bStatus = TRUE;

cleanup:

    if(fp!=NULL)
    {
        fclose(fp);
    }

    if(png_ptr)
    {
        png_destroy_read_struct(&png_ptr,
            (png_infopp)&info_ptr, (png_infopp)&end_info);
    }

    if(row)
    {
        delete [] row;
    }

    if(pBMI)
    {
        delete [] pBMI;
    }

    if(!bStatus)
    {
        Destroy();
    }

    return bStatus;
}

BOOL CImage::LoadBitmapFromJPEGFile(LPTSTR szFileName)
{
    BOOL bStatus = false;
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE* fp = NULL;  
    JSAMPROW row = NULL;
    BITMAPINFO bmi;
    HDC hDC = NULL;

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);

    _TFOPEN_S(fp, szFileName, _T("rb"));
    if (!fp)
    {
        goto cleanup;
    }

    jpeg_stdio_src(&cinfo, fp);

    jpeg_read_header(&cinfo, TRUE);

    jpeg_start_decompress(&cinfo);

    row = new BYTE[cinfo.output_width*3+10];

    hDC = GetDC(NULL);

    {
        CAutoLock lock(&m_csLock);
        if(m_bLoadCancelled)
            goto cleanup;
        m_hBitmap = CreateCompatibleBitmap(hDC, cinfo.output_width, cinfo.output_height);  
    }

    memset(&bmi, 0, sizeof(bmi));  
    bmi.bmiHeader.biSize = sizeof(bmi);
    bmi.bmiHeader.biBitCount = 24;
    bmi.bmiHeader.biWidth = cinfo.output_width;
    bmi.bmiHeader.biHeight = cinfo.output_height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = cinfo.output_width*cinfo.output_height*3;

    while (cinfo.output_scanline < cinfo.output_height)
    {    
        jpeg_read_scanlines(&cinfo, &row, 1); 

        if(cinfo.out_color_components==3)
        {
            // Convert RGB to BGR
            UINT i;
            for(i=0; i<cinfo.output_width; i++)
            {
                BYTE tmp = row[i*3+0];
                row[i*3+0] = row[i*3+2];
                row[i*3+2] = tmp;
            }
        }
        else
        {
            // Convert grayscale to BGR
            int i;
            for(i=cinfo.output_width-1; i>=0; i--)
            {
                row[i*3+0] = row[i];
                row[i*3+1] = row[i];
                row[i*3+2] = row[i];
            }
        }

        {
            CAutoLock lock(&m_csLock);
            int n = SetDIBits(hDC, m_hBitmap, cinfo.output_height-cinfo.output_scanline, 1, row, &bmi, DIB_RGB_COLORS);
            if(n==0)
                goto cleanup;
        }
    }

    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);

    bStatus = true;

cleanup:

    if(row)
        delete [] row;

    if(!bStatus)
    {
        Destroy();
    }

    return bStatus;
}

void CImage::Draw(HDC hDC, LPRECT prcDraw)
{
    CAutoLock lock(&m_csLock);
    HPALETTE hOldPalette = NULL;

    CRect rcDraw = prcDraw;
    BITMAP        bm;
    GetObject( m_hBitmap, sizeof(BITMAP), &bm );

    HDC hMemDC = CreateCompatibleDC( hDC );
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap );
    if(m_hPalette)
    {
        hOldPalette = SelectPalette(hDC, m_hPalette, FALSE );
        RealizePalette( hDC );
    }

    if((float)rcDraw.Width()/(float)bm.bmWidth <
        (float)rcDraw.Height()/(float)bm.bmHeight)
    {    
        int nDstMid = rcDraw.top + rcDraw.Height()/2;
        int nDstHeight = (int)( rcDraw.Width()*(float)bm.bmHeight/bm.bmWidth ); 
        rcDraw.top = nDstMid - nDstHeight/2; 
        rcDraw.bottom = nDstMid + nDstHeight/2; 
    }
    else
    {
        int nDstMid = rcDraw.left + rcDraw.Width()/2;
        int nDstWidth = (int)( rcDraw.Height()*(float)bm.bmWidth/bm.bmHeight ); 
        rcDraw.left = nDstMid - nDstWidth/2; 
        rcDraw.right = nDstMid + nDstWidth/2; 
    }

    int nOldMode = SetStretchBltMode(hDC, HALFTONE);
    StretchBlt(hDC, rcDraw.left, rcDraw.top, rcDraw.Width(), rcDraw.Height(), 
        hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY );

    SetStretchBltMode(hDC, nOldMode);
    SelectObject( hMemDC, hOldBitmap );  
    if(m_hPalette)
    {
        SelectPalette( hDC, hOldPalette, FALSE ); 
    }
}


//-----------------------------------------------------------------------------
// CFilePreviewCtrl implementation
//-----------------------------------------------------------------------------

CFilePreviewCtrl::CFilePreviewCtrl()
{   
    m_xChar = 10;
    m_yChar = 10;
    m_nHScrollPos = 0;
    m_nHScrollMax = 0;
    m_nMaxColsPerPage = 0;
    m_nMaxLinesPerPage = 0;
    m_nMaxDisplayWidth = 0;   
    m_uNumLines = 0;
    m_nVScrollPos = 0;
    m_nVScrollMax = 0;
    m_nBytesPerLine = 16; 
    m_cchTabLength = 4;
    m_sEmptyMsg = _T("No data to display");
    m_hFont = CreateFont(14, 7, 0, 0, 0, 0, 0, 0, ANSI_CHARSET, 0, 0, 
        ANTIALIASED_QUALITY, FIXED_PITCH, _T("Courier"));

    m_hWorkerThread = NULL;
    m_bCancelled = FALSE;
    m_PreviewMode = PREVIEW_HEX;
    m_TextEncoding = ENC_ASCII;
    m_nEncSignatureLen = 0;
}

CFilePreviewCtrl::~CFilePreviewCtrl()
{
    DeleteObject(m_hFont);  
}

LPCTSTR CFilePreviewCtrl::GetFile()
{
    if(m_sFileName.IsEmpty())
        return NULL;
    return m_sFileName;
}

BOOL CFilePreviewCtrl::SetFile(LPCTSTR szFileName, PreviewMode mode, TextEncoding enc)
{
    // If we are currently processing some file in background,
    // stop the worker thread
    if(m_hWorkerThread!=NULL)
    {
        m_bCancelled = TRUE;
        m_bmp.Cancel();
        WaitForSingleObject(m_hWorkerThread, INFINITE);
        m_hWorkerThread = NULL;
    }

    CAutoLock lock(&m_csLock);

    m_sFileName = szFileName;

    if(mode==PREVIEW_AUTO)
        m_PreviewMode = DetectPreviewMode(m_sFileName);
    else
        m_PreviewMode = mode;

    if(szFileName==NULL)
    {
        m_fm.Destroy();
    }
    else
    {
        if(!m_fm.Init(m_sFileName))
        {
            m_sFileName.Empty();
            return FALSE;
        }
    }

    CRect rcClient;
    GetClientRect(&rcClient);

    HDC hDC = GetDC();
    HFONT hOldFont = (HFONT)SelectObject(hDC, m_hFont);

    LOGFONT lf;
    ZeroMemory(&lf, sizeof(LOGFONT));
    GetObject(m_hFont, sizeof(LOGFONT), &lf);			
    m_xChar = lf.lfWidth;
    m_yChar = lf.lfHeight;

    SelectObject(hDC, hOldFont);

    m_nVScrollPos = 0; 
    m_nVScrollMax = 0;
    m_nHScrollPos = 0;
    m_nHScrollMax = 0;
    m_aTextLines.clear();
    m_uNumLines = 0;
    m_nMaxDisplayWidth = 0;
    m_bmp.Destroy();

    if(m_PreviewMode==PREVIEW_HEX)
    {
        if(m_fm.GetSize()!=0)
        {
            m_nMaxDisplayWidth = 
                8 +				//adress
                2 +				//padding
                m_nBytesPerLine * 3 +	//hex column
                1 +				//padding
                m_nBytesPerLine;	//ascii column
        }

        m_uNumLines = m_fm.GetSize() / m_nBytesPerLine;

        if(m_fm.GetSize() % m_nBytesPerLine)
            m_uNumLines++;
    }
    else if(m_PreviewMode==PREVIEW_TEXT)
    {       
        if(enc==ENC_AUTO)
            m_TextEncoding = DetectTextEncoding(m_sFileName, m_nEncSignatureLen);
        else
        {
            m_TextEncoding = enc;
            // Determine the length of the signature.
            int nSignatureLen = 0;
            TextEncoding enc2 = DetectTextEncoding(m_sFileName, nSignatureLen);
            if(enc==enc2)
                m_nEncSignatureLen = nSignatureLen;
        }

        m_bCancelled = FALSE;
        m_hWorkerThread = CreateThread(NULL, 0, WorkerThread, this, 0, NULL);
        ::SetTimer(m_hWnd, 0, 250, NULL);
    }
    else if(m_PreviewMode==PREVIEW_IMAGE)
    {
        m_bCancelled = FALSE;
        m_hWorkerThread = CreateThread(NULL, 0, WorkerThread, this, 0, NULL);    
        ::SetTimer(m_hWnd, 0, 250, NULL);
    }

    SetupScrollbars();
    InvalidateRect(NULL, FALSE);
    UpdateWindow();

    return TRUE;
}

PreviewMode CFilePreviewCtrl::GetPreviewMode()
{
    return m_PreviewMode;
}

void CFilePreviewCtrl::SetPreviewMode(PreviewMode mode)
{
    SetFile(m_sFileName, mode);
}

TextEncoding CFilePreviewCtrl::GetTextEncoding()
{
    return m_TextEncoding;
}

void CFilePreviewCtrl::SetTextEncoding(TextEncoding enc)
{
    SetFile(m_sFileName, m_PreviewMode, enc);
}

PreviewMode CFilePreviewCtrl::DetectPreviewMode(LPCTSTR szFileName)
{
    PreviewMode mode = PREVIEW_HEX;
    CString sFileName;
    std::set<CString>::iterator it;
    std::set<CString> aTextFileExtensions;
    CString sExtension;

    sFileName = szFileName;

    if(CImage::IsImageFile(sFileName))
    {
        mode = PREVIEW_IMAGE;
        goto cleanup;
    }

    int backslash_pos = sFileName.ReverseFind('\\');
    if(backslash_pos>=0)
        sFileName = sFileName.Mid(backslash_pos+1);  
    int dot_pos = sFileName.ReverseFind('.');
    if(dot_pos>0)
        sExtension = sFileName.Mid(dot_pos+1);
    sExtension.MakeUpper();

    aTextFileExtensions.insert(_T("TXT"));
    aTextFileExtensions.insert(_T("INI"));
    aTextFileExtensions.insert(_T("LOG"));
    aTextFileExtensions.insert(_T("XML"));
    aTextFileExtensions.insert(_T("HTM"));
    aTextFileExtensions.insert(_T("HTML"));
    aTextFileExtensions.insert(_T("JS"));
    aTextFileExtensions.insert(_T("C"));
    aTextFileExtensions.insert(_T("H"));
    aTextFileExtensions.insert(_T("CPP"));
    aTextFileExtensions.insert(_T("HPP"));

    it = aTextFileExtensions.find(sExtension);
    if(it!=aTextFileExtensions.end())
    {
        mode = PREVIEW_TEXT;
    }

cleanup:

    return mode;
}

TextEncoding CFilePreviewCtrl::DetectTextEncoding(LPCTSTR szFileName, int& nSignatureLen)
{
    TextEncoding enc = ENC_ASCII;
    nSignatureLen = 0;

    FILE* f = NULL;

#if _MSC_VER<1400
    f = _tfopen(szFileName, _T("rb"));
#else
    _tfopen_s(&f, szFileName, _T("rb"));
#endif

    if(f==NULL)
        goto cleanup;   

    BYTE signature2[2];
    size_t nRead = fread(signature2, 1, 2, f);
    if(nRead!=2)
        goto cleanup;

    // Compare with UTF-16 LE signature
    if(signature2[0]==0xFF && 
        signature2[1]==0xFE 
        )
    {
        enc = ENC_UTF16_LE;
        nSignatureLen = 2;
        goto cleanup;
    }

    // Compare with UTF-16 BE signature
    if(signature2[0]==0xFE && 
        signature2[1]==0xFF 
        )
    {
        enc = ENC_UTF16_BE;
        nSignatureLen = 2;
        goto cleanup;
    }

    rewind(f);

    BYTE signature3[3];
    nRead = fread(signature3, 1, 3, f);
    if(nRead!=3)
        goto cleanup;

    // Compare with UTF-8 signature
    if(signature3[0]==0xEF && 
        signature3[1]==0xBB && 
        signature3[2]==0xBF 
        )
    {
        enc = ENC_UTF8;
        nSignatureLen = 3;
        goto cleanup;
    }

cleanup:

    fclose(f);

    return enc;
}

DWORD WINAPI CFilePreviewCtrl::WorkerThread(LPVOID lpParam)
{
    CFilePreviewCtrl* pCtrl = (CFilePreviewCtrl*)lpParam;
    pCtrl->DoInWorkerThread(); 
    return 0;
}

void CFilePreviewCtrl::DoInWorkerThread()
{
    if(m_PreviewMode==PREVIEW_TEXT)
        ParseText();
    else
        LoadBitmap();
}

WCHAR swap_bytes(WCHAR src_char)
{
    return (WCHAR)MAKEWORD((src_char>>8), (src_char&0xFF));
}

void CFilePreviewCtrl::ParseText()
{
    DWORD dwFileSize = (DWORD)m_fm.GetSize();
    DWORD dwOffset = 0;
    DWORD dwPrevOffset = 0;
    int nTabs = 0;  

    if(dwFileSize!=0)
    {
        CAutoLock lock(&m_csLock);
        if(m_PreviewMode==PREVIEW_TEXT)
        { 
            dwOffset+=m_nEncSignatureLen;
            m_aTextLines.push_back(dwOffset);
        }
        else
        {
            m_aTextLines.push_back(0);
        }

        m_uNumLines++;
    }

    for(;;)
    {
        {
            CAutoLock lock(&m_csLock);        
            if(m_bCancelled)
                break;
        }

        DWORD dwLength = 4096;
        if(dwOffset+dwLength>=dwFileSize)
            dwLength = dwFileSize-dwOffset;

        if(dwLength==0)
            break;

        LPBYTE ptr = m_fm.CreateView(dwOffset, dwLength);

        UINT i;
        for(i=0; i<dwLength; )
        {
            {
                CAutoLock lock(&m_csLock);        
                if(m_bCancelled)
                    break;
            }

            if(m_TextEncoding==ENC_UTF16_LE || m_TextEncoding==ENC_UTF16_BE)
            {
                WCHAR src_char = ((WCHAR*)ptr)[i/2];

                WCHAR c = m_TextEncoding==ENC_UTF16_LE?src_char:swap_bytes(src_char);

                if(c=='\t')
                {
                    nTabs++;
                }
                else if(c=='\n')
                {
                    CAutoLock lock(&m_csLock);        
                    m_aTextLines.push_back(dwOffset+i+2);
                    int cchLineLength = dwOffset+i+2-dwPrevOffset;
                    if(nTabs!=0)
                        cchLineLength += nTabs*(m_cchTabLength-1);

                    m_nMaxDisplayWidth = max(m_nMaxDisplayWidth, cchLineLength);
                    m_uNumLines++;
                    dwPrevOffset = dwOffset+i+2;        
                    nTabs = 0;
                }

                i+=2;
            }
            else
            {
                char c = ((char*)ptr)[i];      

                if(c=='\t')
                {
                    nTabs++;
                }
                else if(c=='\n')
                {
                    CAutoLock lock(&m_csLock);        
                    m_aTextLines.push_back(dwOffset+i+1);
                    int cchLineLength = dwOffset+i+1-dwPrevOffset;
                    if(nTabs!=0)
                        cchLineLength += nTabs*(m_cchTabLength-1);

                    m_nMaxDisplayWidth = max(m_nMaxDisplayWidth, cchLineLength);
                    m_uNumLines++;
                    dwPrevOffset = dwOffset+i+1;        
                    nTabs = 0;
                }

                i++;
            }
        }

        dwOffset += dwLength;        
    }

    PostMessage(WM_FPC_COMPLETE);
}

void CFilePreviewCtrl::LoadBitmap()
{
    m_bmp.Load(m_sFileName);
    PostMessage(WM_FPC_COMPLETE);
}

void CFilePreviewCtrl::SetEmptyMessage(CString sText)
{
    m_sEmptyMsg = sText;
}

BOOL CFilePreviewCtrl::SetBytesPerLine(int nBytesPerLine)
{
    if(nBytesPerLine<0)
        return FALSE;

    m_nBytesPerLine = nBytesPerLine;
    return TRUE;
}

void CFilePreviewCtrl::SetupScrollbars()
{
    CAutoLock lock(&m_csLock);
    CRect rcClient;
    GetClientRect(&rcClient);

    SCROLLINFO sInfo;

    //	Vertical scrollbar

    m_nMaxLinesPerPage = (int)min(m_uNumLines, rcClient.Height() / m_yChar);
    m_nVScrollMax = (int)max(0, m_uNumLines-1);
    m_nVScrollPos = (int)min(m_nVScrollPos, m_nVScrollMax-m_nMaxLinesPerPage+1);

    sInfo.cbSize = sizeof(SCROLLINFO);
    sInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
    sInfo.nMin	= 0;
    sInfo.nMax	= m_nVScrollMax;
    sInfo.nPos	= m_nVScrollPos;
    sInfo.nPage	= min(m_nMaxLinesPerPage, m_nVScrollMax+1);
    SetScrollInfo (SB_VERT, &sInfo, TRUE);

    //	Horizontal scrollbar 

    m_nMaxColsPerPage = min(m_nMaxDisplayWidth+1, rcClient.Width() / m_xChar);	
    m_nHScrollMax = max(0, m_nMaxDisplayWidth-1);
    m_nHScrollPos = min(m_nHScrollPos, m_nHScrollMax-m_nMaxColsPerPage+1);

    sInfo.cbSize = sizeof(SCROLLINFO);
    sInfo.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
    sInfo.nMin	= 0;
    sInfo.nMax	= m_nHScrollMax;
    sInfo.nPos	= m_nHScrollPos;
    sInfo.nPage	= min(m_nMaxColsPerPage, m_nHScrollMax+1);

    SetScrollInfo (SB_HORZ, &sInfo, TRUE);

}	

//
//	Create 1 line of a hex-dump, given a buffer of BYTES
//
CString CFilePreviewCtrl::FormatHexLine(LPBYTE pData, int nBytesInLine, ULONG64 uLineOffset)
{
    CString sResult;
    CString str;
    int i;

    //print the hex address
    str.Format(_T("%08X  "), uLineOffset);
    sResult += str;

    //print hex data
    for(i = 0; i < nBytesInLine; i++)
    {
        str.Format(_T("%02X "), pData[i]);
        sResult += str;
    }

    //print some blanks if this isn't a full line
    for(; i < m_nBytesPerLine; i++)
    {
        str.Format(_T("   "));
        sResult += str;
    }

    //print a gap between the hex and ascii
    sResult += _T(" ");

    //print the ascii
    for(i = 0; i < nBytesInLine; i++)
    {
        BYTE c = pData[i];
        if(c < 32 || c > 128) c = '.';
        str.Format( _T("%c"), c);
        sResult += str;
    }

    //print some blanks if this isn't a full line
    for(; i < m_nBytesPerLine; i++)
    {
        sResult += _T(" ");
    }

    return sResult;
}

//
//	Draw 1 line to the display
//
void CFilePreviewCtrl::DrawHexLine(HDC hdc, DWORD nLineNo)
{	
    int nBytesPerLine = m_nBytesPerLine;

    if(m_fm.GetSize() - nLineNo * m_nBytesPerLine < (UINT)m_nBytesPerLine)
        nBytesPerLine= (DWORD)m_fm.GetSize() - nLineNo * m_nBytesPerLine;

    //get data from our file mapping
    LPBYTE ptr = m_fm.CreateView(nLineNo * m_nBytesPerLine, nBytesPerLine);

    //convert the data into a one-line hex-dump
    CString str = FormatHexLine(ptr, nBytesPerLine, nLineNo*m_nBytesPerLine );

    //draw this line to the screen
    TextOut(hdc, -(int)(m_nHScrollPos * m_xChar), 
        (nLineNo - m_nVScrollPos) * (m_yChar-1) , str, str.GetLength());
}

void CFilePreviewCtrl::DrawTextLine(HDC hdc, DWORD nLineNo)
{
    CRect rcClient;
    GetClientRect(&rcClient);

    DWORD dwOffset = 0;
    DWORD dwLength = 0;
    {
        CAutoLock lock(&m_csLock);
        dwOffset = m_aTextLines[nLineNo];
        if(nLineNo==m_uNumLines-1)
            dwLength = (DWORD)m_fm.GetSize() - dwOffset;
        else
            dwLength = m_aTextLines[nLineNo+1]-dwOffset-1;
    }

    if(dwLength==0)
        return;

    //get data from our file mapping
    LPBYTE ptr = m_fm.CreateView(dwOffset, dwLength);

    //draw this line to the screen
    CRect rcText;
    rcText.left = -(int)(m_nHScrollPos * m_xChar);
    rcText.top = (nLineNo - m_nVScrollPos) * m_yChar;
    rcText.right = rcClient.right;
    rcText.bottom = rcText.top + m_yChar;
    DRAWTEXTPARAMS params;
    memset(&params, 0, sizeof(DRAWTEXTPARAMS));
    params.cbSize = sizeof(DRAWTEXTPARAMS);
    params.iTabLength = m_xChar*m_cchTabLength;

    DWORD dwFlags = DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX|DT_EXPANDTABS;

    if(m_TextEncoding==ENC_UTF8)
    {
        // Decode line
        strconv_t strconv;
        LPCWSTR szLine = strconv.utf82w((char*)ptr, dwLength-1);
        DrawTextExW(hdc, (LPWSTR)szLine, -1,  &rcText, dwFlags, &params);
    }
    else if(m_TextEncoding==ENC_UTF16_LE)
    {
        DrawTextExW(hdc, (WCHAR*)ptr, dwLength/2-1,  &rcText, 
            dwFlags, &params);   
    }
    else if(m_TextEncoding==ENC_UTF16_BE)
    {
        // Decode line
        strconv_t strconv;
        LPCWSTR szLine = strconv.w2w_be((WCHAR*)ptr, dwLength/2-1);
        DrawTextExW(hdc, (LPWSTR)szLine, -1,  &rcText, dwFlags, &params);
    }
    else // ASCII
    {
        DrawTextExA(hdc, (char*)ptr, dwLength-1,  &rcText, 
            dwFlags, &params);
    }
}

void CFilePreviewCtrl::DoPaintEmpty(HDC hDC)
{
    RECT rcClient;
    GetClientRect(&rcClient);

    HFONT hOldFont = (HFONT)SelectObject(hDC, m_hFont);

    FillRect(hDC, &rcClient, (HBRUSH)GetStockObject(WHITE_BRUSH));

    CRect rcText;
    DrawTextEx(hDC, m_sEmptyMsg.GetBuffer(0), -1, &rcText, DT_CALCRECT, NULL);

    rcText.MoveToX(rcClient.right/2-rcText.right/2);
    DrawTextEx(hDC, m_sEmptyMsg.GetBuffer(0), -1, &rcText, DT_LEFT, NULL);

    SelectObject(hDC, hOldFont);
}

void CFilePreviewCtrl::DoPaintText(HDC hDC)
{
    HFONT hOldFont = (HFONT)SelectObject(hDC, m_hFont);

    RECT rcClient;
    GetClientRect(&rcClient);

    HRGN hRgn = CreateRectRgn(0, 0, rcClient.right, rcClient.bottom);
    SelectClipRgn(hDC, hRgn);

    FillRect(hDC, &rcClient, (HBRUSH)GetStockObject(WHITE_BRUSH));

    int iPaintBeg = max(0, m_nVScrollPos);			//only update the lines that 
    int iPaintEnd = (int)min(m_uNumLines, m_nVScrollPos + rcClient.bottom / m_yChar);		//need updating!!!!!!!!!!!!!

    if(rcClient.bottom % m_yChar) iPaintEnd++;
    if(iPaintEnd > m_uNumLines) iPaintEnd--;	

    //
    //	Only paint what needs to be!
    //
    int i;
    for(i = iPaintBeg; i < iPaintEnd; i++)
    {
        if(m_PreviewMode==PREVIEW_HEX)
            DrawHexLine(hDC, i);
        else
            DrawTextLine(hDC, i);				
    }

    SelectObject(hDC, hOldFont);
}

void CFilePreviewCtrl::DoPaintBitmap(HDC hDC)
{
    RECT rcClient;
    GetClientRect(&rcClient);

    HRGN hRgn = CreateRectRgn(0, 0, rcClient.right, rcClient.bottom);
    SelectClipRgn(hDC, hRgn);

    FillRect(hDC, &rcClient, (HBRUSH)GetStockObject(WHITE_BRUSH));

    if(m_bmp.IsValid())
    {
        m_bmp.Draw(hDC, &rcClient);
    }
    else
    {
        DoPaintEmpty(hDC);
    }
}

void CFilePreviewCtrl::DoPaint(HDC hDC)
{
    if(m_PreviewMode==PREVIEW_TEXT ||
        m_PreviewMode==PREVIEW_HEX)
    {
        DoPaintText(hDC);
    }
    else
    {
        DoPaintBitmap(hDC);
    }
}

LRESULT CFilePreviewCtrl::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
    m_fm.Destroy();
    bHandled = FALSE;  
    return 0;
}

LRESULT CFilePreviewCtrl::OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    SetupScrollbars();

    InvalidateRect(NULL, FALSE);
    UpdateWindow();

    return 0;
}

LRESULT CFilePreviewCtrl::OnHScroll(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    SCROLLINFO info;
    int nHScrollInc = 0;
    int  nOldHScrollPos = m_nHScrollPos;

    switch (LOWORD(wParam))
    {
    case SB_LEFT:
        m_nHScrollPos = 0;
        break;

    case SB_RIGHT:
        m_nHScrollPos = m_nHScrollMax + 1;
        break;

    case SB_LINELEFT:
        if(m_nHScrollPos > 0) --m_nHScrollPos;
        break;

    case SB_LINERIGHT:
        m_nHScrollPos++;
        break;

    case SB_PAGELEFT:
        m_nHScrollPos -= m_nMaxColsPerPage;
        if(m_nHScrollPos > nOldHScrollPos) 
            m_nHScrollPos = 0;
        break;

    case SB_PAGERIGHT:
        m_nHScrollPos += m_nMaxColsPerPage;
        break;

    case SB_THUMBPOSITION:
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_TRACKPOS;
        GetScrollInfo(SB_HORZ, &info);
        m_nHScrollPos = info.nTrackPos;
        break;

    case SB_THUMBTRACK:
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_TRACKPOS;
        GetScrollInfo(SB_HORZ, &info);
        m_nHScrollPos = info.nTrackPos;
        break;

    default:
        nHScrollInc = 0;
    }

    //keep scroll position in range
    if(m_nHScrollPos  > m_nHScrollMax - m_nMaxColsPerPage + 1)
        m_nHScrollPos = m_nHScrollMax - m_nMaxColsPerPage + 1;

    nHScrollInc = m_nHScrollPos - nOldHScrollPos;

    if (nHScrollInc)
    {	

        //finally setup the actual scrollbar!
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_POS;
        info.nPos = m_nHScrollPos;
        SetScrollInfo(SB_HORZ, &info, TRUE);

        InvalidateRect(NULL);
    }

    return 0;
}

LRESULT CFilePreviewCtrl::OnVScroll(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    // React to the various vertical scroll related actions.
    // CAUTION:
    // All sizes are in unsigned values, so be carefull
    // when testing for < 0 etc

    SCROLLINFO info;
    int nVScrollInc;
    int  nOldVScrollPos = m_nVScrollPos;

    switch (LOWORD(wParam))
    {
    case SB_TOP:
        m_nVScrollPos = 0;
        break;

    case SB_BOTTOM:
        m_nVScrollPos = m_nVScrollMax - m_nMaxLinesPerPage + 1;
        break;

    case SB_LINEUP:
        if(m_nVScrollPos > 0) --m_nVScrollPos;
        break;

    case SB_LINEDOWN:
        m_nVScrollPos++;
        break;

    case SB_PAGEUP:
        m_nVScrollPos -= max(1, m_nMaxLinesPerPage);
        if(m_nVScrollPos > nOldVScrollPos) m_nVScrollPos = 0;
        break;

    case SB_PAGEDOWN:
        m_nVScrollPos += max(1, m_nMaxLinesPerPage);
        break;

    case SB_THUMBPOSITION:
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_TRACKPOS;
        GetScrollInfo(SB_VERT, &info);
        m_nVScrollPos = info.nTrackPos;
        break;

    case SB_THUMBTRACK:
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_TRACKPOS;
        GetScrollInfo(SB_VERT, &info);
        m_nVScrollPos = info.nTrackPos;
        break;

    default:
        nVScrollInc = 0;
    }

    //keep scroll position in range
    if(m_nVScrollPos > m_nVScrollMax - m_nMaxLinesPerPage+1)
        m_nVScrollPos = m_nVScrollMax - m_nMaxLinesPerPage+1;

    if(m_nVScrollPos<0)
        m_nVScrollPos = 0;

    nVScrollInc = m_nVScrollPos - nOldVScrollPos;

    if (nVScrollInc)
    {	

        //finally setup the actual scrollbar!
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_POS;
        info.nPos = m_nVScrollPos;
        SetScrollInfo(SB_VERT, &info, TRUE);

        InvalidateRect(NULL);
    }

    return 0;
}

LRESULT CFilePreviewCtrl::OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    // Do nothing
    return 0;   
}

LRESULT CFilePreviewCtrl::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{ 
    PAINTSTRUCT ps;
    HDC hDC = BeginPaint(&ps);

    {
        CMemoryDC memDC(hDC, ps.rcPaint);

        if(m_fm.GetSize()==0)
            DoPaintEmpty(memDC);
        else
            DoPaint(memDC);
    }

    EndPaint(&ps);

    return 0;
}

LRESULT CFilePreviewCtrl::OnTimer(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    SetupScrollbars();
    InvalidateRect(NULL);
    return 0;
}

LRESULT CFilePreviewCtrl::OnComplete(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    KillTimer(0);
    SetupScrollbars();
    InvalidateRect(NULL);
    return 0;
}

LRESULT CFilePreviewCtrl::OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    SetFocus();
    return 0;
}

LRESULT CFilePreviewCtrl::OnRButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    NMHDR nmhdr;
    nmhdr.hwndFrom = m_hWnd;
    nmhdr.code = NM_RCLICK;
    nmhdr.idFrom = GetWindowLong(GWL_ID);

    HWND hWndParent = GetParent();
    ::SendMessage(hWndParent, WM_NOTIFY, 0, (LPARAM)&nmhdr);
    return 0;
}

LRESULT CFilePreviewCtrl::OnMouseWheel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    if(m_PreviewMode!=PREVIEW_TEXT && 
        m_PreviewMode!=PREVIEW_HEX)
        return 0;

    int nDistance =  GET_WHEEL_DELTA_WPARAM(wParam)/WHEEL_DELTA;
    int nLinesPerDelta = m_nMaxLinesPerPage!=0?m_nVScrollMax/m_nMaxLinesPerPage:0;

    SCROLLINFO info;
    memset(&info, 0, sizeof(SCROLLINFO));
    info.cbSize = sizeof(SCROLLINFO);
    info.fMask = SIF_ALL;
    GetScrollInfo(SB_VERT, &info);	
    info.nPos -=nDistance*nLinesPerDelta;
    SetScrollInfo(SB_VERT, &info, TRUE);

    SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, info.nPos), 0);
    return 0;
}
