/************************************************************************
 ************************************************************************
    FAUST compiler
	Copyright (C) 2003-2004 GRAME, Centre National de Creation Musicale
    ---------------------------------------------------------------------
    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 2 of the License, 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.
 ************************************************************************
 ************************************************************************/


#include "seqSchema.h"
#include <iostream>
#include <assert.h>

using namespace std;

enum {kHorDir, kUpDir, kDownDir}; ///< directions of connections

static double computeHorzGap(schema* a, schema* b);
static int direction(const point& a, const point& b);


//----------------------------INTERFACE--------------------------------

/**
 * Make a sequential schema. May add cables to ensure the internal
 * connections are between the same number of outputs and inputs.
 * Compute an horizontal gap based on the number of upward and
 * downward connections.
 */
schema * makeSeqSchema (schema* s1, schema* s2)
{
	unsigned int o = s1->outputs();
	unsigned int i = s2->inputs();

	schema* a = (o < i) ? makeParSchema(s1, makeCableSchema(i-o)) : s1;
	schema* b = (o > i) ? makeParSchema(s2, makeCableSchema(o-i)) : s2;

	return new seqSchema(a, b, computeHorzGap(a,b));
}



//-----------------------IMPLEMENTATION------------------------------

/**
 * Constructor for a sequential schema (s1:s2). The components s1 and s2
 * are supposed to be "compatible" (s1 : n->m and s2 : m->q)
 */
seqSchema::seqSchema (schema* s1, schema* s2, double hgap)
	: 	schema(	s1->inputs(),
				s2->outputs(),
				s1->width() + hgap + s2->width(),
				max(s1->height(), s2->height()) ),
		fSchema1(s1),
		fSchema2(s2),
		fHorzGap(hgap)
{
	assert(s1->outputs() == s2->inputs());
}


//-----------------------placement------------------------------


/**
 * Place the two components horizontally with enough space
 * for the connections
 */
void seqSchema::place(double ox, double oy, int orientation)
{
	beginPlace(ox, oy, orientation);

	double y1 = max(0.0, 0.5*(fSchema2->height() - fSchema1->height()));
	double y2 = max(0.0, 0.5*(fSchema1->height() - fSchema2->height()));

	if (orientation == kLeftRight) {
		fSchema1->place(ox, oy+y1, orientation);
		fSchema2->place(ox+fSchema1->width()+fHorzGap, oy+y2, orientation);
	} else {
		fSchema2->place(ox, oy+y2, orientation);
		fSchema1->place(ox+fSchema2->width()+fHorzGap, oy+y1, orientation);
	}
	endPlace();
}


/**
 * The input points are the input points of the first component
 */
point seqSchema::inputPoint(unsigned int i) const
{
	return fSchema1->inputPoint(i);
}


/**
 * The output points are the output points of the second component
 */
point seqSchema::outputPoint(unsigned int i) const
{
	return fSchema2->outputPoint(i);
}



//--------------------------drawing------------------------------


/**
 * Draw the two components as well as the internal wires
 */
void seqSchema::draw(device& dev)
{
    assert(placed());
    assert(fSchema1->outputs() == fSchema2->inputs());

    fSchema1->draw(dev);
    fSchema2->draw(dev);
    //drawInternalWires(dev);
}

/**
 * Draw the two components as well as the internal wires
 */
void seqSchema::collectTraits(collector& c)
{
    assert(placed());
    assert(fSchema1->outputs() == fSchema2->inputs());

    fSchema1->collectTraits(c);
    fSchema2->collectTraits(c);
    collectInternalWires(c);
}


/**
 * Draw the internal wires aligning the vertical segments in
 * a symetric way when possible.
 */

void seqSchema::drawInternalWires(device& dev)
{
	assert (fSchema1->outputs() == fSchema2->inputs());

	const int 	N 	= fSchema1->outputs();
	double 		dx 	= 0;
	double		mx 	= 0;
	int			dir	=-1;

	if (orientation() == kLeftRight) {
		// draw left right cables
		for (int i=0; i<N; i++) {
			point src = fSchema1->outputPoint(i);
			point dst = fSchema2->inputPoint(i);

            int d = direction(src,dst);
            if (d != dir) {
                // compute attributes of new direction
                switch (d) {
                    case kUpDir 	: mx = 0; dx = dWire; break;
                    case kDownDir	: mx = fHorzGap; dx = -dWire; break;
                    default			: mx = 0; dx = 0; break;
                }
                dir = d;
            } else {
                // move in same direction
                mx = mx +dx;
            }
            if (src.y == dst.y) {
                // draw straight cable
                dev.trait(src.x, src.y, dst.x, dst.y);
            } else {
                // draw zizag cable
                dev.trait(src.x, src.y, src.x+mx, src.y);
                dev.trait(src.x+mx, src.y, src.x+mx, dst.y);
                dev.trait(src.x+mx, dst.y, dst.x, dst.y);
            }

        }
	} else {
		// draw right left cables
		for (int i=0; i<N; i++) {
			point src = fSchema1->outputPoint(i);
			point dst = fSchema2->inputPoint(i);

            int d = direction(src,dst);
            if (d != dir) {
                // compute attributes of new direction
                switch (d) {
                    case kUpDir 	: mx = -fHorzGap; dx = dWire; break;
                    case kDownDir	: mx = 0; dx = -dWire; break;
                    default			: mx = 0; dx = 0; break;
                }
                dir = d;
            } else {
                // move in same direction
                mx = mx +dx;
            }
            if (src.y == dst.y) {
                // draw straight cable
                dev.trait(src.x, src.y, dst.x, dst.y);
            } else {
                // draw zizag cable
                dev.trait(src.x, src.y, src.x+mx, src.y);
                dev.trait(src.x+mx, src.y, src.x+mx, dst.y);
                dev.trait(src.x+mx, dst.y, dst.x, dst.y);
            }

        }
	}
}



/**
 * Draw the internal wires aligning the vertical segments in
 * a symetric way when possible.
 */

void seqSchema::collectInternalWires(collector& c)
{
    assert (fSchema1->outputs() == fSchema2->inputs());

    const int 	N 	= fSchema1->outputs();
    double 		dx 	= 0;
    double		mx 	= 0;
    int			dir	=-1;

    if (orientation() == kLeftRight) {
        // draw left right cables
        for (int i=0; i<N; i++) {
            point src = fSchema1->outputPoint(i);
            point dst = fSchema2->inputPoint(i);

            int d = direction(src,dst);
            if (d != dir) {
                // compute attributes of new direction
                switch (d) {
                    case kUpDir 	: mx = 0; dx = dWire; break;
                    case kDownDir	: mx = fHorzGap; dx = -dWire; break;
                    default			: mx = 0; dx = 0; break;
                }
                dir = d;
            } else {
                // move in same direction
                mx = mx +dx;
            }
            if (src.y == dst.y) {
                // draw straight cable
                c.addTrait(trait(point(src.x, src.y), point(dst.x, dst.y)));
            } else {
                // draw zizag cable
                c.addTrait(trait(point(src.x, src.y), point(src.x+mx, src.y)));
                c.addTrait(trait(point(src.x+mx, src.y), point(src.x+mx, dst.y)));
                c.addTrait(trait(point(src.x+mx, dst.y), point(dst.x, dst.y)));
            }

        }
    } else {
        // draw right left cables
        for (int i=0; i<N; i++) {
            point src = fSchema1->outputPoint(i);
            point dst = fSchema2->inputPoint(i);

            int d = direction(src,dst);
            if (d != dir) {
                // compute attributes of new direction
                switch (d) {
                    case kUpDir 	: mx = -fHorzGap; dx = dWire; break;
                    case kDownDir	: mx = 0; dx = -dWire; break;
                    default			: mx = 0; dx = 0; break;
                }
                dir = d;
            } else {
                // move in same direction
                mx = mx +dx;
            }
            if (src.y == dst.y) {
                // draw straight cable
                c.addTrait(trait(point(src.x, src.y), point(dst.x, dst.y)));
            } else {
                // draw zizag cable
                c.addTrait(trait(point(src.x, src.y), point(src.x+mx, src.y)));
                c.addTrait(trait(point(src.x+mx, src.y), point(src.x+mx, dst.y)));
                c.addTrait(trait(point(src.x+mx, dst.y), point(dst.x, dst.y)));
            }

        }
    }
}

//--------------------------helpers------------------------------



/**
 * Compute the direction of a connection. Note that
 * Y axis goes from top to bottom
 */
static int direction(const point& a, const point& b)
{
	if (a.y > b.y) return kUpDir; 		// upward connections
	if (a.y < b.y) return kDownDir; 	// downward connection
	return kHorDir;						// horizontal connections
}

/**
 * Compute the horizontal gap needed to draw the internal wires.
 * It depends on the largest group of connections that go in the same
 * direction.
 */
static double computeHorzGap(schema* a, schema* b)
{
	assert(a->outputs() == b->inputs());

	if (a->outputs() == 0) {
		return 0;
	} else {
		// store here the size of the largest group for each direction
		int	MaxGroupSize[3]; for(int i=0; i<3; i++) MaxGroupSize[i]=0;

		// place a and b to have valid connection points
		double ya = max(0.0, 0.5*(b->height() - a->height()));
		double yb = max(0.0, 0.5*(a->height() - b->height()));
		a->place(0,ya,kLeftRight);
		b->place(0,yb,kLeftRight);

		// init current group direction and size
		int	gdir 	= direction(a->outputPoint(0), b->inputPoint(0));
		int	gsize 	= 1;

		// analyze direction of remaining points
		for (unsigned int i=1; i<a->outputs(); i++) {
			int d = direction(a->outputPoint(i), b->inputPoint(i));
			if (d == gdir) {
				gsize++;
			} else {
				if (gsize > MaxGroupSize[gdir])  MaxGroupSize[gdir]=gsize;
				gsize = 1;
				gdir = d;
			}
		}

		// update for last group
		if (gsize > MaxGroupSize[gdir])  MaxGroupSize[gdir]=gsize;

		// the gap required for the connections
		return dWire * max(MaxGroupSize[kUpDir],MaxGroupSize[kDownDir]);
	}
}
