/*
** freepages: counts the number of free pages in an sqlite database.
**
** 2002-11-24	James P. Lyon
** 2003-01-04	Added sqlite_count_total_pages().
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
 */

#include <assert.h>

#include "sqlite.h"
#include "sqliteInt.h"
#include "btree.h"
#include "pager.h"

/************************************************************************/
/*
** Declarations needed from sqlite code.
*/

/*
** This is a magic string that appears at the beginning of every
** SQLite database in order to identify the file as a real database.
*/
static const char zMagicHeader[] = 
   "** This file contains an SQLite 2.1 database **";
#define MAGIC_SIZE (sizeof(zMagicHeader))

/*
** Structure holding the first page in the database.
*/
struct PageOne {
  char zMagic[MAGIC_SIZE]; /* String that identifies the file as a database */
  int iMagic;              /* Integer to verify correct byte order */
  Pgno freeList;           /* First free page in a list of all free pages */
  int nFree;               /* Number of pages on the free list */
  int aMeta[SQLITE_N_BTREE_META-1];  /* User defined integers */
};

typedef struct PageOne PageOne;

/*
** Everything we need to know about an open database
*/
struct Btree {
  Pager *pPager;        /* The page cache */
  BtCursor *pCursor;    /* A list of all open cursors */
  PageOne *page1;       /* First page of the database */
  u8 inTrans;           /* True if a transaction is in progress */
  u8 inCkpt;            /* True if there is a checkpoint on the transaction */
  u8 readOnly;          /* True if the underlying file is readonly */
  u8 needSwab;          /* Need to byte-swapping */
};

extern u32 swab32(u32 x);

#define SWAB32(B,X)   ((B)->needSwab? swab32(X) : (X))

/************************************************************************/
/*
** functions duplicated from sqlite because they are static scope there.
*/

#define MAGIC 0xdae37528

/*
** Get a reference to page1 of the database file.  This will
** also acquire a readlock on that file.
**
** SQLITE_OK is returned on success.  If the file is not a
** well-formed database file, then SQLITE_CORRUPT is returned.
** SQLITE_BUSY is returned if the database is locked.  SQLITE_NOMEM
** is returned if we run out of memory.  SQLITE_PROTOCOL is returned
** if there is a locking protocol violation.
*/
static int lockBtree(Btree *pBt){
  int rc;
  if( pBt->page1 ) return SQLITE_OK;
  rc = sqlitepager_get(pBt->pPager, 1, (void**)&pBt->page1);
  if( rc!=SQLITE_OK ) return rc;

  /*
  ** Do some checking to help insure the file we opened really is
  ** a valid database file. 
  */
  if( sqlitepager_pagecount(pBt->pPager)>0 ){
    PageOne *pP1 = pBt->page1;
    if( strcmp(pP1->zMagic,zMagicHeader)!=0 ||
          (pP1->iMagic!=MAGIC && swab32(pP1->iMagic)!=MAGIC) ){
      rc = SQLITE_CORRUPT;
      goto page1_init_failed;
    }
    pBt->needSwab = pP1->iMagic!=MAGIC;
  }
  return rc;

page1_init_failed:
  sqlitepager_unref(pBt->page1);
  pBt->page1 = 0;
  return rc;
}

/*
** If there are no outstanding cursors and we are not in the middle
** of a transaction but there is a read lock on the database, then
** this routine unrefs the first page of the database file which 
** has the effect of releasing the read lock.
**
** If there are any outstanding cursors, this routine is a no-op.
**
** If there is a transaction in progress, this routine is a no-op.
*/
static void unlockBtreeIfUnused(Btree *pBt){
  if( pBt->inTrans==0 && pBt->pCursor==0 && pBt->page1!=0 ){
    sqlitepager_unref(pBt->page1);
    pBt->page1 = 0;
    pBt->inTrans = 0;
    pBt->inCkpt = 0;
  }
}

/************************************************************************/

/*
** Find the number of free pages in the database.
** This requires the database to have been created with the same SQLITE_PAGE_SIZE.
** Returns -1 on error.
*/
int sqlite_count_free_pages(sqlite* db)
{
	Btree *pBtree;
	PageOne *page1;
	int nFree;
	int rc;

	assert(db);
	if (!db) return -1;

	pBtree = db->pBe;
	assert(pBtree);
	if (!pBtree) return -1;

	/*
	** We must acquire a lock to fill in the page1 info.
	*/
	rc = lockBtree(pBtree);
	if (rc != SQLITE_OK)
		return -1;

	/*
	** The PageOne structure contains the free pages list info.
	*/
	page1 = pBtree->page1;
	assert(page1);
	if (!page1) return -1;

	nFree = SWAB32(pBtree, page1->nFree);

	/*
	** Release the lock on the btree.
	*/
	unlockBtreeIfUnused(pBtree);

	return nFree;
}

/*
** Find the total number of pages in the database.
** This requires the database to have been created with the same SQLITE_PAGE_SIZE.
** Returns -1 on error.
*/
int sqlite_count_total_pages(sqlite* db)
{
	Btree *pBtree;
	Pager *pPager;
	int nTotal;
	int rc;

	assert(db);
	if (!db) return -1;

	pBtree = db->pBe;
	assert(pBtree);
	if (!pBtree) return -1;

	/* We must acquire a lock to fill in the pager info. */
	rc = lockBtree(pBtree);
	if (rc != SQLITE_OK)
		return -1;

	/* The pager structure contains the page count info. */
	pPager = pBtree->pPager;
	assert(pPager);
	if (!pPager) return -1;

	nTotal = sqlitepager_pagecount(pPager);

	/* Release the lock on the btree. */
	unlockBtreeIfUnused(pBtree);

	return nTotal;
}

/*
** Routine to test the sqlite_count_***_pages() functions.
*/
int main(int argc, char **argv)
{
	sqlite *db;
	char *zErrMsg = NULL;
	int mode = 0;
	int nFree, nTotal;

	/*
	** Validate command-line arguments.
	** argv[0] will be the application name.
	*/
	if (argc != 2 || !argv[1])
	{
		fprintf(stderr, "usage: %s database\n", argv[0]);
		return -1; /* error */
	}

	/*
	** Open the database connection. The mode flag is unused (as of 2.7.3)
	*/
	db = sqlite_open(argv[1], mode, &zErrMsg);
	if (!db)
	{
		fprintf(stderr, "%s: failed to open database '%s'.\n%s", argv[0], argv[1], zErrMsg);
		sqliteFree(zErrMsg);
		return -1; /* error */
	}

	/*
	** Find the number of total pages.
	*/
	nTotal = sqlite_count_total_pages(db);
	if (nTotal < 0)
	{
		puts("Error: unable to determine number of total pages.\n");
	}
	else
	{
		printf("total pages: %d\n", nTotal);
		printf("total bytes: %d\n", nTotal * SQLITE_PAGE_SIZE);
	}

	/*
	** Find the number of free pages.
	*/
	nFree = sqlite_count_free_pages(db);
	if (nFree < 0)
	{
		puts("Error: unable to determine number of free pages.\n");
	}
	else
	{
		printf("free pages: %d\n", nFree);
		printf("free bytes: %d\n", nFree * SQLITE_PAGE_SIZE);
	}

	sqlite_close(db);

	return (nFree > 0)? 0: -1;
}
