/*

    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, "@(#)Geo.c	3.7 7/28/92");

extern "C" {
#include	"unistd.h"
#include	"fcntl.h"
#include	"time.h"
#ifdef ET25
#include	"errno.h"
#else
#include	"sys/errno.h"
#endif

#ifndef F_TLOCK
#define F_TLOCK	2
#endif

#ifndef F_TEST
#define F_TEST	3
#endif

}

#include	"Class.h"
#include	"Document.h"
#include	"PolyShape.h"
#include	"ObjList.h"
#include	"DrawDocument.h"
#include	"DrawView.h"
#include	"OverView.h"
#include	"Dictionary.h"
#include	"Set.h"
#include	"Geo.h"
#include	"CellShape.h"
#include	"Sql.h"
#include	"Alert.h"
#include	"FileDialog.h"
#include	"ObjInt.h"
#ifdef ET25
#include	"CLib.h"
#else
#include	"ClassManager.h"
#endif

static FileDialog	*filed;

static void InitFD()
{
  if (!filed)
	filed= new FileDialog;
}

bool CheckFile(char *name)
{
  InitFD();
#ifdef ET25
  int fd;
  if ((fd= CLib::Open(name, 1)) < 0)
    return FALSE;

  CLib::Close(fd);
  return TRUE;
#else
  return !filed->NotReadable(name);
#endif
}

bool CheckFileWrite(char *name)
{
  InitFD();
#ifdef ET25
  int fd;
  if ((fd= CLib::Open(name, 2)) < 0)
    return FALSE;

  CLib::Close(fd);
  return TRUE;
#else
  return !filed->NotWritable(name);
#endif
}


class LayerLockName: public Layer {
	int n_locks;
	int fd;
public:
	LayerLockName(char *s, int f= 0): (s), n_locks(1), fd(f) {}
	int IncLockCnt() { return ++n_locks; }
	int DecLockCnt() { return --n_locks; }
	int GetLockCnt() { return n_locks; }
	int GetFd() { return fd; }
};


extern "C" int lockf(int fd, int cmd, long size);

static const char LockDir[]= "/locks/";

const int	L_ERROR_SIZE=	200;
const int	HOSTNAME_SIZE=	100;

static char l_error[L_ERROR_SIZE];
static char hostname[HOSTNAME_SIZE];

static Set	*locked_files;

static void InitLockedFiles()
{
  if (locked_files)
	return;
  locked_files= new Set;
}


static const char * Lock_Name(char *name)
{
  if (!hostname[0])
	gethostname(hostname, HOSTNAME_SIZE);

  const int	LNAME_SIZE= 300;
  static char	lname[LNAME_SIZE];
  char		*pf= name, *pt;
  char		*lck_dir, lck_s[LNAME_SIZE];
  extern char	*GEOLOCKDIR;
  extern char	*GEOHOME;

  if (!(lck_dir= GEOLOCKDIR)) {
    strcpy(lck_s, GEOHOME);
    strcat(lck_s, LockDir);
    lck_dir= lck_s;
  }

  strncpy(lname, lck_dir, LNAME_SIZE-2);
  if (lname[strlen(lname)-1] != '/')
    strcat(lname, "/");

  for (pt= lname + strlen(lname); pt - lname < LNAME_SIZE-1 && *pf; pf++)
	*pt++= *pf == '/' ? ':' : *pf;
  *pt= '\0';
  return lname;
}


static char * Read_Lock(int fd)
{
  if (read(fd, l_error, L_ERROR_SIZE) < 0)
	perror("Read_Lock");
  return l_error;
}


bool file_locked(char *name, char **error_msg)
{
  int f;
  const char *lname= Lock_Name(name);

  LayerLockName layer_lock_name(lname);
  InitLockedFiles();
  if (locked_files->Contains(&layer_lock_name))
	return FALSE;

  if ((f= open(lname, 0)) < 0)
	return FALSE;
  bool ret= (lockf(f, F_TEST, 1) == -1);
  *error_msg= Read_Lock(f);
  close(f);
  return ret;
}


bool lock_file(char *name, int *fd, char **error_msg)
{
  const char *lname= Lock_Name(name);

  LayerLockName layer_lock_name(lname),
		*found_name;
  InitLockedFiles();
  if (found_name= (LayerLockName*) locked_files->Find(&layer_lock_name)) {
	found_name->IncLockCnt();
	ShowAlert(eAlertNote, "@B%s@P is already being edited by you.\nBe careful not to overwrite your changes to it!",
		name);
	return TRUE;
  }

  if ((*fd= open(lname, O_RDWR | O_CREAT, 0666)) < 0) {
	sprintf(l_error, "%s:\n%s", lname,
			errno < sys_nerr ? sys_errlist[errno]: "?");
	*error_msg= l_error;
	return FALSE;
  }

  if (lockf(*fd, F_TLOCK, 0) == -1) {
	if (errno != EACCES)
		perror("lock_file");
	*error_msg= Read_Lock(*fd);
	return FALSE;
  }

  long cur_time= time(0);
  sprintf(l_error, "%s@@%s at %s",
		getenv("USER"), hostname, ctime(&cur_time));

  int n= strlen(l_error);
  if (write(*fd, l_error, n) != n) {
	perror("lock_file");
	return FALSE;
  }

  locked_files->Add(new LayerLockName(lname, *fd));

  return TRUE;
}


static void DeleteLock(char *name)
{
  const char *lname= Lock_Name(name);

  LayerLockName layer_lock_name(lname),
		*found_name;
  InitLockedFiles();
  if (found_name= (LayerLockName*) locked_files->Find(&layer_lock_name)) {
	if (found_name->DecLockCnt() == 0) {
		if (close(found_name->GetFd()) < 0)
			perror("DeleteLock");
		delete locked_files->RemovePtr(found_name);
	}
  }
}


// MetaImpl(Geo, (TP(layers), TP(overlayers), 0));
MetaImpl(Geo, (TP(overlayers), 0));


Geo::Geo()
{
#if 0
  if (!layers)
    layers= new Dictionary;
#endif
  overlayers= new Dictionary;
}

Geo::~Geo()
{
  Iter		next(overlayers);
#ifdef ET25
  Object	*key;
  while (key= next()) {
	  Layer *l= Guard(key, Layer);
#else
  Assoc		*a;
  while (a= (Assoc *) next()) {
	  Layer *l= Guard(a->Key(), Layer);
#endif
	  DeleteLock(l->AsString());
  }

  overlayers->FreeAll();
  delete overlayers;
}


void Geo::SaveOverlays()
{
  Iter next(overlayers);

#ifdef ET25
  Object *key;
  while (key= next()) {
	Layer	*l= Guard(key, Layer);
#else
  Assoc		*a;
  while (a= (Assoc *) next()) {
	Layer	*l= Guard(a->Key(), Layer);
#endif
	char	*name= l->AsString();
	char	*err_msg;

	if (!CheckFileWrite(name)) {
		;
	} else if (file_locked(name, &err_msg)) {
	    ShowAlert(eAlertNote, 
	      "Overlay @B%s@P is locked and will not be saved:\n%s",
	      name, err_msg);
	} else {
#ifdef ET25
	      OStream os(name);

	      os << overlayers->AtKey(key);
#else
	      ostream os(name);

	      gClassManager->Reset();
	      os << a->Value();
	      gClassManager->Reset();
#endif
	}
  }
}


ObjList *Geo::GetOverlayCol(TextItem *layer, bool *new_load)
{
  Layer *t= Guard(layer, Layer);
  ObjList *l;

  if (l= (ObjList*) overlayers->AtKey(t)) {
	*new_load= FALSE;
	return l;
  } else {
    char	*name= t->AsString();

    *new_load= TRUE;
    if (!CheckFile(name)) {
	l= new ObjList;
    } else {
	int	lfd;
	char	*err_msg;

	if (!lock_file(name, &lfd, &err_msg))
	    ShowAlert(eAlertNote, 
	      "Overlay @B%s@P is locked and will not be saved:\n%s",
	      name, err_msg);

#ifdef ET25
        IStream	ol(name);
        ol >> l;
#else
        istream	ol(name); // , "r");
        gClassManager->Reset();
        ol >> l;
        gClassManager->Reset();
#endif
    }
#ifdef ET25
    overlayers->PutAtKey(l, t->DeepClone());
#else
    overlayers->AtKeyPut(t->DeepClone(), l);
#endif
    return (l);
  }
}


// ============================Layer======================

MetaImpl0(Layer);

Layer::Layer(char *s): (s, gSysFont, Point(4,0)) {}

bool Layer::IsEqual(Object *o)
{
  return strcmp(o->AsString(), AsString()) == 0;
}

unsigned long Layer::Hash()
{
  register char *p= AsString();
  register int h= 1;

  while (*p)
	h*= *p++;
  return h & (unsigned(~0) >> 1);
}


#ifdef ET25
bool DrawDocument::LoadMap(IStream &from)
#else
bool DrawDocument::LoadMap(istream &from)
#endif
{
  const char *filename= GetName(), *p;

  if (!(p= index(filename, '.')) || strcmp(p, ".rtreeboot") != 0)
	return(0);

  char	name[1000];

  while (from>>name) {
	LoadGeo(name);
  }
  LoadGeoDone();
  return(1);
}


#if 0
static ObjList* Geo::LoadLayer(TextItem *layer, char ** error)
{
  char		*p;
  char		filename[1000];
  ObjList	*newshapes= 0;

  strcpy(filename, layer->AsString());
  if (!(p= index(filename, '.'))) {
	*error= "has not a valid type extension\n(.cell, .line, .fill, .rtree, ...)";
	// ShowAlert(eAlertNote, "@B%s@P has not a valid type extension\n(.cell, .line, .fill, ...)", filename);
	return 0; // new ObjList;
  }

  if (strncmp(p+1, "cell", 4) == 0)
	newshapes= LoadCell(filename);
  else if (strncmp(p+1, "rtree", 5) == 0)
	newshapes= new ObjList; // newshapes must have a value for AtKeyPut
  else
	newshapes= LoadPolyLayer(filename);

#ifdef ET25
  layers->PutAtKeyPut(newshapes, layer);
#else
  layers->AtKeyPut(layer, newshapes);
#endif
  return newshapes;
}


static ObjList* Geo::LoadCell(char *filename)
{
  ObjList	*newshapes= new ObjList;
  char		*data= new char[500 * 500];

  for (int i= 0; i < 500*500/4; i++)
	((long*)data)[i]= random();

  newshapes->Add(new CellShape(0, 1, 50, 51, 500, 500, (short*)data));

  return newshapes;
}


static ObjList* Geo::LoadPolyLayer(char *filename)
{
  int		n;
  char		*p;
  char		name[1000];
  int		res= 0;
  ObjList	*newshapes;
  bool		polyline;
  int		col;

  polyline= ((p= index(filename, '.')) && strncmp(p+1, "line", 4) == 0);

  if (p && (p= index(p+1, '.'))) {
	col= atoi(p+1);
	*p= '\0';
  } else
	col= 2; // gColor_len-1;

  newshapes= new ObjList;
  if (CheckFile(filename)) {
#ifdef ET25
    IStream	f(filename);
#else
    istream	f(filename); // , "r");
#endif

    while (f >> n) {
	if (!res)
		res= n == -9999 ? 100 : 10000;
	f >> n;
	f >> name;

	if (strcmp(name, "_") == 0)
		name[0]= '\0';
	else
	  for (char *cp= name; *cp; cp++)
		if (*cp == '_')
			*cp= ' ';
	if (n > 1) {
	    GeoShape *p= new GeoShape;
	    p->fnpts= polyline? n: n+1;

	    p->SetName(name);
	    p->Long= new float[p->fnpts];
	    p->Lat= new float[p->fnpts];
	    p->pts= new Point[p->fnpts];

	    for (int i= 0; i < n; i++) {
		    int x,y;
		    f >> y;
		    f >> x;
		    p->Long[i]= x/float(res);
		    p->Lat[i]= y/float(res);
	    }
	    if (!polyline) {
		    p->Long[n]= p->Long[0];
		    p->Lat[n]= p->Lat[0];
	    }

	    ObjInt ii(polyline ? 0: 2);
	    p->SetProperty(eShapePattern, &ii);
	    p->SetLat(p->Lat[0]);
	    p->SetLong(p->Long[0]);

	    newshapes->Add(p);
	} else {
		for (int i= 0; i < n; i++) {
			int skip;
			f >> skip >> skip;
		}
	}
    }
  }

  return newshapes;
}
#endif


void DrawDocument::LoadGeo(char *filename)
{
  TextItem *l= new Layer(filename);
  overView->Add(l);
  TextItem *n= new Layer(filename);
  drawView->Add(n);
}



void DrawDocument::LoadGeoDone()
{
  overView->ShowArea(gRect0, 0, 15, 46.8, 56.1);
  overView->LoadDone();
  drawView->SetArea(0, 15, 46.8, 56.1);
  drawView->LoadDone();
}
