//			Drawable shapes
//
// Handling a collection of polymorphic objects in a non-traditional style.
// BRules are in effect: no virtual functions, no assignments, no
// overriding.

// Yet this file achieves the _full_ separation of interface and
// implementation. Even if Shape is extended, the code that deals with
// abstract shapes does not even have to be recompiled! Compare to
// Shapes-oop.cc file in this directory. Another advantage of the
// present implementation is that _both_ Square and Rectangle objects
// can be passed to a function set_dim(). Depending on the new
// dimensions, a square can *become* a rectangle or a rectangle square.
// Also compare this file to a similar code Shapes-h.hs, which is written
// in a pure functional language Haskell. The similarity is uncanny!
//
// See ../No-problem/Readme.txt for details and explanations, section
// "Existential types with BRules"
// $Id: Shapes-no-oop.cc,v 1.2 2003/01/27 21:30:50 oleg Exp oleg $

#include <iostream>
using std::ostream;
using std::cerr;
using std::endl;

//------------------------------------------------------------------------
// Public interfaces

// An existential datatype
class Shape;			// There is _nothing_ known about its structure
ostream& draw(ostream& os, const Shape& shape);
const Shape * offset_by(const Shape& shape, const float x, const float y);
const Shape * set_dim(const Shape& shape, 
		      const float width, const float height);

// This is supposed to be a list of shapes to draw
// For simplicity, we'll hold three shapes only
// For a functional implementation of a list (with garbage collection),
// see ../No-problem/Bag.cc

class Shapes {
  const Shape& s1;
  const Shape& s2;
  const Shape& s3;
public:
  Shapes(const Shape * const _s1, const Shape * const _s2,
	 const Shape * const  _s3)
    : s1(*_s1), s2(*_s2), s3(*_s3) {}
  friend ostream& draw(const Shapes& shapes, ostream& os);
  friend Shapes pack(const Shapes& shapes);
  friend Shapes resize(const Shapes& shapes);
};

ostream& draw(const Shapes& shapes, ostream& os);
Shapes pack(const Shapes& shapes);
Shapes resize(const Shapes& shapes);

//------------------------------------------------------------------------
// Implementation

// Note that the following three functions on Shapes are implemented
// _before_ the Shape class is revealed. Therefore Shapes-based
// functions do not depend on an implementation of a Shape _at all_.

ostream& draw(const Shapes& shapes, ostream& os)
{ return draw(draw(draw(os,shapes.s1),shapes.s2),shapes.s3); }

Shapes pack(const Shapes& shapes)
{
  return Shapes(
		offset_by(shapes.s1,0,1),
		offset_by(shapes.s2,2,3),
		offset_by(shapes.s3,4,5));
}

Shapes resize(const Shapes& shapes)
{
  return Shapes(
		set_dim(shapes.s1,10,10),
		set_dim(shapes.s2,10,11),
		set_dim(shapes.s3,11,12));
}


// Finally, the details of the Shape are revealed.  Shape may appear
// very similar to the Abstract base class in file
// Shapes-oop.cc. There is an important difference however. Unlike
// virtual functions, the three function pointers in the body of the
// Shape are _constant_ pointers. Once set, they may _not_ be
// overridden.
//
// Note a similarity to newbus or vnode interfaces, see e.g.,
// 	http://people.freebsd.org/~asmodai/newbus-draft.txt
// (search the latter file for 'INTERFACE FOO')

class Shape {
  ostream& (* const draw)(ostream& os, const Shape& shape);
  const Shape * (* const offset_by)(const Shape& shape, 
				    const float x, const float y);
  const Shape * (* const set_dim)(const Shape& shape, 
				  const float width, const float height);
 
protected:
  Shape(ostream& (* const _draw)(ostream& os, const Shape& shape),
	const Shape * (* const _offset_by)(const Shape& shape, 
				const float x, const float y),
	const Shape * (* const _set_dim)(const Shape& shape, 
				const float width, const float height))
    : draw(_draw), offset_by(_offset_by), set_dim(_set_dim) {}

  friend ostream& draw(ostream& os, const Shape& shape);
  friend const Shape * offset_by(const Shape& shape, 
				 const float x, const float y);
  friend const Shape * set_dim(const Shape& shape, 
			       const float width, const float height);
};

// Note the lack of public constructors in the above class (and the
// lack of any public interface for that matter)

ostream& draw(ostream& os, const Shape& shape)
{
  return shape.draw(os,shape);
}

const Shape * offset_by(const Shape& shape, const float x, const float y)
{
  return shape.offset_by(shape,x,y);
}

const Shape * set_dim(const Shape& shape, 
		      const float width, const float height)
{
  return shape.set_dim(shape,width,height);
}


// Two concrete instances of the existential Shape class.
// A static_cast<> below may appear a hack. Yet it is safe. It's merely
// a consequence of difficulties in expressing function closures in C++.
// The similar Shapes-h.hs code, written in a _strongly_ statically-typed
// language Haskell, typechecks without a single error or warning.

class Rectangle : public Shape {
  const float x, y;
  const float width, height;

  static ostream& draw(ostream& os, const Shape& _s)
  {
    const Rectangle & s = static_cast<const Rectangle&>(_s);
    return os << "Drawing a rectangle [" << s.x << ", " << s.y
	 << "] - [" << (s.x+s.width) << ", " << (s.y+s.height) 
	 << "]" << endl;
  }

  static const Shape * offset_by(const Shape& _s, 
				 const float x, const float y)
  {
    const Rectangle & s = static_cast<const Rectangle&>(_s);
    return new Rectangle(s.x+x,s.y+y,s.width,s.height);
  }

  static const Shape * set_dim(const Shape& _s, 
			       const float width, const float height)
  {
    const Rectangle & s = static_cast<const Rectangle&>(_s);
    return make(s.x,s.y,width,height);
  }

				// A private constructor
  Rectangle(const float _x, const float _y, 
	    const float _width, const float _height) :
    Shape(&draw,&offset_by,&set_dim), x(_x), y(_y), 
    width(_width), height(_height) {}

public:
  static const Shape * make(const float width, const float height)
  { return make (0,0,width,height); }
  static const Shape * make(const float x, const float y, 
			    const float width, const float height);
  // for the implementation, see below.
};

class Square : public Shape {
  const float x, y;
  const float size;

  static ostream& draw(ostream& os, const Shape& _s)
  {
    const Square & s = static_cast<const Square&>(_s);
    return os << "Drawing a square [" << s.x << ", " << s.y
	 << "] of size " << (s.size) << endl;
  }

  static const Shape * offset_by(const Shape& _s, 
				 const float x, const float y)
  {
    const Square & s = static_cast<const Square&>(_s);
    return new Square(s.x+x,s.y+y,s.size);
  }

  static const Shape * set_dim(const Shape& _s, 
			       const float width, const float height)
  {
    const Square & s = static_cast<const Square&>(_s);
    return Rectangle::make(s.x,s.y,width,height);
  }

				// A private constructor
  Square(const float _x, const float _y, const float _size) :
    Shape(&draw,&offset_by,&set_dim), x(_x), y(_y), 
    size(_size) {}

public:
  static const Shape * make(const float size)
  { return new Square(0,0,size); }
  static const Shape * make(const float x, const float y, const float size)
  { return new Square(x,y,size); }
};

const Shape * Rectangle::make(const float x, const float y, 
			      const float width, const float height)
{
  return width == height ?  Square::make(x,y,width) :
    new Rectangle(x,y,width,height);
}


//------------------------------------------------------------------------
// Test
int main(void)
{
  cerr << "Instantiating shapes..." << endl;
  Shapes shapes(Rectangle::make(1,2), Rectangle::make(5,5),
		Rectangle::make(7,7));
  cerr << "Drawing ... some rectangles are actually squares" << endl;
  draw(shapes,cerr);
  cerr << "Packing ..." << endl;
  Shapes shapes_packed = pack(shapes);
  draw(shapes_packed,cerr);
  cerr << "Resizing ... rectangles turn squares and vice versa" << endl;
  Shapes shapes_resized = resize(shapes_packed);
  draw(shapes_resized,cerr);
  cerr << "\nAll done" << endl;
  return 0;
}
