/*
 * BaseWindow.C - abstract base class for window support.
 *
 * Copyright (C) 1992, Christoph Streit (streit@iam.unibe.ch)
 *                     University of Berne, Switzerland
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 */

#include "BaseWindow.h"
#include "list.h"
#include "Error.h"
#include "Options.h"

//___________________________________________________________ LineSegment

struct LineSegment
{
  LineSegment(const Vector& p1, const Vector& p2)
  : from(p1), to(p2) {}

  Vector from, to;
};

typedef LineSegment* LineSegmentPtr;
declareList(LineList, LineSegmentPtr);
implementList(LineList, LineSegmentPtr);

//___________________________________________________________ BaseWindow

BaseWindow::BaseWindow()
: view(NULL), resX(400), resY(400), 
  lines(NULL), polys(NULL),
  windowIsOpen(0), buffering(1)
{}

BaseWindow::~BaseWindow()
{
  if (view != NULL)  
    delete view;
  clearBuffer();
  if (lines != NULL)
    delete lines;
  if (polys != NULL)
    delete polys;
}

void BaseWindow::open(int width, int height, const rcString& windowName)
{
  if (windowIsOpen)
    Error(ERR_PANIC, "Window is already open");

  windowIsOpen = 1;
  resX = width;
  resY = height;
  name = windowName;
}

void BaseWindow::close()
{
  if (!windowIsOpen)
    Error(ERR_PANIC, "close window: but window is not open");
  windowIsOpen = 0;
}

void BaseWindow::clear()
{
  if (!windowIsOpen)
    Error(ERR_PANIC, "clear window: but window is not open");
}

/*
 * If buffering is enabled store the given line segment in the
 * line list. Otherwise transform the segment to screen coordinates
 * and show it on the screen.
 */

void BaseWindow::line(const Vector& from, const Vector& to)
{
  if (buffering) {
    if (lines == NULL)
      lines = new LineList(1000);
    lines->append(new LineSegment(from, to));
  }
  else if (view == NULL) {
    Error(ERR_PANIC, "no view parameter set");
  }

  /*
   * Transform and draw the line without buffering.
   */
  else { 
    Vector x = view->transformWorld2Screen(from);
    Vector y = view->transformWorld2Screen(to);
    drawLine((int)x[0], (int)x[1], (int)y[0], (int)y[1]);
  }
}

/*
 * If buffering is enabled store the given polygon in the
 * poly list. Otherwise transform the polygon to screen coordinates
 * and show it on the screen.
 */

void BaseWindow::polygon(Polygon* p)
{
  if (buffering) {
    if (polys == NULL)
      polys = new PolygonList(1000);
    polys->append(p);
  }
  else if (view == NULL) {
    Error(ERR_PANIC, "no view parameter set");
  }
  else if (p->numVertices() >= 3) { 
    Vector x = view->transformWorld2Screen(p->vertex(0));
    Vector y;

    for (register long i=1; i<p->numVertices(); i++) {
      y = view->transformWorld2Screen(p->vertex(i));
      drawLine((int)x[0], (int)x[1], (int)y[0], (int)y[1]);
      x = y;
    }
    y = view->transformWorld2Screen(p->vertex(0));
    drawLine((int)x[0], (int)x[1], (int)y[0], (int)y[1]);
  }  

  if (!buffering)
    delete p;
}

/*
 * For all the line segment in the line list and all the polys in the
 * polygon list, transform and bring dem to screen.
 */

void BaseWindow::flush()
{
  if (view == NULL)
    Error(ERR_PANIC, "no view parameter set");

  Vector from, to;
  if (lines != NULL) {
    for (register long i=0; i<lines->count(); i++) {
      from = view->transformWorld2Screen(lines->item(i)->from);
      to   = view->transformWorld2Screen(lines->item(i)->to);
      drawLine((int)from[0], (int)from[1], (int)to[0], (int)to[1]);
    }
  }
  
  if (polys != NULL) {
    for (register long i=0; i<polys->count(); i++) {
      Polygon* p = polys->item(i);
      if (p->numVertices() >= 3) {
	from = view->transformWorld2Screen(p->vertex(0));
	for (register long j=1; j<p->numVertices(); j++) {
	  to   = view->transformWorld2Screen(p->vertex(j));
	  drawLine((int)from[0], (int)from[1], (int)to[0], (int)to[1]);
	  from = to;
	}
	to = view->transformWorld2Screen(p->vertex(0));
	drawLine((int)from[0], (int)from[1], (int)to[0], (int)to[1]);
      }
    }
  }
}

void BaseWindow::disableBuffering()
{ 
  buffering = 0; 
}

void BaseWindow::enableBuffering()
{
  buffering = 1; 
}

void BaseWindow::setView(const Vector& eye, const Vector& lookat, const Vector& up, 
			 real fov)
{
  if (view != NULL)
    delete view;
  view = new ViewTransform(eye, lookat, up, fov, resX, resY);

  if (theOptions.verbose)
    cerr << *view << '\n';
}

void BaseWindow::setView(const BoundingBox& bbox, const Vector& up, real fov)
{
  if (view != NULL)
    delete view;
  view = new ViewTransform(bbox, up, fov, resX, resY);

  if (theOptions.verbose)
    cerr << *view << '\n';
}

/*
 * Remove all the line segment und polygon from the buffer.
 */

void BaseWindow::clearBuffer()
{
  if (lines != NULL) {
    for (register long i=0; i<lines->count(); i++)
      delete lines->item(i);
    lines->remove_all();
  }
  if (polys != NULL) {
    for (register long i=0; i<polys->count(); i++)
      delete polys->item(i);
    polys->remove_all();
  }
}
