/*===========================================================================
  Copyright (C) 1995-2009 European Southern Observatory (ESO)
 
  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 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, write to the Free 
  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.TYPE        Module
.NAME        TZ6.C
.LANGUAGE    C
.AUTHOR      J.D.Ponz  IPG-ESO Garching
.CATEGORY    table interface (Design 2.0) low level routines.
.COMMENTS    This module contains the lower level routines handling tables:
	read/write consecutive pieces of data.
\begin{TeX}
	There are basically three access modes:
	\begin{enumerate}
        \item Mapping access: The complete file is mapped. 
        	This can be forced in TCTOPN / TCTINI routines
        	with the F\_MAP\_FORCE option.
        \item Elementary access (i/o): only what is required is read
        	in a buffer. The maximal number of buffers is the
        	configuration TBL\_TBUFS
        \item Simulated mapping 
        \end{enumerate}

\end{TeX}
.VERSION    1.0	25-Mar-1987   Implementation     JDP
.VERSION    1.1	16-Jan-1989   Include handling of record file organization
.VERSION   3.0	01-Jul-1990   	New Version with Arrays / Elementary IO
.VERSION   3.1	27-Sep-1990   	Added TBL_UMAP (Unmap a region)
.VERSION   3.2	29-Apr-1991:	Better management ot EIO mode
.VERSION   3.3	06-Jun-1991:	Corrected mapped flags in eload
.VERSION   3.4  27-JAN-92       CORRECT FLAG IN ELOAD FOR MAPPING OF BIG TABLES

 090406		last modif
------------------------------------------------------------*/

#include <midas_def.h>		/* General MIDAS Symbols	*/
#include <tblsys.h>		/* Table System parameters	*/
#include <tblerr.h>		/* List of Table Errors		*/
#include <macrogen.h>		/* General-use Macros		*/

#include <stdio.h>



static int status = 0;		/* Can be retrieved via TBL_RDst*/

static  unsigned char masks[8] = {
	0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
  };
char *osmmget();

/*=======================================================================
 *		Internal Routines
 *=======================================================================*/

#ifdef __STDC__
static int write_buffer (TABLE *tp, TABLE_BUF *bp)
#else
static int write_buffer (tp, bp)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Write to associated file the specified buffer 
.RETURNS  Status (!=0 if fails)
.REMARKS  Only useful in EIO (elementay i/o) mode.
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned		*/
	TABLE_BUF *bp;		/* MOD: The buffer concerned	*/
#endif
{
	int	status;	
  

  status = ERR_NORMAL;
  if (!bp->buf)	return(status);

  if (bp->flags & TBL__MODIFIED)
	status = SCFPUT (tp->imno, 1 + bp->offset, bp->used, (char *)bp->buf);

				/* Free the allocated space	*/
  if (status)
  	TBL_errf(status, "writing table %s (EIO mode)", tp->phname);
  else  {
	osmmfree ((char *)bp->buf);
	tp->sbuf -= bp->size;
	oscfill ((char *)bp, sizeof(TABLE_BUF), 0);	/* Zero freed buffer	*/
  }

  return(status);
}

#ifdef __STDC__
static int write_nonmapped (TABLE *tp, TABLE_BUF *bp)
#else
static int write_nonmapped (tp, bp)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Write to associated file the buffer, ONLY if NON-Mapped
.RETURNS  Status (!=0 if fails)
.REMARKS  Only useful in EIO (elementay i/o) mode.
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned		*/
	TABLE_BUF *bp;		/* MOD: The buffer concerned	*/
#endif
{
  if_not (bp->flags & TBL__MAPPED && tp->tflags & TBL__MODIFIED)
  	return(write_buffer(tp, bp));

  TBL_errf(ERR_TBLMAP, "overlap of mapped zone(s) in table: %s", 
  	tp->phname);
  return (ERR_TBLMAP);
}

#ifdef __STDC__
static char *load (TABLE *tp, int offset, int len, int flag)
#else
static char *load (tp, offset, len, flag)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Load len bytes into a table, starting at offset.
.RETURNS  Where data loaded (tp->addres + offset) / NULL if fails
.REMARKS  Only useful simulated mapping mode.
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned		*/
	int	offset;		/* IN: starting point		*/
	int	len;		/* IN: How many bytes		*/
	int	flag;		/* IN: not-zero for write	*/
#endif
{
	int	mask;
	int	i, byte, bit0, bit1, read_items;


				/* Transform offset into buffer	*/
  bit0 = offset >> TBL_LOG2BUF;	
  bit1 = (offset+len-1) >> TBL_LOG2BUF;	
  status = ERR_NORMAL;

			/* Load required buffers if necessary	*/

  for (i = bit0; (status == ERR_NORMAL) && (i <= bit1); i++) {
  	byte = i >> 3;
  	mask = masks[i&7];
  	if (tp->loaded_map[byte] & mask)	;
  	else {
  		status = SCFGET (tp->imno, 1+i*TBL_BUFITEMS, TBL_BUFITEMS, 
  			&read_items, tp->addres + (i<<TBL_LOG2BUF));
  		tp->loaded_map[byte] |= mask;
  	}
  	if (flag)
           {		/* do not set flag if only reading  (KB 000901) */
           if (tp->tflags & TBL__READONLY)
              ;
           else
              tp->modified_map[byte] |= mask;
           }
  }
  if (status)
  	TBL_errf(status, "reading table %s (MAP mode)", tp->phname);

  return(tp->addres + offset);
}

#ifdef __STDC__
static char *eload (TABLE *tp, int offset, int len, int flag)
#else
static char *eload (tp, offset, len, flag)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Elementary Load len bytes into a table, starting at offset.
.RETURNS  Where data was loaded
.REMARKS  Only useful in EIO (elementay i/o) mode.
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned		*/
	int	offset;		/* IN: starting point		*/
	int	len;		/* IN: How many bytes		*/
	int	flag;		/* IN: 1 for Write, 2 for Map 	*/
#endif
{
	TABLE_BUF ab, *abp, *bp, *hbp;
	int	upper, lower, w0, w1;
	int	W0, W1;
	int	i;
	char	can_load_more;


				/* Fill the `running' buffer	*/
  tp->iocount += 1;
  ab.iocount   = tp->iocount;
  abp = &ab;
  status = ERR_NORMAL;
	
	/* w0 & w1 are the lower & upper bounds of the zone.
	   W0 & W1 are the lower & upper bounds rounded for best i/o
	   upper & lower are limits of the loaded zone being examined
	   can_load_more is a flag used to enlarge the read
	*/

				/* Scan buffers for THE buffer	*/
				/* Starting point = previously found buffer */
  w0 = offset >> 2;	
  w1 = (offset+len-1) >> 2;	
  W0 =  (w0 >> (TBL_LOG2BUF-2)) << (TBL_LOG2BUF-2);
  W1 = ((w1 >> (TBL_LOG2BUF-2)) << (TBL_LOG2BUF-2)) |(TBL_BUFITEMS-1);
  can_load_more = (flag != TBL__MAPPED);
		/* Find out whether buffer is already loaded	*/
  hbp = tp->tbuf + tp->nbuf;		/* Highest buffer	*/
  bp  = tp->tbuf + tp->cbuf;		/* Current buffer	*/

  for (i = tp->nbuf; --i >= 0; bp++){
	if (bp == hbp)	bp = tp->tbuf;
  	choose_oldest: 
  	if (status != ERR_NORMAL) 	break;
  	if (bp->iocount < abp->iocount) abp = bp;
  	if (!bp->buf)	continue;
  	lower = bp->offset; upper = lower + bp->size - 1;
  	if (w0 >= lower) {
		if (w1 <= upper){	/* Asked zone included in buffer */
			if_not (flag  & TBL__MAPPED)	goto found_buffer;
			if (w0 == lower && w1 == upper)	goto found_buffer;
			if (bp->flags & TBL__MAPPED)	goto found_buffer;
		}
		if (w0 > upper)	{	/* Asked zone following   buffer */
			if(W0 <= upper)	can_load_more = 0;
			continue;
		}
		status = write_nonmapped(tp, bp);
		goto choose_oldest;
  	}
  	if (w1 < lower) {
		if (W1 >= lower)	can_load_more = 0;
  		continue;
  	}
  	status = write_buffer (tp, bp);
  	goto choose_oldest;
  }
  if (status)	return((char *)0);
  
  bp = abp;
  if (bp->buf)	{
	if (bp->flags & TBL__MAPPED) {		/* Must expand EIO Buffers */
		i = tp->nbuf + TBL_TBUFS;
		TBL_errf(-1, "Expand Number of Mapped Buffers to %d", tp->nbuf);
		tp->tbuf = (TABLE_BUF *)osmmexp((char *)tp->tbuf, i*sizeof(TABLE_BUF));
		bp = abp = tp->tbuf + tp->nbuf;
		oscfill ((char *)bp, TBL_TBUFS*sizeof(TABLE_BUF), 0);
		tp->nbuf = i;
	}
  	else	status = write_buffer (tp, bp);
  	if (status)	return((char *)0);
  }

				/* Allocate & Load the buffer	*/
  found_buffer:
  if (!bp->buf) {
	bp->offset = (can_load_more ? W0 : w0);
	bp->size   = 1 + (can_load_more ? W1-W0 : w1 - w0);
				/* Must rewrite buffers if not enough space */
	upper = (TBL_EIO_LIMIT >> 2) - bp->size;
	for (i = tp->nbuf, abp = tp->tbuf; 
	    (--i >= 0) && (tp->sbuf > upper); abp++) {
	    	if (!abp->buf)	continue;
	    	if (abp->flags & TBL__MAPPED) continue;
	    	status = write_buffer (tp, abp);
	    	if (status)	return((char *)0);
	}
	
	
/*	bp->buf    = (int *)osmmget (sizeof(long) * bp->size);*/
	bp->buf    = (int *)osmmget (sizeof(int) * bp->size);
        if (!bp->buf) {  
                TBL_errf(ERR_TBLMAP,"mapping table %s for %d elements",
			tp->phname, bp->size); 
                return((char *)0);              
         }
	tp->sbuf += bp->size;
  	status  = SCFGET (tp->imno, 1+bp->offset, bp->size, &bp->used, (char *)bp->buf);
  	if (status) {
  		TBL_errf(status, "reading table %s (EIO mode)", tp->phname);
	  	return((char *)0);
  	}
  }
  bp->iocount = tp->iocount;
				/* Flag loaded + Modified	*/
  bp->flags |= flag;
  if (flag == 1) bp->flags |= TBL__MODIFIED; 
  tp->cbuf  = (bp - tp->tbuf);		/* Current buffer	*/

  return((char *)(bp->buf + w0 - bp->offset));
}

/*=======================================================================
 *		Public Routines
 *=======================================================================*/

int TBL_WR(tp)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Physically write the modified parts of the table
.RETURNS Status
------------------------------------------------------------------*/
	TABLE	*tp;	/* MOD: Table concerned	*/
{
	int	status, mask;
	int	i, len, bit1;
	TABLE_BUF *bp;
  

  status = ERR_NORMAL;
  if (tp->tflags & TBL__MAPPED)		return(status);

  if (tp->tflags & TBL__EIO)	{	/* Elementary Mode	*/
  	for (i = tp->nbuf, bp = tp->tbuf; (status == ERR_NORMAL) && (--i >= 0); 
  		bp++)	status = write_buffer (tp, bp);
  	return(status);
  }

					/* Simulated Map Mode	*/
  bit1 = tp->wsize >> (TBL_LOG2BUF - 2);
  
  for (i=0, mask=0x80; (status == ERR_NORMAL) && (i <= bit1); i++) {
	if (tp->modified_map[i>>3] & mask) {
		len = (i < bit1 ? TBL_BUFITEMS : tp->wsize & (TBL_BUFITEMS-1));
		status = SCFPUT (tp->imno, 1 + (i << (TBL_LOG2BUF-2)), 
			len, tp->addres + (i<<TBL_LOG2BUF));
	}
	mask >>= 1;
	if (!mask)	mask = 0x80;
  }
  if (status)	TBL_errf(status, "writing table %s (MAP mode)", tp->phname);
  return (status);
}

int TBL_RDst ()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Retrieve the status of the last encountered error in TBLRD / TBDRDF
.RETURNS  Status (0 for OK)
-------------------------------------------------------------*/
{
  return(status);
}

char *TBL_RD (tp, offset, len)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Check len bytes in a table, starting at offset, are loaded
.RETURNS  Pointer to table of specified offset / NULL if fails
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned	*/
	int	offset;		/* IN: starting point	*/
	int	len;		/* IN: How many bytes	*/
{
	char	*p;

  	/* NOTE: status is set up by load / eload */

				/* Which Access Mode ?	*/
  if (tp->tflags & TBL__MAPPED)	return (tp->addres + offset);
  if (tp->tflags & TBL__EIO)	
      {
       p = eload(tp, offset, len, 0);
       p += offset - ((offset >> 2) << 2);
       }
  else				p = load (tp, offset, len, 0);
  	
  return (p);
}

char *TBL_RDF (tp, offset, len, flag)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Check len bytes in a table, starting at offset, are loaded.
		Set MODIFIED flag.
.RETURNS  Pointer to table of specified offset / NULL if fails
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned	*/
	int	offset;		/* IN: starting point	*/
	int	len;		/* IN: How many bytes	*/
	int	flag;		/* IN: Flag 1 (Write) / 2 (Map) */
{
	char	*p;
        char    errview[100];
			/* If file is mapped, don't care about modifications! */
  if (tp->tflags & TBL__MAPPED)	return (tp->addres + offset);

  if (flag == 1 && tp->usname) {
              sprintf(errview,"Unable to write in view: %s",tp->usname);
              SCTPUT(errview);
              SCSEPI();
              }
  if ((tp->tflags & TBL__READONLY)&&(flag !=2))	flag = 0; 
  
  if (flag == 2 && tp->tflags & TBL__READONLY) ;
  else if (flag) tp->tflags |= TBL__MODIFIED;
  if (tp->tflags & TBL__EIO)
        {
	p = eload(tp, offset, len, flag);
        p += offset - ((offset >> 2) << 2); 
        }
  else				p = load (tp, offset, len, flag);
  return (p);
}

int TBL_UMAP (tp, addr)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE  Unmap a buffer that was previously mapped.
.RETURNS  Status as 0 (OK) / -1 (Was not mapped) / Other error
-------------------------------------------------------------*/
	TABLE	*tp;		/* IN: Table concerned	*/
	char	*addr;		/* IN: Address to Unmap	*/
{
	TABLE_BUF *bp;
	int	i, status;

  if (! addr)	return(0);	/* NULL is OK	*/
  status = -1;			/* Default if buffer not found */
  for (i = tp->nbuf, bp = tp->tbuf; --i >= 0; bp++){
	if ((char *)bp->buf == addr)	{
		status = write_buffer(tp, bp);
		break;		/* Only one can exist..	*/
	}
  }

  return (status);
}
