/* -*- C -*-
 * File: libraw_datastream.h
 * Copyright 2008-2009 Alex Tutubalin <lexa@lexa.ru>
 * Created: Sun Jan 18 13:07:35 2009
 *
 * LibRaw Data stream interface
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#ifndef __LIBRAW_DATASTREAM_H
#define __LIBRAW_DATASTREAM_H

#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>

#ifndef __cplusplus

struct LibRaw_abstract_datastream;

#else // __cplusplus

#include "libraw_const.h"

class LibRaw_buffer_datastream;

class LibRaw_abstract_datastream
{
  public:
    LibRaw_abstract_datastream(){substream=0;};
    virtual             ~LibRaw_abstract_datastream(void){if(substream) delete substream;}
    virtual int         valid(){return 0;}
    // file input emulation
    virtual int         read(void *,size_t, size_t ){ return -1;}
    virtual int         seek(off_t o, int whence){return -1;}
    virtual int         tell(){return -1;}
    virtual int         get_char(){return -1;}
    virtual char*       gets(char *, int){ return NULL;}
    virtual int         scanf_one(const char *, void *){return -1;}
    virtual int         eof(){return -1;}

    virtual const char* fname(){ return NULL;};
    virtual int         subfile_open(const char*){ return EINVAL;}
    virtual void        subfile_close(){}
    virtual int		tempbuffer_open(void*, size_t);
    virtual void	tempbuffer_close()
    {
        if(substream) delete substream;
        substream = NULL;
    }

  protected:
    LibRaw_abstract_datastream *substream;
};


class LibRaw_file_datastream : public LibRaw_abstract_datastream
{
  public:
    LibRaw_file_datastream(const char *fname) 
        { 
            if(fname)
                {filename = fname; f = fopen(fname,"rb");}
            else 
                {filename=0;f=0;}
            sav=0;
        }

    virtual ~LibRaw_file_datastream() {if(f)fclose(f); if(sav)fclose(sav);}

    virtual int valid() { return f?1:0;}

#define CHK() do {if(!f) throw LIBRAW_EXCEPTION_IO_EOF;}while(0)
    virtual int read(void * ptr,size_t size, size_t nmemb) 
    { 
        CHK(); 
        return substream?substream->read(ptr,size,nmemb):int(fread(ptr,size,nmemb,f));
    }
    virtual int eof() 
    { 
        CHK(); 
        return substream?substream->eof():feof(f);
    }
    virtual int seek(off_t o, int whence) 
    { 
        CHK(); 
        return substream?substream->seek(o,whence):fseek(f,o,whence);
    }
    virtual int tell() 
    { 
        CHK(); 
        return substream?substream->tell():ftell(f);
    }
    virtual int get_char() 
    { 
        CHK(); 
        return substream?substream->get_char():fgetc(f);
    }
    virtual char* gets(char *str, int sz) 
    { 
        CHK(); 
        return substream?substream->gets(str,sz):fgets(str,sz,f);
    }
    virtual int scanf_one(const char *fmt, void*val) 
    { 
        CHK(); 
        return substream?substream->scanf_one(fmt,val):fscanf(f,fmt,val);
    }

    virtual const char *fname() { return filename; }

    // secondary 
    virtual int subfile_open(const char *fn)
    {
        if(sav) return EBUSY;
        sav = f;
        f = fopen(fn,"rb");
        if(!f)
            {
                f = sav;
                sav = NULL;
                return ENOENT;
            }
        else
            return 0;
    }
    virtual void subfile_close()
    {
        if(!sav) return;
        fclose(f);
        f = sav;
        sav = 0;
    }

  private:
    FILE *f,*sav;
    const char *filename;
};
#undef CHK

class LibRaw_buffer_datastream : public LibRaw_abstract_datastream
{
  public:
    LibRaw_buffer_datastream(void *buffer, size_t bsize)
        {
            buf = (unsigned char*)buffer; streampos = 0; streamsize = bsize;
        }
    virtual ~LibRaw_buffer_datastream(){}
    virtual int valid() { return buf?1:0;}
    virtual int 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);
    }

    virtual int eof() 
    { 
        if(substream) return substream->eof();
        return streampos >= streamsize;
    }

    virtual int seek(off_t 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 += o;
                    }
                else if (o>0)
                    {
                        if(o+streampos> streamsize)
                            streampos = streamsize;
                        else
                            streampos += o;
                    }
                return 0;
            case SEEK_END:
                if(o>0)
                    streampos = streamsize;
                else if ( size_t(-o) > streamsize)
                    streampos = 0;
                else
                    streampos = streamsize+o;
                return 0;
            default:
                return 0;
            }
    }
    
    virtual int tell() 
    { 
        if(substream) return substream->tell();
        return int(streampos);
    }

    virtual int get_char() 
    { 
        if(substream) return substream->get_char();
        if(streampos>=streamsize)
            return -1;
        return buf[streampos++];
    }
    virtual char* 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;
    }
    virtual int scanf_one(const char *fmt, void* val) 
    { 
        if(substream) return substream->scanf_one(fmt,val);
        int scanf_res;
        if(streampos>streamsize) return 0;
        scanf_res = sscanf((char*)(buf+streampos),fmt,val);
        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;
    }
  private:
    unsigned char *buf;
    size_t   streampos,streamsize;
};

inline 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;
}


#endif

#endif

