/* -*- C++ -*-
 * File: libraw_datastream.cpp
 * Copyright 2008-2017 LibRaw LLC (info@libraw.org)
 *
 * LibRaw C++ interface (implementation)

 LibRaw is free software; you can redistribute it and/or modify
 it under the terms of the one of two licenses as you choose:

1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
   (See file LICENSE.LGPL provided in LibRaw distribution archive for details).

2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
   (See file LICENSE.CDDL provided in LibRaw distribution archive for details).

*/

#ifdef WIN32
#ifdef __MINGW32__
    #define _WIN32_WINNT 0x0500
    #include <stdexcept>
#endif
#endif

#define LIBRAW_LIBRARY_BUILD
#include "libraw/libraw_types.h"
#include "libraw/libraw.h"
#include "libraw/libraw_datastream.h"
#include <sys/stat.h>
#ifdef USE_JASPER
#include <jasper/jasper.h>	/* Decode RED camera movies */
#else
#define NO_JASPER
#endif
#ifdef USE_JPEG
#include <jpeglib.h>
#else
#define NO_JPEG
#endif

int LibRaw_abstract_datastream::tempbuffer_open(void  *buf, size_t size)
{
    if(substream) return EBUSY;
    substream = new LibRaw_buffer_datastream(buf,size);
    return substream?0:EINVAL;
}


void	LibRaw_abstract_datastream::tempbuffer_close()
{
    if(substream) delete substream;
    substream = NULL;
}

// == LibRaw_file_datastream ==

LibRaw_file_datastream::~LibRaw_file_datastream()
{
  if(jas_file) fclose(jas_file);
}

LibRaw_file_datastream::LibRaw_file_datastream(const char *fname)
    :filename(fname)
    ,_fsize(0)
#ifdef WIN32
    ,wfilename()
#endif
    ,jas_file(NULL)
{
  if (filename.size()>0) 
    {
#ifndef WIN32
      struct stat st;
      if(!stat(filename.c_str(),&st))
        _fsize = st.st_size;	
#else
      struct _stati64 st;
      if(!_stati64(filename.c_str(),&st))
        _fsize = st.st_size;
#endif
      
      std::auto_ptr<std::filebuf> buf(new std::filebuf());
      buf->open(filename.c_str(), std::ios_base::in | std::ios_base::binary);
      if (buf->is_open()) {
        f = buf;
      }
    }
}
#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
LibRaw_file_datastream::LibRaw_file_datastream(const wchar_t *fname) : filename(),wfilename(fname),jas_file(NULL),_fsize(0)
{
  if (wfilename.size()>0) 
    {
      struct _stati64 st;
      if(!_wstati64(wfilename.c_str(),&st))
        _fsize = st.st_size;
      std::auto_ptr<std::filebuf> buf(new std::filebuf());
      buf->open(wfilename.c_str(), std::ios_base::in | std::ios_base::binary);
      if (buf->is_open()) {
        f = buf;
      }
    }
}
const wchar_t *LibRaw_file_datastream::wfname()
{
  return wfilename.size()>0?wfilename.c_str():NULL;
}
#endif

 int LibRaw_file_datastream::valid()
{ 
    return f.get() ? 1 : 0; 
}

#define LR_STREAM_CHK() do {if(!f.get()) throw LIBRAW_EXCEPTION_IO_EOF;}while(0)

int LibRaw_file_datastream::read(void * ptr,size_t size, size_t nmemb)
{
    if(substream) return substream->read(ptr,size,nmemb);
    
/* Visual Studio 2008 marks sgetn as insecure, but VS2010 does not. */
#if defined(WIN32SECURECALLS) && (_MSC_VER < 1600)
    LR_STREAM_CHK(); return int(f->_Sgetn_s(static_cast<char*>(ptr), nmemb * size,nmemb * size) / (size>0?size:1)); 
#else
    LR_STREAM_CHK(); return int(f->sgetn(static_cast<char*>(ptr), std::streamsize(nmemb * size)) / (size>0?size:1)); 
#endif
}

int LibRaw_file_datastream::eof() 
{ 
    if(substream) return substream->eof();
    LR_STREAM_CHK(); return f->sgetc() == EOF; 
}

int LibRaw_file_datastream::seek(INT64 o, int whence) 
{ 
    if(substream) return substream->seek(o,whence);
    LR_STREAM_CHK(); 
    std::ios_base::seekdir dir;
    switch (whence) 
        {
        case SEEK_SET: dir = std::ios_base::beg; break;
        case SEEK_CUR: dir = std::ios_base::cur; break;
        case SEEK_END: dir = std::ios_base::end; break;
        default: dir = std::ios_base::beg;
        }
    return f->pubseekoff((long)o, dir) < 0;
}

INT64 LibRaw_file_datastream::tell()     
{ 
    if(substream) return substream->tell();
    LR_STREAM_CHK(); return f->pubseekoff(0, std::ios_base::cur);  
}

char* LibRaw_file_datastream::gets(char *str, int sz) 
{ 
    if(substream) return substream->gets(str,sz);
    LR_STREAM_CHK(); 
    std::istream is(f.get());
    is.getline(str, sz);
    if (is.fail()) return 0;
    return str;
}

int LibRaw_file_datastream::scanf_one(const char *fmt, void*val) 
{ 
    if(substream) return substream->scanf_one(fmt,val);
    LR_STREAM_CHK(); 
    
    std::istream is(f.get());
    
    /* HUGE ASSUMPTION: *fmt is either "%d" or "%f" */
    if (strcmp(fmt, "%d") == 0) {
        int d;
        is >> d;
        if (is.fail()) return EOF;
        *(static_cast<int*>(val)) = d;
    } else {
        float f;
        is >> f;
        if (is.fail()) return EOF;
        *(static_cast<float*>(val)) = f;
    }
    
    return 1;
}

const char* LibRaw_file_datastream::fname() 
{ 
  return filename.size()>0?filename.c_str():NULL; 
}
    
/* You can't have a "subfile" and a "tempfile" at the same time. */
int LibRaw_file_datastream::subfile_open(const char *fn)
{
    LR_STREAM_CHK();
    if (saved_f.get()) return EBUSY;
    saved_f = f;
        std::auto_ptr<std::filebuf> buf(new std::filebuf());
        
        buf->open(fn, std::ios_base::in | std::ios_base::binary);
        if (!buf->is_open()) {
            f = saved_f;
            return ENOENT;
        } else {
            f = buf;
        }
        
        return 0;
}

#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
int LibRaw_file_datastream::subfile_open(const wchar_t *fn)
{
	LR_STREAM_CHK();
	if (saved_f.get()) return EBUSY;
	saved_f = f;
	std::auto_ptr<std::filebuf> buf(new std::filebuf());

	buf->open(fn, std::ios_base::in | std::ios_base::binary);
	if (!buf->is_open()) {
		f = saved_f;
		return ENOENT;
	} else {
		f = buf;
	}

	return 0;
}
#endif


void LibRaw_file_datastream::subfile_close()
{ 
    if (!saved_f.get()) return; 
    f = saved_f;   
}

#undef LR_STREAM_CHK

void * LibRaw_file_datastream::make_jas_stream()
{
#ifdef NO_JASPER
    return NULL;
#else
#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
	if(wfname())
	{
		jas_file = _wfopen(wfname(),L"rb");
		return jas_stream_fdopen(fileno(jas_file),"rb");
	}
	else
#endif
	{
		return jas_stream_fopen(fname(),"rb");
	}
#endif
}

int LibRaw_file_datastream::jpeg_src(void *jpegdata)
{
#ifdef NO_JPEG
  return -1; // not supported
#else
  if(jas_file) { fclose(jas_file); jas_file = NULL;}
#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
  if(wfname())
    {
      jas_file = _wfopen(wfname(),L"rb");
    }
  else
#endif
    {
      jas_file = fopen(fname(),"rb");
    }
  if(jas_file)
    {
      fseek(jas_file,tell(),SEEK_SET);
      j_decompress_ptr cinfo = (j_decompress_ptr) jpegdata;
      jpeg_stdio_src(cinfo,jas_file);
      return 0; // OK
    }
  return -1;
#endif
}


// == LibRaw_buffer_datastream
LibRaw_buffer_datastream::LibRaw_buffer_datastream(void *buffer, size_t bsize)
{    
    buf = (unsigned char*)buffer; streampos = 0; streamsize = bsize;
}

LibRaw_buffer_datastream::~LibRaw_buffer_datastream(){}

int LibRaw_buffer_datastream::read(void * ptr,size_t sz, size_t nmemb)
{ 
    if(substream) return substream->read(ptr,sz,nmemb);
    size_t to_read = sz*nmemb;
    if(to_read > streamsize - streampos)
        to_read = streamsize-streampos;
    if(to_read<1) 
        return 0;
    memmove(ptr,buf+streampos,to_read);
    streampos+=to_read;
    return int((to_read+sz-1)/(sz>0?sz:1));
}

int LibRaw_buffer_datastream::seek(INT64 o, int whence)
{ 
    if(substream) return substream->seek(o,whence);
    switch(whence)
        {
        case SEEK_SET:
            if(o<0)
                streampos = 0;
            else if (size_t(o) > streamsize)
                streampos = streamsize;
            else
                streampos = size_t(o);
            return 0;
        case SEEK_CUR:
            if(o<0)
                {
                    if(size_t(-o) >= streampos)
                        streampos = 0;
                    else
                        streampos += (size_t)o;
                }
            else if (o>0)
                {
                    if(o+streampos> streamsize)
                        streampos = streamsize;
                    else
                        streampos += (size_t)o;
                }
            return 0;
        case SEEK_END:
            if(o>0)
                streampos = streamsize;
            else if ( size_t(-o) > streamsize)
                streampos = 0;
            else
                streampos = streamsize+(size_t)o;
            return 0;
        default:
            return 0;
        }
}

INT64 LibRaw_buffer_datastream::tell()
{ 
    if(substream) return substream->tell();
    return INT64(streampos);
}

char* LibRaw_buffer_datastream::gets(char *s, int sz)
{ 
    if (substream) return substream->gets(s,sz);
    unsigned char *psrc,*pdest,*str;
    str = (unsigned char *)s;
    psrc = buf+streampos;
    pdest = str;
    while ( (size_t(psrc - buf) < streamsize)
            &&
            ((pdest-str)<sz)
        )
        {
            *pdest = *psrc;
            if(*psrc == '\n')
                break;
            psrc++;
            pdest++;
        }
    if(size_t(psrc-buf) < streamsize)
        psrc++;
    if((pdest-str)<sz)
        *(++pdest)=0;
    streampos = psrc - buf;
    return s;
}

int LibRaw_buffer_datastream::scanf_one(const char *fmt, void* val)
{ 
    if(substream) return substream->scanf_one(fmt,val);
    int scanf_res;
    if(streampos>streamsize) return 0;
#ifndef WIN32SECURECALLS
    scanf_res = sscanf((char*)(buf+streampos),fmt,val);
#else
    scanf_res = sscanf_s((char*)(buf+streampos),fmt,val);
#endif
    if(scanf_res>0)
        {
            int xcnt=0;
            while(streampos<streamsize)
                {
                    streampos++;
                    xcnt++;
                    if(buf[streampos] == 0
                       || buf[streampos]==' '
                       || buf[streampos]=='\t'
                       || buf[streampos]=='\n'
                       || xcnt>24)
                        break;
                }
        }
    return scanf_res;
}

int LibRaw_buffer_datastream::eof()
{ 
    if(substream) return substream->eof();
    return streampos >= streamsize;
}
 int LibRaw_buffer_datastream::valid() 
{ 
    return buf?1:0;
}


void * LibRaw_buffer_datastream::make_jas_stream()
{
#ifdef NO_JASPER
    return NULL;
#else
    return jas_stream_memopen((char*)buf+streampos,streamsize-streampos);
#endif
}

int LibRaw_buffer_datastream::jpeg_src(void *jpegdata)
{
#if defined(NO_JPEG) || !defined (USE_JPEG)
  return -1;
#else
  j_decompress_ptr cinfo = (j_decompress_ptr) jpegdata;
  jpeg_mem_src(cinfo,(unsigned char*)buf+streampos,streamsize-streampos);
  return 0;
#endif
}


//int LibRaw_buffer_datastream


// == LibRaw_bigfile_datastream
LibRaw_bigfile_datastream::LibRaw_bigfile_datastream(const char *fname): filename(fname)
#ifdef WIN32
	,wfilename()
#endif
{ 
  if(filename.size()>0)
    {
#ifndef WIN32
      struct stat st;
      if(!stat(filename.c_str(),&st))
        _fsize = st.st_size;	
#else
      struct _stati64 st;
      if(!_stati64(filename.c_str(),&st))
        _fsize = st.st_size;
#endif

#ifndef WIN32SECURECALLS
      f = fopen(fname,"rb");
#else
      if(fopen_s(&f,fname,"rb"))
        f = 0;
#endif
    }
    else 
      {filename=std::string();f=0;}
    sav=0;
}

#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
LibRaw_bigfile_datastream::LibRaw_bigfile_datastream(const wchar_t *fname) : filename(),wfilename(fname)
{ 
  if(wfilename.size()>0)
    {
      struct _stati64 st;
      if(!_wstati64(wfilename.c_str(),&st))
        _fsize = st.st_size;
#ifndef WIN32SECURECALLS
      f = _wfopen(wfilename.c_str(),L"rb");
#else
      if(_wfopen_s(&f,fname,L"rb"))
        f = 0;
#endif
    }
  else 
    {
      wfilename=std::wstring();
      f=0;
    }
  sav=0;
}
const wchar_t *LibRaw_bigfile_datastream::wfname()
{
  return wfilename.size()>0?wfilename.c_str():NULL;
}
#endif

LibRaw_bigfile_datastream::~LibRaw_bigfile_datastream() {if(f)fclose(f); if(sav)fclose(sav);}
int         LibRaw_bigfile_datastream::valid() { return f?1:0;}

#define LR_BF_CHK() do {if(!f) throw LIBRAW_EXCEPTION_IO_EOF;}while(0)

int LibRaw_bigfile_datastream::read(void * ptr,size_t size, size_t nmemb) 
{ 
    LR_BF_CHK(); 
    return substream?substream->read(ptr,size,nmemb):int(fread(ptr,size,nmemb,f));
}

int LibRaw_bigfile_datastream::eof()
{ 
    LR_BF_CHK(); 
    return substream?substream->eof():feof(f);
}

int     LibRaw_bigfile_datastream:: seek(INT64 o, int whence)
{ 
    LR_BF_CHK(); 
#if defined (WIN32) 
#ifdef WIN32SECURECALLS
    return substream?substream->seek(o,whence):_fseeki64(f,o,whence);
#else
    return substream?substream->seek(o,whence):fseek(f,(long)o,whence);
#endif
#else
    return substream?substream->seek(o,whence):fseeko(f,o,whence);
#endif
}

INT64 LibRaw_bigfile_datastream::tell()
{ 
    LR_BF_CHK(); 
#if defined (WIN32)
#ifdef WIN32SECURECALLS
    return substream?substream->tell():_ftelli64(f);
#else
    return substream?substream->tell():ftell(f);
#endif
#else
    return substream?substream->tell():ftello(f);
#endif
}

char* LibRaw_bigfile_datastream::gets(char *str, int sz)
{ 
    LR_BF_CHK(); 
    return substream?substream->gets(str,sz):fgets(str,sz,f);
}

int LibRaw_bigfile_datastream::scanf_one(const char *fmt, void*val)
{ 
    LR_BF_CHK(); 
    return substream?substream->scanf_one(fmt,val):
#ifndef WIN32SECURECALLS			
        fscanf(f,fmt,val)
#else
        fscanf_s(f,fmt,val)
#endif
        ;
}

const char *LibRaw_bigfile_datastream::fname() 
{ 
  return filename.size()>0?filename.c_str():NULL; 
}

int LibRaw_bigfile_datastream::subfile_open(const char *fn)
{
    if(sav) return EBUSY;
    sav = f;
#ifndef WIN32SECURECALLS
    f = fopen(fn,"rb");
#else
    fopen_s(&f,fn,"rb");
#endif
    if(!f)
        {
            f = sav;
            sav = NULL;
            return ENOENT;
        }
    else
        return 0;
}
#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
int LibRaw_bigfile_datastream::subfile_open(const wchar_t *fn)
{
	if(sav) return EBUSY;
	sav = f;
#ifndef WIN32SECURECALLS
	f = _wfopen(fn,L"rb");
#else
	_wfopen_s(&f,fn,L"rb");
#endif
	if(!f)
	{
		f = sav;
		sav = NULL;
		return ENOENT;
	}
	else
		return 0;
}
#endif


void LibRaw_bigfile_datastream::subfile_close()
{
    if(!sav) return;
    fclose(f);
    f = sav;
    sav = 0;
}


void *LibRaw_bigfile_datastream::make_jas_stream()
{
#ifdef NO_JASPER
    return NULL;
#else
    return jas_stream_fdopen(fileno(f),"rb");
#endif
}

int LibRaw_bigfile_datastream::jpeg_src(void *jpegdata)
{
#ifdef NO_JPEG
  return -1;
#else
  if(!f) return -1;
  j_decompress_ptr cinfo = (j_decompress_ptr) jpegdata;
  jpeg_stdio_src(cinfo,f);
  return 0; // OK
#endif
}


// == LibRaw_windows_datastream
#ifdef WIN32

LibRaw_windows_datastream::LibRaw_windows_datastream(const TCHAR* sFile)
    : LibRaw_buffer_datastream(NULL, 0)
    , hMap_(0)
    , pView_(NULL)
{
    HANDLE hFile = CreateFile(sFile, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (hFile == INVALID_HANDLE_VALUE) 
        throw std::runtime_error("failed to open the file"); 
    
    try { Open(hFile); 	}	catch(...) { CloseHandle(hFile); throw; }
    
    CloseHandle(hFile);		// windows will defer the actual closing of this handle until the hMap_ is closed
    reconstruct_base();
}

	// ctor: construct with a file handle - caller is responsible for closing the file handle
LibRaw_windows_datastream::LibRaw_windows_datastream(HANDLE hFile)
    : LibRaw_buffer_datastream(NULL, 0)
    , hMap_(0)
    , pView_(NULL)
{
    Open(hFile);
    reconstruct_base();
}

// dtor: unmap and close the mapping handle
LibRaw_windows_datastream::~LibRaw_windows_datastream()
{
    if (pView_ != NULL)
        ::UnmapViewOfFile(pView_);
    
    if (hMap_ != 0)
        ::CloseHandle(hMap_);
}

void LibRaw_windows_datastream::Open(HANDLE hFile)
{
    // create a file mapping handle on the file handle
    hMap_ = ::CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
    if (hMap_ == NULL)	throw std::runtime_error("failed to create file mapping"); 
    
    // now map the whole file base view
    if (!::GetFileSizeEx(hFile, (PLARGE_INTEGER)&cbView_))
        throw std::runtime_error("failed to get the file size"); 
    
    pView_ = ::MapViewOfFile(hMap_, FILE_MAP_READ, 0, 0, (size_t)cbView_);
    if (pView_ == NULL)	
        throw std::runtime_error("failed to map the file"); 
}


#endif


    
