/* ==================================================== ======== ======= *
 *
 *  uubrick.cc
 *  Ubit Project  [Elc][beta1][2001]
 *  Author: Eric Lecolinet
 *
 *  Part of the Ubit Toolkit: A Brick Construction Game Model for Creating GUIs
 *
 *  (C) 1999-2001 Eric Lecolinet @ ENST Paris
 *  WWW: http://www.enst.fr/~elc/ubit   Email: elc@enst.fr (subject: ubit)
 *
 * ***********************************************************************
 * COPYRIGHT NOTICE : 
 * THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY AND WITHOUT EVEN THE 
 * IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 
 * 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.
 * SEE FILES 'COPYRIGHT' AND 'COPYING' FOR MORE DETAILS.
 * ***********************************************************************
 *
 * ==================================================== [Elc:01] ======= *
 * ==================================================== ======== ======= */

//pragma ident	"@(#)uubrick.cc	ubit:b1.11.6"
#include <stdarg.h>  // for var_args
#include <udefs.hh>
#include <ubrick.hh>
#include <ucall.hh>
#include <ustr.hh>
#include <uctrl.hh>
#include <ubox.hh>
#include <uview.hh>
#include <uevent.hh>

const char* uversion() {return _UBIT_VERSION_STRING;}
long uversionNo() {return _UBIT_VERSION_NO;}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// NB: 'funname' starting with a "!" provoques a Fatal Error!

void uva_error(const char *classname, const char *funname, 
	       const char *format, va_list vargs) {  
  if (classname) {
    if (funname[0] == '!')
      fprintf(stderr, "!!!Fatal Error: in class: %s - method: %s\n", 
	      classname, funname+1);
    else 
      fprintf(stderr, "!Error: in class: %s - method: %s\n", 
	      classname, funname);
  }
  else {
    if (funname[0] == '!')
      fprintf(stderr, "!!!Fatal Error: in function: %s\n", funname+1);
    else
      fprintf(stderr, "!Error: in function: %s\n", funname);
  }

  char buffer[1000] = "";
  vsprintf(buffer, format, vargs);
  fprintf(stderr, "!- %s\n", buffer);
  fprintf(stderr, "\n");
}

void uva_warning(const char *classname, const char *funname, 
		 const char *format, va_list vargs) {
  if (classname)
    fprintf(stderr, "!Warning: in class: %s - method: %s\n", 
	    classname, funname);
  else
    fprintf(stderr, "!Warning: in function: %s\n", funname);

  char buffer[1000] = "";
  vsprintf(buffer, format, vargs);
  fprintf(stderr, "!- %s\n", buffer);
 
  fprintf(stderr, "\n");
}

/* ==================================================== ======== ======= */

// printf() syntax
void uerror(const char *funname, const char *format, ...) {
  va_list vargs;
  va_start(vargs, format);     // selon version: avec ou sans 2e arg
  uva_error(null, funname, format, vargs);
  va_end(vargs);
}

void uwarning(const char *funname, const char *format, ...) {
  va_list vargs;
  va_start(vargs, format);     // selon version: avec ou sans 2e arg
  uva_warning(null, funname, format, vargs);
  va_end(vargs);
}

void UBrick::error(const char *funname, const char *format, ...) const {
  va_list vargs;
  va_start(vargs, format);     // selon version: avec ou sans 2e arg
  uva_error(getClassName(), funname, format, vargs);
  va_end(vargs);
}

void UBrick::warning(const char *funname, const char *format, ...) const {
  va_list vargs;
  va_start(vargs, format);     // selon version: avec ou sans 2e arg
  uva_warning(getClassName(), funname, format, vargs);
  va_end(vargs);
}

/* ==================================================== [Elc:00] ======= */
/* ==================================================== ======== ======= */

u_bool ULink::verifies(const UContext *ctx, const class UCtrl *ctrl) const {
  // null cond means always
  return (cond == null || cond->verifies(ctx, ctrl));
}

UGenLink* UGenChain::reset() {
  UGenLink* l = firstlink;
  firstlink = null;
  lastlink = null;
  return l;
}

void UGenChain::append(UGenChain *chain) {
  //2e chaine vide : ne rien faire
  if (!chain || !chain->firstlink) return;

  if (lastlink) {
    lastlink->nextlink = chain->firstlink;
    lastlink = chain->lastlink;
  }
  else {
    firstlink = chain->firstlink;
    lastlink = chain->lastlink;
  }
}

void UGenChain::add(UGenLink *link) {
  if (lastlink) {
    lastlink->nextlink = link;
    lastlink = link;
  }
  else {
    lastlink = firstlink = link;
  }
}

void UGenChain::remove(UGenLink *link) {
  UGenLink *prevl = null;
  for (UGenLink *l = firstlink; l!=null; prevl = l, l = l->next()) 
    if (l == link) {
      removeAfter(prevl);
      return;
    }
}

// insere au debut si poslink==null sinon insere APRES poslink
// -- suppose que poslink soit *effectivement* dans la liste!

void UGenChain::insertAfter(UGenLink *addedlink, UGenLink *poslink) {
 
  if (!firstlink) {  // liste vide
    firstlink = lastlink = addedlink;
    addedlink->nextlink = null;   // securit
  }
  else if (poslink == null) {  // insert at the beginning 
    UGenLink *nextlink = firstlink;
    firstlink = addedlink;
    addedlink->nextlink = nextlink;
    if (!nextlink) lastlink = addedlink;
  }
  else {  // insert after poslink
    UGenLink *nextlink = poslink->next();
    poslink->nextlink = addedlink;
    addedlink->nextlink = nextlink;
    if (!nextlink) lastlink = addedlink;
  }
}

// enleve et renvoie le lien qui SUIT 'poslink' (n'enleve pas 'poslink'!)
// -- enleve et renvoie le 1er lien (s'il existe) si 'poslink'==null
// -- renvoie null et ne fait rien si aucun lien ne suit 'poslink'
//NB: important:
//suppose que poslink est *effectivement* dans la liste!

UGenLink* UGenChain::removeAfter(UGenLink *poslink) {
  UGenLink *l = null;

  if (poslink == null) { // enleve et renvoie le 1er lien (s'il existe)
    l = firstlink;
    if (l) { // l exsite
      firstlink = l->next();
      if (!firstlink) lastlink = null;    //plus rien apres firstlink
      l->nextlink = null; //securite
    }
  }

  else {
    l = poslink->next();
    if (l) {  // l exsite
      poslink->nextlink = l->next();
      if (!poslink->nextlink) lastlink = poslink;  //plus rien apres poslink
      l->nextlink = null; //securite
    }
  }

  return l;  // renvoyer l (eventuellement null)
}

ULink* UChain::search(const UBrick *b, const ULink *fromlink) {
  ULink *l = const_cast<ULink*>(fromlink ? fromlink : first());
  for (; l!=null; l = l->next()) 
    if (l->brick() == b) return l;
  return null;  // not found
}

/* ==================================================== ======== ======= */

ULinkLink* ULLChain::removePointingTo(ULink *pointed) {
  ULinkLink *prevl = null;
  ULinkLink *l = first();

  for ( ; l != null; l = l->next()) {
    if (l->link() != pointed)
      prevl = l;
    else {
      if (prevl) prevl->nextlink = l->next();
      else firstlink = l->next();
      if (lastlink == l) lastlink = prevl;
      return l;
    }
  }
  return null;  // not found
}

void ULLChain::updateParents(const UUpdate &upmode)  const {  
  // mettre a jour toutes les Box qui pointent sur l'item
  for (ULinkLink *llk = first(); llk != null; llk = llk->next()) {
    UGroup *box = llk->parent();
    // box==null --> pas encore passe par initContext()
    if (box) box->update(upmode);
  }
}

void ULLChain::fireParents(UEvent& e, const UOn &on) const {  
  // mettre a jour toutes les Box qui pointent sur l'item
  for (ULinkLink *llk = first(); llk != null; llk = llk->next()) {
    UGroup *grp = llk->parent();                       //???!!!!TrueParent??
    // box==null --> pas encore passe par initContext()
    if (grp) {
      e.setGroupSource(grp);
      grp->fire(e, on);
    }
  }
}

void ULLChain::setModesOfParents(u_modes bmodes, u_modes cmodes, 
				 u_bool onoff) const {
  for (ULinkLink *llk = first(); llk != null; llk = llk->next()) {
    UGroup *box = llk->parent();
    // box==null --> pas encore passe par initContext()
    if (box) {
      box->setBmodes(bmodes, onoff);
      box->setCmodes(cmodes, onoff);
    }
  }
}

void ULLChain::setModesOfParentViews(int vmodes, u_bool onoff) const {

  for (ULinkLink *llk = first(); llk != null; llk = llk->next()) {
    UBox *box = llk->parent() ? dynamic_cast<UBox*>(llk->parent()) : null;
  
    if (box) {
      for (ULinkLink *llk2 = box->getParentList().first();
	   llk2 != null; 
	   llk2 = llk2->next()){

	UBoxLink *link = dynamic_cast<UBoxLink*>(llk2->link());
	for (int kv = 0; kv < link->viewCount; kv++) {
	  UView *view = link->views[kv];
	  if (view) view->setVmodes(vmodes, onoff);
	}
      }
    }
  }
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

const UArgs UArgs::none;

UArgs::UArgs() {}

UArgs::UArgs(const char *s) {
  UBrick *b = new UStr(s);
  children.add(b->makeLink());
}

UArgs::UArgs(UBrick *b) {
  if (!b) uerror("UArgs::UArgs", "Null brick in addlist !");
  else children.add(b->makeLink());
}

UArgs::UArgs(UBrick &b) {
  children.add(b.makeLink());
}

UArgs::UArgs(ULink &k) {
  if (!k.brick()) 
    uerror("UArgs::UArgs", "Null or undefined Link in addlist!");
  //else k.brick()->linkToParent(this, &k);
  children.add(&k);
}

UArgs::UArgs(ULink *k) {
  if (!k || !k->brick()) 
    uerror("UArgs::UArgs", "Null or undefined Link in addlist!");
  //else k->brick()->linkToParent(this, k);
  children.add(k);
}


UArgs operator+(UArgs l, const char *s) {
  UBrick *b = new UStr(s);
  //b->linkToParent(&l, b->makeLink());
  l.children.add(b->makeLink());
  return l;
}

UArgs operator+(UArgs l, UBrick *b) {
  if (!b) uerror("operator+", "Null brick in addlist !");
  l.children.add(b->makeLink());
  return l;
}

UArgs operator+(UArgs l, UBrick &b) {
  l.children.add(b.makeLink());
  return l;
}

UArgs operator+(UArgs l, ULink &k) {
  if (!k.brick()) 
    uerror("operator+", "Null or undefined Link in addlist !");
  else l.children.add(&k);
  return l;
}

UArgs operator+(UArgs l, ULink *k) {
  if (!k || !k->brick())
    uerror("operator+", "Null or undefined Link in addlist !");
  else l.children.add(k);
  return l;
}


ULink& operator/(const UCond& cond, UBrick& b) {
  ULink *k = b.makeLink();
  k->setCond(cond);
  return *k;
}

ULink& operator/(const UCond& cond, UBrick* b) {
  ULink *k;
  if (!b) {
    uerror("operator/", "Null brick as a right operand");
    k = new ULink(null);
  }
  else {
    k = b->makeLink();
    k->setCond(cond);
  }
  return *k;
}

/* ==================================================== [Elc:00] ======= */
/* ==================================================== ======== ======= */
/* ==================================================== ======== ======= */


UClass::UClass(const char *n) : name(n) {
  // !! egalement:  ajouter a la classDB
  // !! egalemnt: superclass
  //  static int k = 0;
  //printf("-Class %d, Name=%s\n", k++, name);
}

const UClass *UClass::getClass(const char *classname) {
  return null;			// a completer !!!
}

const UClass UBrick::uclass("UBrick");

const char*   UBrick::cname()        const {return getClass()->name;}
const char*   UBrick::getClassName() const {return getClass()->name;}
const UClass* UBrick::getClass()     const {return &uclass;}


/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

void ucannotDelete(UBrick *b) {
  b->setBmodes(UMode::NO_DEL, true);
}
void ucannotDelete(UBrick &b) {
  b.setBmodes(UMode::NO_DEL, true);
}

void UBrick::autodel(u_bool on) {
  if (!(bmodes & (UMode::UCONST | UMode::NO_DEL)))  //securite
    setBmodes(UMode::NO_AUTODEL, !on);
}
  
void UBrick::setBmodes(u_modes m, u_bool onoff) {
  if (onoff) bmodes = bmodes | m;
  else bmodes = bmodes & ~m;
}

/* ==================================================== ======== ======= */
// called by udelete(). returns true if the object was actually deleted

u_bool udeletep(UBrick *b) {
  if (!b) {
    uerror("udelete","null pointer as an argument");
    return false;
  }
  
  // don't delete UCONST and NO_DEL objects!
  if (b->bmodes & (UMode::UCONST | UMode::NO_DEL)) {
    b->clean();    // enlever du graphe d'instance
    return false;  // l'objet n'est pas detruit (b inchange)
  }
  else {  	   //sinon: cas general: destruction
    delete b;
    return true;
  }
}

/* ==================================================== ======== ======= */
// ATTENTION: les appels aux fcts vituelles ne marchent pas normalement dans
// les destructeurs (l'appel est fait avec la classe du destructeur, pas la
// classe effective)
// ==> appeler removeFromParents() DANS LE DESTRUCTEUR de la SOUS-CLASSE
//     pour qu'il soit applique avec le type effectif de l'objet

void UBrick::clean() {
  // detacher des parents
  if (parents.first()) removeFromParents();
  // faudrait aussi s'occuper des liens croises (par les conditions) !!! 
  // et aussi du CACHE
}

/* ==================================================== ======== ======= */
//!!selflink = the link that POINTS to 'this' obj!

void UBrick::addingTo(ULink *selflink, UGroup *parent) {
  selflink->setParent(parent);

  // Rendre parent sensitif aux events ad hoc  //!NEW18m
  // !!!ATTENTION: PROBLEME
  // setModes() pose probleme avec select/unselect car ce n'est pas un mode
  // qu'il faut propager au parent mais juste un referent pour le UOn
  // (pour l'instant c'est regle en ne rajoutant pas ces modes et 
  // en testant directement l'egalite des UOn dans match() et verifies()

  //if (selflink->cond) parent->setModes(selflink->cond);
  if (selflink->cond) selflink->cond->setParentModes(parent);

  ///... inutile pour les UCONST!!!;
  parents.add(new ULinkLink(selflink));
}

/* ==================================================== ======== ======= */
//Notes:
// 1. removingFrom() requires a destructor to be defined
// 2. selflink = the link that points to 'this' obj!

void UBrick::removingFrom(ULink *selflink, UGroup *parent) {

  // ne PAS enlever les modes du parent car il peut y avoir
  // plusieurs callbacks avec la meme condition
  // (et de toute facon ce n'est pas une erreur, juste un peu plus long)

  ///... inutile pour les UCONST!!!;
  ULinkLink *ll = parents.removePointingTo(selflink);
  if (ll) delete ll;
}

/* ==================================================== ======== ======= */

UGroup* UBrick::getParent(int pos) {
  if (pos == -1) {  // find the last one
    return (parents.last() ? parents.last()->link()->parent() : null);
  }
  else if (pos < 0) {
    warning("getParent", "invalid position: pos=%d", pos);
    return null;
  }
  else {
    int kk = 0;
    for (ULinkLink *l = parents.first(); l!=null; l = l->next(), kk++){
      if (kk == pos) return l->link()->parent();
    }
    return null; // out of range
  }
}

int UBrick::getParentCount() {
  int kk = 0;
  for (ULinkLink *l = parents.first(); l!=null; l = l->next()) 
    kk++;   //k++ mis ici pour eviter les bugs... des compilateurs!
  return kk;
}

// Returns a copy of the object's child list
// -- returns a null terminated table that must be destroyed after use
//    by using the delete[] primitive

UGroup** UBrick::getParents() {
  int count;
  return getParents(count);
}

//!!!!!!!ATT: ambiguite sur la NATURE des parents :                    !!!!!!
// on veut TOUS les parents ou seulement ceux lies aux 'children' lists ????

UGroup** UBrick::getParents(int &count) {
  count = getParentCount();

  if (count <= 0) return null;  // ajout 12fev00
  else {
    UGroup **tab = new UGroup*[count+1];
    int kk = 0;
    for (ULinkLink *l = parents.first(); l!=null; l = l->next(), kk++){
      tab[kk] = l->link()->parent();
    }
    tab[kk] = null;  // null terminated
    return tab;
  }
}

u_bool UBrick::hasMultipleTrueParents() {
  u_bool already_one = false;
  for (ULinkLink *ll = parents.first(); ll != null; ll = ll->next()) 
    if (!ll->link()->parent()->isDef(0,UMode::SOFTWIN_LIST)){
      if (already_one) return true;
      already_one = true;
    }
  return false;
}

void UBrick::removeFromParents() {    // detacher de tous les parents  
  // nb: on enleve le 1er jusuq'a ce qu'il n'y en ait plus
  // -- correct pour multi-occurences:
  // -- l'ordre n'a pas d'importance puisqu'on les enleve tous
  //    (si un child est inclus plusieurs fois dans un meme parent
  //     on ne sait pas dans quel ordre parent->remove(child)
  //     enleve ces occurences)
  ULinkLink *ll, *last_ll = null;

  while ((ll = parents.first())) {
    if (ll == last_ll) {
      // !Cas d'erreur: toujours le meme link ll parce child pointe vers parent
      // alors que parent ne pointe pas vers child
      // ==> enlever direcetement ce lien de la lsite des parents et le detruire
      parents.removeAfter(null);   // null => debut de chaine
      delete ll; 
    }
    else {
      last_ll = ll;
      //nb1: voir commentaire ci-dessus sur l'ordre
      //nb2: 2e arg. false => ne pas detruire la brick, juste l'enlever
      ll->parent()->remove(this, false);
    }
  }
}

// returns true if argument is a direct parent (case indirect = false)
// or an ancestor (case indirect = true) of 'this' object

u_bool UBrick::isChildOf(UGroup *possible_ancestor, u_bool indirect) {

  for (ULinkLink *l = parents.first(); l!=null; l = l->next()) {
    UGroup *par = l->link()->parent();
    // par == null if no other parent (UFrame or similar)
    if (par) {
      if (par == possible_ancestor) return true; 
      else if (indirect) {
	if (par->isChildOf(possible_ancestor, true))
	  return true;
      }
    }
  }
  return false; // not found
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

UBrick& operator>>(UBrick &b, UCall &c) {
  if (!b.cache) b.cache = new UChain();

  ULink *l = new ULink(&c);
  b.cache->add(l);

  if (!c.getCond())  c.setCond(UOn::change); //default
  l->cond = c.getCond();

  //else ... a completer ulterieurement : ne fait rien si UStr etc.
  UGroup *g = dynamic_cast<UGroup*>(&b);
  if (g && l->cond) l->cond->setParentModes(g);

  return b;
}

UBrick& operator>>(UBrick *b, UCall &c) {
  if (!b) {
    uerror("operator","left arg is null");
    return *b;   //!!!renvoyer elment empty !! 
  }
  else return *b >> c;
}


void UBrick::fire(UEvent& e, const UOn &cond) {
  if (cache) {
    ULink *l; UCall *c;
    //NB! faudrait tester que le callback detruit pas l'objet appelant
    // comme on le fait pour les UGroup !!

    for (l = cache->first(); l != null; l = l->next()) {
      if (l->match(cond) && (c = dynamic_cast<UCall*>(l->brick()))) {
        e.cond = &cond; c->call(&e);
      }
    }
  }
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

const UClass UItem::uclass("UItem");

void UItem::changed(u_bool update_now) {
  if (update_now) update();

 if (cache) {
    UEvent e(UEvent::change, null, NULL);
    e.setItemSource(this);

    fire(e, UOn::change);
    // ensuite on lance les callbacks des parents
    parents.fireParents(e, UOn::itemChange);
  }
}

/* ==================================================== [TheEnd] ======= */
/* ==================================================== [Elc:01] ======= */
