/*

    GEO, Postgres GIS (Geografic Information System) frontend

    Copyright (C) 1991  TNO Institute for Perception, The Netherlands
			Written by: Tom Vijlbrief

    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 1, 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 Mass Ave, Cambridge, MA 02139, USA.


Note that parts of this distribution are taken from the ET++
distribution. See the et2.2/ subdirectories for details.

===============================================================================
Tom Vijlbrief
TNO Institute for Perception		Phone: +31 34 63 562 11
P.O. Box 23				Fax:   +31 34 63 539 77
3769 ZG  Soesterberg			E-mail: tom@izf.tno.nl
The Netherlands				or: uunet!hp4nl.nluug.nl!tnosoes!tom
===============================================================================

*/

static char *sccs_id= (sccs_id, "@(#)QuelInfo.c	3.4 2/4/92");

#include	"Class.h"
#include	"Dictionary.h"
#include	"OrdColl.h"
#include	"ObjInt.h"
#include	"Menu.h"
#include	"RegularExp.h"
#include	"Alert.h"
#include	"ByteArray.h"
#include	"ClassManager.h"
#ifdef ET25
#include	"Set.h"
#include	"MemBuf.h"
#include	"PopupItem.h"
#include	"Box.h"
#else
#include	"IO/membuf.h"
#endif
#include	"String.h"

#include	"QuelInfo.h"
#include	"Quel.h"
#include	"Query.h"
#include	"AdjTextItem.h"
#include	"ImageItem.h"
#include	"IconShape.h"

#include	"dynload.h"


extern bool	GEOBROWSE;
extern bool	GEOSIMPLE;

static const char * gpd_table= "geo_predefs";
static const char * gre_table= "geo_regexp";
static const char * gic_table= "geo_icons";
static const char * gdyn_table= "geo_dyninfo";
static const char * gedit_table= "geo_edit";
static const char * gdynattrs_table= "geo_attr";
static const char * gdynwheres_table= "geo_where";
static const char * gmenu_table= "geo_menu";

static char	curpgdb[30];
char		*curdb= curpgdb;

void SetDb(char *db)
{
  if (strcmp(db, curpgdb) != 0) {
	fprintf(stderr, "Switching from database `%s' to `%s'\n",
	  curpgdb, db);
	PQsetdb(db);
	strcpy(curpgdb, db);
  }
}

char *GetDb()
{
  return curpgdb;
}

void MenuEnableAll(Menu *m)
{
    m->GetCollection()->ForEach(VObject,Enable)(TRUE, FALSE);
}


int NoDefMenu::Show(Point p, VObject *fp) {
#ifdef ET25
    int retval= Menu::Pulldown(p, fp);
    SetSelectedItem(0);
#else
    int retval= Menu::Show(p, fp);
    SetNoSelection();
#endif
    return retval;
}


static char *GlueRelAttr(char *rel, char *attr, char *result)
{
  sprintf(result, "%s.%s", rel, attr);
  return result;
}



DynAttrInfo::DynAttrInfo(char *rel, char *attr, char *func, char *file):
  ByteArray(""), dynfunc(strsave(func)), dynfile(strsave(file))
{
  char s[1000];
  GlueRelAttr(rel,attr,s);
  SetString(s);
}


DynAttrInfo::~DynAttrInfo()
{
  SafeDelete(dynfunc);
  SafeDelete(dynfile);
}


EditInfo::EditInfo(char *r, char *func, char *file):
  dynfunc(strsave(func)), dynfile(strsave(file)), (r)
{}


EditInfo::~EditInfo()
{
  SafeDelete(dynfunc);
  SafeDelete(dynfile);
}


DynInfo::DynInfo(char *r, char *attr, char *bboxattr, char *func,
	char *file, char *oid, bool isbin):
  dynattr(strsave(attr)), dynbbox(bboxattr ? strsave(bboxattr): bboxattr),
  dynfunc(strsave(func)), dynfile(strsave(file)),
  dynoid(oid ? strsave(oid): oid), is_bin(isbin), (r)
{}


char * DynInfo::CheckNone(char *a) const
{
    return a == 0 || a[0] == '\0' || strcmp(a, "None") == 0 ?
      0: a;
}


DynInfo::~DynInfo()
{
  SafeDelete(dynattr);
  SafeDelete(dynfunc);
  SafeDelete(dynfile);
  SafeDelete(dynbbox);
  SafeDelete(dynoid);
}


OprInfo::OprInfo(char *s, char k, int rt, int al, int ar, char *code):
	rettype(rt), kind(k), argl(al), argr(ar), (s)
{
  if (code)
    oprcode= strsave(code);
  nargs= kind == 'b' ? 2: 1;
}

PredefInfo::PredefInfo(char *s, int t, char *tr):
  type(t), (s)
{
  tree= strsave(tr);
}

PredefInfo::~PredefInfo()
{
  delete tree;
}

class TypeInfo: public ByteArray {
  int	type;
public:
  TypeInfo(char *s, int t): type(t), (s) {}

  int GetType() { return type; }
};


#ifdef ET25
DictionaryIterator *QuelInfo::GetTableKeyIter()
    { return new DictionaryIterator(tables); }
#else
Iterator *QuelInfo::GetTableIter()
    { return new DictIterKeys(tables); }
#endif

Iterator *QuelInfo::GetAttrIter(char *table)
#ifdef ET25
    { return GetCol(table)->MakeIterator(FALSE); }
#else
    { return GetCol(table)->MakeReversedIterator(); }
#endif

int QuelInfo::GetNrAttr(char *table)
    { return GetCol(table)->Size(); }

bool QuelInfo::TableExists(char *table)
    { return GetCol(table) != 0; }

int QuelInfo::GetNrTables()
    { return tables ? tables->Size(): 0; }

Iterator *QuelInfo::GetPredefIter()
    { FillPredefs(); return predefs->MakeIterator(); }

Iterator *QuelInfo::GetTypeIter()
    { FillTypes(); return types->MakeIterator(); }

Iterator *QuelInfo::GetProcIter()
    { FillProcs(); return procs->MakeIterator(); }

Iterator *QuelInfo::GetOprIter()
    { FillOprs(); return oprs->MakeIterator(); }

#ifdef ET25
DictionaryIterator *QuelInfo::GetBitmapValueIter()
    { FillBitmaps(); return new DictionaryIterator(bitmaps); }

DictionaryIterator *QuelInfo::GetDynValueIter()
    { FillDyns(); return new DictionaryIterator(dyns); }

DictionaryIterator *QuelInfo::GetEditValueIter()
    { FillEdits(); return new DictionaryIterator(edits); }
#else
Iterator *QuelInfo::GetBitmapIter()
    { FillBitmaps(); return new DictIterValues(bitmaps); }

Iterator *QuelInfo::GetDynIter()
    { FillDyns(); return new DictIterValues(dyns); }

Iterator *QuelInfo::GetEditIter()
    { FillEdits(); return new DictIterValues(edits); }
#endif

Iterator *QuelInfo::GetMenuIter()
    { FillMenu(); return dynmenu->MakeIterator(); }



static Dictionary	*dynfunc_dict;
static Set		*dynfile_set;

void *GetFuncPtr(char *filen, char *funcn)
{
  void		*fp;
  ObjInt	*oi;
  ByteArray	*func= new ByteArray(funcn);

  if (!dynfunc_dict) {
    dynfunc_dict= new Dictionary;
    dynfile_set= new Set;
    oi= 0;
  } else
    oi= (ObjInt*) dynfunc_dict->AtKey(func);

  if (!oi) {
    char	expfilen[1000];
    char	*slashp= 0;
    char	*env;

    // Expand $ENVIRONMENT:
    if (filen[0] == '$' && (slashp= index(filen, '/'))) {
      *slashp= '\0';
      if (env= getenv(filen+1)) {
	strcpy(expfilen, env);
	*slashp= '/';
	strcat(expfilen, slashp);
      } else
	slashp= 0;
    }
    if (!slashp)
      strcpy(expfilen, filen);

    ByteArray	*file= new ByteArray(expfilen);
    int		dont_load;

    if (dynfile_set->Contains(file)) {
      delete file;
      dont_load= 1;
    } else {
      dynfile_set->Add(file);
      dont_load= 0;
    }

    fp= (void*) dyn_load(expfilen, funcn, dont_load);
    oi= new ObjInt(int(fp));
#ifdef ET25
    dynfunc_dict->PutAtKey(oi, func);
#else
    dynfunc_dict->AtKeyPut(func, oi);
#endif
  } else {
    delete func;
    fp= (void*) int(*oi);
  }
  
  return fp;
}


pqsfcp QuelInfo::GetDynFunc(ByteArray *is, DynInfo *di)
{
  if (!dyn_dict)
	dyn_dict= new Dictionary;

  pqsfcp	fp;
  ObjInt	*oi= (ObjInt*) dyn_dict->AtKey(is);

  if (!oi) {
    fp= (pqsfcp) GetFuncPtr(di->File(), di->Func());
    oi= new ObjInt(int(fp));
#ifdef ET25
    dyn_dict->PutAtKey(oi, new ByteArray(is->Str()));
#else
    dyn_dict->AtKeyPut(new ByteArray(is->Str()), oi);
#endif
  } else
    fp= pqsfcp(int(*oi));
  
  return fp;
}


char *QuelInfo::GetTypeStr(int typeoid)
{
  FillTypes();

  ObjInt	*oi= new ObjInt(typeoid);
  TypeInfo	*ti= (TypeInfo*) nr2type->AtKey(oi);
  char		*r;

  r= ti ? ti->Str(): 0;
  delete oi;
  return r;
}


int QuelInfo::GetTypeOid(char *s)
{
  FillTypes();

  ByteArray	*l= new ByteArray(s);
  int		oid;
  ObjInt	*oi= (ObjInt*) type2nr->AtKey(l);

  if (oi)
	oid= *oi;
  else
	oid=0;
  delete l;
  return oid;
}


#if 0

// Not (yet) used

Menu *QuelInfo::GetTypesMenu()
{
  Menu *m= new Menu("Constant", TRUE, 0, 4);
  m->SetFlag(eMenuNoScroll);

  Iter	next(GetTypeIter());
  TypeInfo	*ti;

  for (ntypes= 0; ti= (TypeInfo*) next(); ntypes++) {
    char s[1000];
    sprintf(s, "%s...", ti->AsString());
    m->AppendItem(s, ntypes+cIdFirstType);
  }

  return m;
}
#endif


int OprInfo::FirstChildType()
{
  switch (GetNrArgs()) {
    case 1:
      if (GetKind() == 'l')
	return GetArgR();
      else
	return GetArgL();
    case 2:
      return GetArgL();
    default:
      abort();
  }
  return 0; // Avoid warning
}


bool QuelInfo::HideType(OprInfo *oi)
{
  extern char	*GEOHIDETYPE;
  static Set	*hide;

  if (! GEOHIDETYPE)
    return FALSE;

  if (!hide) {
    hide= new Set;

    char *p= GEOHIDETYPE, *pi;

    do {
      if (pi= index(p, ','))
        *pi= '\0';
      hide->Add(new ObjInt(GetTypeOid(p)));
      if (pi)
        p= pi+1;
    } while (pi);
  }

  ObjInt *ret= new ObjInt(oi->GetRetType());
  ObjInt *argl= new ObjInt(oi->GetArgL());
  ObjInt *argr= new ObjInt(oi->GetArgR());

  bool retval= hide->Contains(ret)
	|| hide->Contains(argl)
	|| hide->Contains(argr);

  delete ret;
  delete argl;
  delete argr;

  return retval;
}


Menu *QuelInfo::GetOprMenu(int type, int childtype)
{
  NoDefMenu *m= new NoDefMenu("Operator", FALSE, 0, 4);
  m->SetFlag(eMenuNoScroll);
#ifdef ET25
#else
  m->SetGap(Point(20, 1));
#endif

#if 0
  if (GEOSIMPLE)
#endif
    m->AppendItem("-", 0); // Makes an accidental select less likely
			   // for people with bad motoric skills...

  Iter		next(GetOprIter());
  OprInfo	*oi;
  int		i;
  int		oldtype= -1;

#if 0
  if (type == GetTypeOid("dt"))
    type= GetTypeOid("abstime");
#endif

  for (i= nopr= 0; oi= (OprInfo*) next(); i++) {
	if ((!childtype && oi->GetRetType() == type)
	|| (childtype && childtype == oi->FirstChildType())) {

	  if (HideType(oi))
	    continue;

	  char	s[1000];
	  char	s2[1000];
	  int	newtype;
	  char	*os= oi->Str();

	  strcpy(s, os);

	  switch (oi->GetNrArgs()) {
	    case 1:
		if (oi->GetKind() == 'l')
		  sprintf(s2, " (%s %s)", os, GetTypeStr(newtype=
		    oi->GetArgR()));
		else
		  sprintf(s2, " (%s %s)", GetTypeStr(newtype=
		    oi->GetArgL()), os);
		break;
	    case 2:
		sprintf(s2, " (%s %s %s)", GetTypeStr(newtype= oi->GetArgL()),
		  os, GetTypeStr(oi->GetArgR()));
		break;
	    default:
		abort();
	  }
	  if (newtype != oldtype) {
		if (oldtype != -1)
		  m->AppendItem("-", 0);
		oldtype= newtype;
	  }
	  AdjTextItem *ati= new AdjTextItem(s, gSysFont, Point(4,0));
	  ati->SetMinWidth(35);

#ifdef ET25
	  HBox *vp= new HBox(gPoint4, eVObjHCenter,
	    ati,
	    new TextItem(s2, gSysFont, Point(4,0)),
	    0);
#else
	  VObjectPair *vp= new VObjectPair(
	    ati,
	    new TextItem(s2, gSysFont, Point(4,0)), Point(5,0));
#endif
	  vp->SetId(i+cIdFirstOperator);
	  m->GetCollection()->Add(vp);
	  nopr++;
	}
  }

  if (nopr < 8)
    m->SetCols(1);
  else if (nopr < 16)
    m->SetCols(2);
  else if (nopr < 32)
    m->SetCols(3);

  return m;
}


#if 0
// Not yet used

Menu *QuelInfo::GetProcMenu(int type)
{
  Menu *procsmenu= new Menu("Procedure", TRUE, 0, 4);
  procsmenu->SetFlag(eMenuNoScroll);

  Iter	next(GetProcIter());
  ProcInfo	*pi;
  int		i;

  for (i= nproc= 0; pi= (ProcInfo*) next(); i++) {
	if (pi->GetRetType() == type) {
	  procsmenu->AppendItem(pi->Str(), i+cIdFirstProc);
	  nproc++;
	}
  }

  return procsmenu;
}
#endif

bool QuelInfo::HasAttr(char *table, char *attr, char *types)
{
  Iter		next(GetAttrIter(table));
  AttrInfo	*ai;
  int		typeoid= types ? GetTypeOid(types): 0;

  while (ai= (AttrInfo*) next()) {
    if (strcmp(ai->Str(), attr) == 0
    && (typeoid == 0 || ai->GetType() == typeoid))
	return TRUE;
  }
  return FALSE;
}


void QuelInfo::GetAttrTypeStr(char *table, char *attr, char *result)
{
  Iter		next(GetAttrIter(table));
  AttrInfo	*ai;

  while (ai= (AttrInfo*) next()) {
    if (strcmp(ai->Str(), attr) == 0) {
	strcpy(result, GetTypeStr(ai->GetType()));
	return;
    }
  }
}


bool QuelInfo::IsUserTabName(char *s)
{
  return ! (strncmp(s, "pg_", 3) == 0 || strncmp(s, "geo_", 4) == 0);
}

int QuelInfo::GetNrUserTables()
{
  Iter		next(tableoc->MakeIterator());
  ByteArray	*ti;
  int		tab_cnt= 0;

  for (; ti= (ByteArray*) next();) {
	char	*str= ti->Str();

	if (! IsUserTabName(str))
	  continue;
	tab_cnt++;
  }
  return tab_cnt;
}

void QuelInfo::FillTablesAttrMenu(Menu *tam, bool hide_pg)
{
  Iter		next(tableoc->MakeIterator());
  ByteArray	*ti;

  for (; ti= (ByteArray*) next();) {
	char	*str= ti->Str();

	if (hide_pg && ! IsUserTabName(str))
	  continue;

	Menu *m= GetAttrMenu(str);
	tam->AppendMenu(m);
  }
}

Menu *QuelInfo::GetTablesAttrMenu(bool hide_pg)
{
  Menu *tablesattrmenu= new NoDefMenu("Table", FALSE);

  FillTablesAttrMenu(tablesattrmenu, hide_pg);
  return tablesattrmenu;
}


void QuelInfo::SetTablesAttrMenu(Menu *m, bool hide_pg, int type,
	bool is_leaf)
{
  Collection *mc= m->GetCollection();
  if (mc->Size() != (hide_pg ? GetNrUserTables(): GetNrTables())) {
    m->RemoveAllItems(TRUE);
    FillTablesAttrMenu(m, hide_pg);
  }

  Iter		next(mc->MakeIterator());
  MenuItem	*mi;

  while (mi= (MenuItem*) next()) {
#ifdef ET25
    int nv= SetAttrMenu(mi->MyMenu(), mi->MyMenu()->GetName(),
#else
    int nv= SetAttrMenu(mi->ContMenu(), mi->ContMenu()->GetTitle()->AsString(),
#endif
      type, is_leaf, FALSE);
    mi->Enable(nv > 0, FALSE);
  }
}


Menu *QuelInfo::GetAttrMenu(char *table)
{
  Menu		*n= new NoDefMenu(table, FALSE, 0, 2);
  
  n->SetFlag(eMenuNoScroll);  
  FillAttrMenu(n, table);
  return n;
}


void QuelInfo::FillAttrMenu(Menu *n, char *table)
{
  // Make robust for deleted relation...
  ByteArray	*l= new ByteArray(table);
  OrdCollection	*the_oc= (OrdCollection*) tables->AtKey(l);
  if (!the_oc) {
    delete l;
    return;
  }

#ifdef ET25
  Iter		next(the_oc, FALSE);
#else
  RevIter	next(the_oc);
#endif

  delete l;

  AttrInfo	*ai;
  for (int ac= 0; ai= (AttrInfo*) next(); ac++) {
    TextItem *ti= new TextItem(ai->Str());
    ti->SetId(cIdFirstTable + (cIdFirstAttr * ai->GetNumber()) + ac);
    n->Append(ti);
  }
}


int QuelInfo::SetAttrMenu(Menu *m, char *table, int type,
	bool is_leaf, bool recreate)
{
  if (recreate) {
    m->RemoveAllItems(TRUE);
    FillAttrMenu(m, table);
  }

  Iter		next(GetAttrIter(table));
  AttrInfo	*ai;
  int		ok_cnt= 0;

  for (int i= 0; ai= (AttrInfo*) next(); i++) {
    bool is_ok= (is_leaf || ai->GetType() == type);
    if (is_ok)
      ok_cnt++;
    ((VObject*)m->GetCollection()->At(i))->Enable(is_ok, FALSE);
  }
  return ok_cnt;
}


OrdCollection *QuelInfo::GetCol(char *table)
{
  FillTables();
  ByteArray *l= new ByteArray(table);
  OrdCollection *oc= (OrdCollection*) tables->AtKey(l);
  delete l;
  return oc;
}



MetaImpl0(QuelInfo);

QuelInfo::QuelInfo(char *db)
{
  db_name= strsave(db);
  FillTables();
  FillMenu();
}


QuelInfo::~QuelInfo()
{
  delete db_name;

  EmptyInfo();
}


void QuelInfo::EmptyInfo()
{
  if (tables) {
    tables->FreeAll();
    SafeDelete(tables);
  }
  SafeDelete(tableoc);
  SafeDelete(nr2type);
  SafeDelete(type2nr);
  if (types) {
    types->FreeAll();
    SafeDelete(types);
  }
  if (dyns) {
    dyns->FreeAll();
    SafeDelete(dyns);
  }
  if (dyn_wheres) {
    dyn_wheres->FreeAll();
    SafeDelete(dyn_wheres);
  }
  if (dyn_attrs) {
    dyn_attrs->FreeAll();
    SafeDelete(dyn_attrs);
  }
  if (edits) {
    edits->FreeAll();
    SafeDelete(edits);
  }
  if (dyn_dict) {
    dyn_dict->FreeAll();
    SafeDelete(dyn_dict);
  }

#if 0	// May not be deleted,
	// because dyn loadable file may never be loaded twice
  if (dynfunc_dict) {
    dynfunc_dict->FreeAll();
    SafeDelete(dynfunc_dict);
  }
  if (dynfile_set) {
    dynfile_set->FreeAll();
    SafeDelete(dynfile_set);
  }
#endif

  SafeDelete(procs);
  SafeDelete(bitmaps);
  SafeDelete(bitmapoc);
}


void QuelInfo::RefetchInfo()
{
  EmptyInfo();

  FillTables();
  FillMenu();
}


void QuelInfo::FillAll()
{
  FillTables();
  FillProcs();
  FillOprs();
  FillTypes();
  FillRegexps();
  FillPredefs();
  FillBitmaps();
  FillDyns();
  FillEdits();
  FillDynAttrs();
  FillDynWheres();
  FillMenu();
}


void QuelInfo::FillMenu()
{
  if (GEOBROWSE)
      return;

  if (dynmenu == 0) {

    SetDb(db_name);
    dynmenu= new OrdCollection;
    if (TableExists(gmenu_table)) {
	QueryIter	qi(gmenu_table);
	char	**a;

	while (a= qi.Next()) {
	  void *fp= GetFuncPtr(a[0], a[1]);
	  if (!fp)
	    continue;
	  dynmenu->Add(new ObjInt(int(fp)));
	}
    } else
	fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gmenu_table, db_name);
  }
}


void QuelInfo::FillEdits()
{
  if (GEOBROWSE)
    return;

  if (edits == 0) {
    SetDb(db_name);
    edits= new Dictionary;
    if (TableExists(gedit_table)) {
	QueryIter	qi(gedit_table, 0, 0,
			  "relname", "dynfunc", "dynfile", 0);
	char	**a;

	while (a= qi.Next()) {
#ifdef ET25
	  edits->PutAtKey(new EditInfo(
	    a[0], a[1], a[2]), new ByteArray(a[0]));
#else
	  edits->AtKeyPut(new ByteArray(a[0]), new EditInfo(
	    a[0], a[1], a[2]));
#endif
	}
    } else
	fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gedit_table, db_name);
  }
}


EditInfo * QuelInfo::GetEditInfo(char *rel)
{
  if (GEOBROWSE)
    return 0;

  FillEdits();

  ByteArray	*l= new ByteArray(rel);
  EditInfo	*ei= (EditInfo*) edits->AtKey(l);

  delete l;
  return ei;
}


void QuelInfo::FillDynWheres()
{
  if (GEOBROWSE)
    return;

  if (dyn_wheres == 0) {
    SetDb(db_name);
    dyn_wheres= new Dictionary;
    if (TableExists(gdynwheres_table)) {
	QueryIter	qi(gdynwheres_table, 0, 0,
			  "relname", "dynfunc", "dynfile", 0);
	char	**a;

	while (a= qi.Next()) {
#ifdef ET25
	  dyn_wheres->PutAtKey(new DynWhereInfo(
	    a[0], a[1], a[2]), new ByteArray(a[0]));
#else
	  dyn_wheres->AtKeyPut(new ByteArray(a[0]), new DynWhereInfo(
	    a[0], a[1], a[2]));
#endif
	}
    } else
	fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gdynwheres_table, db_name);
  }
}


DynWhereInfo * QuelInfo::GetDynWhereInfo(char *rel)
{
  if (GEOBROWSE)
    return 0;

  FillDynWheres();

  ByteArray	*l= new ByteArray(rel);
  DynWhereInfo	*di= (DynWhereInfo*) dyn_wheres->AtKey(l);

  delete l;
  return di;
}


void QuelInfo::FillDynAttrs()
{
  if (GEOBROWSE)
    return;

  if (dyn_attrs == 0) {
    SetDb(db_name);
    dyn_attrs= new Dictionary;
    if (TableExists(gdynattrs_table)) {
	QueryIter	qi(gdynattrs_table, 0, 0,
			  "relname", "relattr", "dynfunc", "dynfile", 0);
	char	**a;

	while (a= qi.Next()) {
	  char s[1000];
	  GlueRelAttr(a[0], a[1], s);
#ifdef ET25
	  dyn_attrs->PutAtKey(new DynAttrInfo(
	    a[0], a[1], a[2], a[3]), new ByteArray(s));
#else
	  dyn_attrs->AtKeyPut(new ByteArray(s), new DynAttrInfo(
	    a[0], a[1], a[2], a[3]));
#endif
	}
    } else
	fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gdynattrs_table, db_name);
  }
}


DynAttrInfo * QuelInfo::GetDynAttrInfo(char *rel, char *attr)
{
  if (GEOBROWSE)
    return 0;

  FillDynAttrs();

  char s[1000];
  GlueRelAttr(rel, attr, s);
  ByteArray	*l= new ByteArray(s);
  DynAttrInfo	*di= (DynAttrInfo*) dyn_attrs->AtKey(l);

  delete l;
  return di;
}



void QuelInfo::FillDyns()
{
  if (GEOBROWSE)
    return;

  if (dyns == 0) {
    SetDb(db_name);
    dyns= new Dictionary;
    if (TableExists(gdyn_table)) {
	QueryIter	qi(gdyn_table);
	char	**a;

	while (a= qi.Next()) {
#ifdef ET25
	  dyns->PutAtKey(new DynInfo(
	    a[0], a[1], a[2], a[3], a[4], a[5],
	    a[6] ? a[6][0]=='t': FALSE), new ByteArray(a[0]));
#else
	  dyns->AtKeyPut(new ByteArray(a[0]), new DynInfo(
	    a[0], a[1], a[2], a[3], a[4], a[5],
	    a[6] ? a[6][0]=='t': FALSE));
#endif
	}
    } else
#if 0
	ShowAlert(eAlertNote,
	  "@B%s@P is not an existing relation\n", gdyn_table);
#else
	fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gdyn_table, db_name);
#endif
  }
}


DynInfo * QuelInfo::GetDynInfo(char *rel)
{
  if (GEOBROWSE)
    return 0;

  FillDyns();

  ByteArray	*l= new ByteArray(rel);
  DynInfo	*di= (DynInfo*) dyns->AtKey(l);

  delete l;
  return di;
}



Object * QuelInfo::LoadPredef(int id)
{
  Object	*obj;
#ifdef ET25
  MemBuf	mb;
  IStream	from(&mb);
#else
  membuf	mb;
  istream	from(&mb);
#endif
  PredefInfo	*pd= (PredefInfo*) predefs->At(id - cIdFirstPredef);
  char		*s= pd->GetTree();

  mb.sputn(s, strlen(s)); 

#ifdef ET23
  from >> obj;
#else
  gClassManager->Reset();
  from >> obj;
  int status;
  if (status= gClassManager->Reset()) {
      Error("LoadPredef", "status %d", status);
      return 0;
  }
#endif
  return obj;
}


void EscSpecial(register char *in, register char *out, register int len)
{
  if (len == -1)
	len= strlen(in);

  for (int i= 0; i < len; i++, in++) {
	register char c= *in;

	switch (c) {
	case '\n':
	  c= ' ';
	  break;
	case '\"':
	  *out++= '\\';
	  break;
	case '\\':
	  *out++= c;
	  break;
	}
	*out++= c;
  }
  *out= '\0';
}


void QuelInfo::SavePredef(char *name, int type, Object *tree)
{
  if (!TableExists(gpd_table)) {
    if (GEOBROWSE)
      fprintf(stderr, "%s is not an existing relation\n", gpd_table);
    else
      ShowAlert(eAlertNote,
      "@B%s@P is not an existing relation\n",
	gpd_table);
    return;
  }

#ifdef ET25
  MemBuf	mb;
  OStream	to(&mb);

  to << tree NL;
  to.flush();
#else
  membuf	mb;
  ostream	to(&mb);

  gClassManager->Reset();
  gInPrintOn= TRUE;
  to << tree NL;
  to.flush();
  gInPrintOn= FALSE;
  gClassManager->Reset();
#endif

  const int	MaxTree= 7000;
  char		s[MaxTree+1000];
  char		m[MaxTree];
  int		sz= mb.size();

  if (sz >= MaxTree) {
	fprintf(stderr, "QuelInfo::SavePredef: tree too large!\n");
	abort();
  }

  EscSpecial(mb.Base(), m);

  sprintf(s,
	"append %s(name=\"%s\"::text, typeoid=\"%d\"::oid, tree=\"%s\"::text)",
	gpd_table, name, type, m);

  QuelExec(s);

  predefs->FreeAll();
  SafeDelete(predefs);
  FillPredefs();
}


void QuelInfo::FillPredefs()
{
  if (predefs == 0) {
    SetDb(db_name);
    predefs= new OrdCollection;
    if (TableExists(gpd_table)) {
	QueryIter	qi(gpd_table);
	char	**a;

	while (a= qi.Next()) {
	  predefs->Add(new PredefInfo(a[0], atoi(a[1]), a[2]));
	}
    } else {
	if (GEOBROWSE)
	  fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gpd_table, db_name);
	else
	  ShowAlert(eAlertNote,
	  "@B%s@P is not an existing relation\nin database: %s\n",
	    gpd_table, db_name);
    }
  }
}


Menu * QuelInfo::GetPredefMenu()
{
  Menu *m= new NoDefMenu("Predefined Queries");
  m->SetFlag(eMenuNoScroll);

  Iter	next(GetPredefIter());
  PredefInfo	*pi;

  m->AppendItem("Save (sub)tree...", cIdSavePredef);
  m->AppendItem("-", 0);
  for (int i= 0; pi= (PredefInfo*) next(); i++) {
    m->AppendItem(pi->Str(), i+cIdFirstPredef);
  }

  return m;
}


void QuelInfo::SetPredefMenu(Menu *m, int type)
{
  m->DisableAll();

  Iter		next(GetPredefIter());
  PredefInfo	*pi;

  for (int i= 2; pi= (PredefInfo*) next(); i++) {
    if (pi->GetType() == type)
      ((VObject*)m->GetCollection()->At(i))->Enable(TRUE, FALSE);
  }
}


void QuelInfo::FillRegexps()
{
  if (regexps == 0) {
    SetDb(db_name);
    regexps= new Dictionary;
    if (TableExists(gre_table)) {
	QueryIter	qi(gre_table);
	char	**a;

	while (a= qi.Next()) {
	  RegularExp	*re= new RegularExp(a[1]);

	  if (re->GetExprState()) {
	    ShowAlert(eAlertNote,
	      "@B%s@P is not a valid regular expression:\n@B%s@P",
	      a[1], re->GetExprState());
	    continue;
	  }

	  // Cleanup leading or trailing spaces:
	  char str[100];
	  if (sscanf(a[0], "%s", str) != 1) {
	    fprintf(stderr, "Ignored entry in %s: `%s'\n", gre_table, a[0]);
	    continue;
	  }

#ifdef ET25
	  regexps->PutAtKey(re, new ByteArray(str));
#else
	  regexps->AtKeyPut(new ByteArray(str), re);
#endif
	}
    } else
	if (GEOBROWSE)
	  fprintf(stderr, "%s is not an existing relation in database: %s\n",
		gre_table, db_name);
	else
	  ShowAlert(eAlertNote,
	  "@B%s@P is not an existing relation\nin database: %s\n",
	    gre_table, db_name);
  }
}


bool QuelInfo::TypeMatchRE(char *s, int t, char *tab, char *attr)
{
  char str[100];

  if (tab) {
    strcpy(str, tab);
    strcat(str, ".");
    strcat(str, attr);
  } else
    sprintf(str, "%d", t);

  FillRegexps();

  //ObjInt	*oi= new ObjInt(t);
  ByteArray	*bs= new ByteArray(str);
  RegularExp	*re= (RegularExp*) regexps->AtKey(bs);
  bool		retval= TRUE;

  int len= strlen(s);
  if (re && len != re->Match(s, 0, len)) {
    retval= FALSE;
    ShowAlert(eAlertNote, "`@B%s@P' is not a valid `@B%s@P'", s,
      tab ? str: GetTypeStr(t));
  }

  delete bs;
  return retval;
}


void QuelInfo::FillTables()
{
  if (tables)
	return;

  SetDb(db_name);
  tables= new Dictionary;
  tableoc= new OrdCollection;

  char		**a;
  OrdCollection	*oc= 0;
  int		tc= -1;
  char		oldname[100];
  QueryIter	qi("pg_attribute", 0,
    "pg_attribute.attnum > 0 and pg_attribute.attrelid = pg_class.oid sort by relname");

  qi.SetFields(FALSE, "pg_class.relname", "pg_attribute.attname",
	"pg_attribute.atttypid", 0);

  strcpy(oldname, "");
  while (a= qi.Next()) {
    if (strcmp(oldname, a[0]) != 0) {
	strcpy(oldname, a[0]);
	oc= new OrdCollection;
	ByteArray *l;
#ifdef ET25
	tables->PutAtKey(oc, l= new ByteArray(a[0]));
#else
	tables->AtKeyPut(l= new ByteArray(a[0]), oc);
#endif
	tableoc->Add(l);
	tc++;

	oc->Add(new AttrInfo("tmax", 20, tc));
	oc->Add(new AttrInfo("tmin", 20, tc));
	oc->Add(new AttrInfo("oid", 26, tc));
    }
    oc->Add(new AttrInfo(a[1], a[2] ? atoi(a[2]): 0, tc));
  }
}


AttrInfo *QuelInfo::GetAttrInfo(int id, char **tabname)
{
  int		it= (id - cIdFirstTable) / cIdFirstAttr;
  int		ia= (id - cIdFirstTable) % cIdFirstAttr;
  OrdCollection	*oc= GetCol(*tabname= ((ByteArray*) tableoc->At(it))->Str());

  return (AttrInfo*) oc->At(oc->Size()-1-ia);
}

void QuelInfo::FillTypes()
{
  if (types != 0)
	return;

  SetDb(db_name);
  types= new OrdCollection;
  nr2type= new Dictionary;
  type2nr= new Dictionary;

  char		**a;
  QueryIter	qi("pg_type", 0, 0, "typname", "oid", 0);

  while (a= qi.Next()) {
    int		typeoid;
    TypeInfo	*ti;
    ObjInt	*oi;

    types->Add(ti= new TypeInfo(a[0], typeoid= atoi(a[1])));
#ifdef ET25
    nr2type->PutAtKey(ti, oi= new ObjInt(typeoid));
    type2nr->PutAtKey(oi, ti);
#else
    nr2type->AtKeyPut(oi= new ObjInt(typeoid), ti);
    type2nr->AtKeyPut(ti, oi);
#endif
  }
}


static short MapIconImage[][64/sizeof(short)/8 * 64] = {
{
#   include "images/cross.image"
},
{
#   include "images/circle.image"
},
{
#   include "images/diamond.image"
},
{
#   include "images/star.image"
}
};

static const int	NrImages= sizeof(MapIconImage)/sizeof(MapIconImage[0]);

void QuelInfo::FillBitmaps()
{
  if (GEOBROWSE)
	return;

  if (bitmaps != 0)
	return;

  SetDb(db_name);
  bitmaps= new Dictionary;
  bitmapoc= new OrdCollection;

  for (int loop= 0; 1; loop++) {
   if (!TableExists(gic_table) || loop == 1) {
    if (loop == 0)
     ShowAlert(eAlertNote,
     "@B%s@P is not an existing relation\nin database: %s\nUsing default icons",
     gic_table, db_name);
    for (int i= 0; i < NrImages; i++) {
	BitmapInfo *bi= new BitmapInfo(i, 32, 32, 64, 64,
		(char*) MapIconImage[i], FALSE);
	bitmapoc->Add(bi);
#ifdef ET25
	bitmaps->PutAtKey(bi, new ObjInt(i));
#else
	bitmaps->AtKeyPut(new ObjInt(i), bi);
#endif
        nbitmap++;
    }
    return;

   } else {

    char		**a;
    QueryIter	qi(gic_table, 0, 0, "iconind", "usex",
 	"usey", "physx", "physy", "hexwords", 0);

    while (a= qi.Next()) {
      int nr= atoi(a[0]);
      int ux= atoi(a[1]);
      int uy= atoi(a[2]);
      int px= atoi(a[3]);
      int py= atoi(a[4]);
      BitmapInfo *bi= new BitmapInfo(nr, ux, uy, px, py, a[5], TRUE);
      bitmapoc->Add(bi);
#ifdef ET25
      bitmaps->PutAtKey(bi, new ObjInt(nr));
#else
      bitmaps->AtKeyPut(new ObjInt(nr), bi);
#endif
      nbitmap++;
    }
    if (nbitmap > 0)
	return;
    ShowAlert(eAlertNote,
      "@B%s@P is empty!\nUsing default icons.",
	gic_table);
   }
  }
}

BitmapInfo *QuelInfo::GetBitmapInfo(int nr)
{
  ObjInt	*oi= new ObjInt(nr);
  BitmapInfo	*bi= (BitmapInfo*) bitmaps->AtKey(oi);

  delete oi;
  return bi ? bi: (BitmapInfo*) bitmapoc->At(0);
}


MetaImpl0(BitmapInfo);

BitmapInfo::BitmapInfo(int n, int ux, int uy, int px, int py, char *hw, bool ishex):
	usex(ux), usey(uy), physx(px), physy(py), nr(n)
{
  short *icond;

  if (ishex) {
    int nwords= py * ((px+15)/16);
    bw= new short[nwords];
  
    for (int i= 0; i < nwords; i++) {
      sscanf(hw+4*i, "%4hx", bw+i);
    }
    icond= bw;
  } else
    icond= (short*) hw;

  Point dim(usex,usey);
  bm= NewIconBM(dim, icond);
  imitem= new ImageItem(nr, bm);
}

BitmapInfo::~BitmapInfo()
{
  SafeDelete(bw);
  delete bm;
  delete imitem;
}

void QuelInfo::FillOprs()
{
  if (oprs != 0)
	return;

  SetDb(db_name);
  oprs= new OrdCollection;

  char		**a;
  QueryIter	qi("pg_operator", 0, 0, "oprname", "oprkind",
 	"oprresult", "oprleft", "oprright", "oprcode", 0);

  // qi.SortBy("sort by oprleft, oprright, oprname");
  qi.SortBy("sort by oprleft, oprname");

  while (a= qi.Next()) {
    oprs->Add(new OprInfo(a[0], a[1][0],
	atoi(a[2]), atoi(a[3]), atoi(a[4]), a[5]));
  }
}


OprInfo *QuelInfo::GetOprInfo(int i)
{
  return (OprInfo*) oprs->At(i);
}


void QuelInfo::FillProcs()
{
  if (procs != 0)
	return;

  SetDb(db_name);
  procs= new OrdCollection;

#if 0
  char		**a;
  QueryIter	qi("pg_proc", 0, 0, "proname", "pronargs", "prorettype", 0);

  while (a= qi.Next()) {
    procs->Add(new ProcInfo(a[0], atoi(a[1]), atoi(a[2])));
  }
#endif
}


QuelInfo *GetInfo(char *db)
{
  static Dictionary 	*db_info;

  if (!db_info)
	db_info= new Dictionary;

  QuelInfo	*qinfo;
  ByteArray	*l= new ByteArray(db);

  if (!(qinfo= (QuelInfo*) db_info->AtKey(l))) {
#ifdef ET25
    db_info->PutAtKey(qinfo= new QuelInfo(db), l);
#else
    db_info->AtKeyPut(l, qinfo= new QuelInfo(db));
#endif
    return qinfo;
  }
  delete l;
  return qinfo;
}


QuelInfo *GetInfo(bool cur_db)
{
  return GetInfo(cur_db ? GetDb(): GetGeoDB());
}
