/**********************************************************************
XyGrib: meteorological GRIB file viewer
Copyright (C) 2008-2012 - Jacques Zaninetti - http://www.zygrib.org

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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
***********************************************************************/

#include "zuFile.h"

//----------------------------------------------------
int    zu_can_read_file(const char *fname)
{
    ZUFILE *f;
    f = zu_open(fname, "rb");
    if (f == nullptr) {
        return 0;
    }
    zu_close(f);
    return 1;
}

//----------------------------------------------------
ZUFILE * zu_open(const char *fname, const char *mode, int type)
{
    ZUFILE *f;
    if (!fname || strlen(fname)==0) {
        return nullptr;
    }
    f = (ZUFILE *) malloc(sizeof(ZUFILE));
    if (!f) {
        return nullptr;
    }

    f->ok = 1;
    f->pos = 0;
    f->fname = strdup(fname);
    f->type  = type;

	if (type == ZU_COMPRESS_AUTO)
	{
		if (zu_isBZIP (fname))
			f->type = ZU_COMPRESS_BZIP;
		else if (zu_isGZIP (fname))
			f->type = ZU_COMPRESS_GZIP;
		else
			f->type = ZU_COMPRESS_NONE;
	}
	
    switch(f->type) {
        case ZU_COMPRESS_NONE :
            f->zfile = (void *) fopen(f->fname, mode);
            break;
        case ZU_COMPRESS_GZIP :
            f->zfile = (void *) gzopen(f->fname, mode);
            break;
        case ZU_COMPRESS_BZIP :
            f->faux = fopen(f->fname, mode);
            if (f->faux) {
                int bzerror=BZ_OK;
                f->zfile = (void *) BZ2_bzReadOpen(&bzerror,f->faux,0,0,NULL,0);
                if (bzerror != BZ_OK) {
                    BZ2_bzReadClose (&bzerror,(BZFILE*)(f->zfile));
                    fclose(f->faux);
                    f->zfile = nullptr;
                }
            } else {
                f->zfile = nullptr;
            }
            break;
        default :
            f->zfile = nullptr;
    }

    if (f->zfile == nullptr) {
        free(f->fname);
        free(f);
        f = nullptr;
    }

    return f;
}

//-----------------------------------------------------------------
bool zu_isGZIP (const char *fname)
{
	FILE *f = fopen(fname, "r");
	if (!f)
		return false;
	unsigned char buf [8];
	int nb = fread (buf, 1,8, f);
	fclose (f);
	if (nb != 8)
		return false;
	return buf[0]==0x1f && buf[1]==0x8b;
}
//-----------------------------------------------------------------
bool zu_isBZIP (const char *fname)
{
	FILE *f = fopen(fname, "r");
	if (!f)
		return false;
	unsigned char buf [8];
	int nb = fread (buf, 1,8, f);
	fclose (f);
	if (nb != 8)
		return false;
	//BZh91AY
	return buf[0]=='B' && buf[1]=='Z' 
			&&  (buf[2]=='h'||  buf[2]=='1')
			&&  (buf[3]>='0'&&  buf[3]<='9')
			&&  buf[4]=='1' &&  buf[5]=='A' &&  buf[6]=='Y';
}
//----------------------------------------------------
int  zu_read(ZUFILE *f, void *buf, long len)
{
    int nb = 0;
    int bzerror=BZ_OK;
    switch(f->type) {
        case ZU_COMPRESS_NONE :
            nb = fread(buf, 1, len, (FILE*)(f->zfile));
            break;
        case ZU_COMPRESS_GZIP :
            nb = gzread((gzFile)(f->zfile), buf, len);
            break;
        case ZU_COMPRESS_BZIP :
            nb = BZ2_bzRead(&bzerror,(BZFILE*)(f->zfile), buf, len);
            break;
    }
    f->pos += nb;
    return nb;
}

//----------------------------------------------------
int zu_close(ZUFILE *f)
{
    int bzerror=BZ_OK;
    if (f) {
        f->ok = 0;
        f->pos = 0;
        free(f->fname);
        if (f->zfile) {
            switch(f->type) {
                case ZU_COMPRESS_NONE :
                    fclose((FILE*)(f->zfile));
                    break;
                case ZU_COMPRESS_GZIP :
                    gzclose((gzFile)(f->zfile));
                    break;
                case ZU_COMPRESS_BZIP :
                    BZ2_bzReadClose (&bzerror,(BZFILE*)(f->zfile));
                    if (f->faux) {
                        fclose(f->faux);
                    }
                    break;
            }
        }
        free(f);
    }
    return 0;
}



//----------------------------------------------------
long   zu_tell(ZUFILE *f)
{
    return f->pos;
}

//----------------------------------------------------
long   zu_filesize(ZUFILE *f)
{
    return zu_filesize_name (f->fname);
}

//----------------------------------------------------
long   zu_filesize_name (const char* filename)
{
    long res = 0;
    FILE *ftmp = fopen(filename, "rb");
    if (ftmp)
    {
        fseek(ftmp, 0, SEEK_END);
        res = ftell(ftmp);        
        fclose(ftmp);
    }
    return res;
}

//----------------------------------------------------
int zu_seek(ZUFILE *f, long offset, int whence)
{
    int res = 0;
    int bzerror=BZ_OK;
    if (whence == SEEK_END) {
        return -1;              // TODO
    }
    
    switch(f->type) {         //SEEK_SET, SEEK_CUR
        case ZU_COMPRESS_NONE :
            res = fseek((FILE*)(f->zfile), offset, whence);
            f->pos = ftell((FILE*)(f->zfile));
            break;
        case ZU_COMPRESS_GZIP :
            if (whence == SEEK_SET) {
                res = gzseek((gzFile)(f->zfile), offset, whence);
            }
            else {     // !!! BUG with SEEK_CUR in ZLIB !!!
                int p1 = gztell((gzFile)(f->zfile));
                res = gzseek((gzFile)(f->zfile), p1+offset, SEEK_SET);
            }
            f->pos = gztell((gzFile)(f->zfile));
            if (res >= 0)
                res = 0;
            break;
        case ZU_COMPRESS_BZIP :
            if (whence==SEEK_SET  &&  offset >= f->pos) {
                res = zu_bzSeekForward(f, offset-f->pos);
            }
            else if (whence==SEEK_CUR) {
                res = zu_bzSeekForward(f, offset);
            }
            else {    // BAD : reopen file
                BZ2_bzReadClose (&bzerror,(BZFILE*)(f->zfile));
                bzerror=BZ_OK;
                rewind(f->faux);
                f->pos = 0;
                f->zfile = (void *) BZ2_bzReadOpen(&bzerror,f->faux,0,0,NULL,0);
                if (bzerror != BZ_OK) {
                    BZ2_bzReadClose (&bzerror,(BZFILE*)(f->zfile));
                    fclose(f->faux);
                    f->zfile = nullptr;
                    f->ok = 0;
                }
                res = zu_bzSeekForward(f, offset);
            }
            break;
    }
    return res;
}

//-----------------------------------------------------------------
int  zu_bzSeekForward(ZUFILE *f, unsigned long nbytes_)
// for internal use
{
    unsigned long nbytes = nbytes_;
    char buf[ZU_BUFREADSIZE];
    unsigned long nbread = 0;
    int nb;
    int bzerror=BZ_OK;
    while (bzerror==BZ_OK  &&  nbytes>=ZU_BUFREADSIZE) {
        nb = BZ2_bzRead(&bzerror,(BZFILE*)(f->zfile), buf, ZU_BUFREADSIZE);
        nbytes -= nb;
        nbread += nb;
    }
    if (bzerror==BZ_OK  &&  nbytes>0) {
        nb = BZ2_bzRead(&bzerror,(BZFILE*)(f->zfile), buf, nbytes);
        nbread += nb;
    }
    f->pos += nbread;

    return nbread==nbytes_ ? 0 : -1;
}

//-----------------------------------------------------------------
void   zu_rewind(ZUFILE *f)
{
    zu_seek(f, 0, SEEK_SET);
}

//-----------------------------------------------------------------
char * zu_fgets(char *s, int size, ZUFILE *file)
{
	char *p = s;
	char buf = 0;
	int  nb = 0;
	while (buf!='\n' && nb<size-1 && zu_read(file, (void*)(&buf), 1)==1)
	{
		nb ++;
		*p = buf;
		p ++;
	}
	*p = '\0';
	if (nb == 0)
        return nullptr;
	else
		return s;
}







