
//  This file forms part of the GOBLIN C++ Class Library.
//
//  Initially written by Christian Fremuth-Paeger, August 2000
//
//  Copying, compiling, distribution and modification
//  of this source code is permitted only in accordance
//  with the GOBLIN general licence information.

/// \file   exportToXFig.cpp
/// \brief  #exportToXFig class implementation

#include "exportToXFig.h"


static unsigned OFS_NODE_COLOURS = 36;
static unsigned NUM_NODE_COLOURS = 254;
static unsigned OFS_EDGE_COLOURS = OFS_NODE_COLOURS+NUM_NODE_COLOURS;
static unsigned NUM_EDGE_COLOURS = 254;

exportToXFig::exportToXFig(const abstractMixedGraph& GG,const char* expFileName)
    throw(ERFile) : canvasBuilder(GG), expFile(expFileName, ios::out)
{
    double magnification = 1.0;

    // Set magnification ratios
    if (maxX<=maxY)
    {
        // Portrait mode
        magnification = ScaleDisplayParameters(1.0,21000,30000);
    }
    else
    {
        // Landscape mode
        magnification = ScaleDisplayParameters(1.0,30000,21000);
    }

    // Gnerg: ghostscript crashes when the canvas becomes large. So it required
    // to keep the reduced size set up by ScaleDisplayParameters() for fig2dev!
    magnification = 1.0;

    if (!expFile)
    {
        sprintf(CT.logBuffer,"Could not open export file %s, io_state %d",
            expFileName,expFile.rdstate());
        Error(ERR_FILE,"exportToXFig",CT.logBuffer);
    }

    expFile.flags(expFile.flags() | ios::right);
    expFile.setf(ios::floatfield);
    expFile.precision(5);

    expFile << "#FIG 3.2" << endl;
    expFile << "#File generated by the GOBLIN library (Release "
        << CT.MajorVersion() << "." << CT.MinorVersion()
        << CT.PatchLevel() << ")" << endl;

    if (maxX<=maxY)
    {
        expFile << "Portrait" << endl;
    }
    else
    {
        expFile << "Landscape" << endl;
    }

    expFile << "Center" << endl;
    expFile << "Metric" << endl;
    expFile << "A4" << endl;
    expFile << magnification*100.0 << endl;  // Magnification in %
    expFile << "Single" << endl;  // Single page
    expFile << "-3" << endl;      // Transparent background
    expFile << "1200 2" << endl;

    // Colours

    expFile << endl << "# Colour table:" << endl;

    expFile << "0 32 " << predColour << endl;
    expFile << "0 33 " << inftyColour << endl;
    expFile << "0 34 " << FixedNodeColour(NoNode) << endl;
    expFile << "0 35 " << "#A0A0A0" << endl;

    TNode maxColourIndex = MAX_COLOUR-NO_COLOUR-1;

    if (nodeColourMode==NODES_FLOATING_COLOURS)
    {
        if (maxNodeColour<NUM_NODE_COLOURS)
        {
            for (TNode i=0;i<=maxNodeColour;i++)
            {
                expFile << "0 " << (OFS_NODE_COLOURS+i) << " "
                    << SmoothColour(i,maxNodeColour,NO_COLOUR) << endl;
            }
        }
        else
        {
            for (TNode i=0;i<NUM_NODE_COLOURS;i++)
            {
                expFile << "0 " << (OFS_NODE_COLOURS+i) << " "
                    << SmoothColour(i,OFS_NODE_COLOURS-1,NO_COLOUR) << endl;
            }
        }
    }
    else
    {
        for (TNode i=0;i<=maxColourIndex;i++)
        {
            expFile << "0 " << (OFS_NODE_COLOURS+i) << " " << FixedNodeColour(i) << endl;
        }
    }

    if (arcColourMode==ARCS_FLOATING_COLOURS)
    {
        if (maxEdgeColour<NUM_EDGE_COLOURS)
        {
            for (TNode i=0;i<=maxEdgeColour;i++)
            {
                expFile << "0 " << (OFS_EDGE_COLOURS+i) << " "
                    << SmoothColour(i,maxEdgeColour,MAX_COLOUR) << endl;
            }
        }
        else
        {
            for (TNode i=0;i<NUM_EDGE_COLOURS;i++)
            {
                expFile << "0 " << (OFS_EDGE_COLOURS+i) << " "
                    << SmoothColour(i,NUM_EDGE_COLOURS-1,MAX_COLOUR) << endl;
            }
        }
    }
    else
    {
        for (TNode i=0;i<=maxColourIndex;i++)
        {
            expFile << "0 " << (OFS_EDGE_COLOURS+i) << " " << FixedEdgeColour(i) << endl;
        }
    }


    // Invisible bounding Box
    int minXNodeGrid = int(floor(minX/nodeSpacing-0.5));
    int maxXNodeGrid = int(ceil(maxX/nodeSpacing+0.5));
    int minYNodeGrid = int(floor(minY/nodeSpacing-0.5));
    int maxYNodeGrid = int(ceil((maxY+CFG.legenda)/nodeSpacing+0.5));

    expFile << "2 1 0 0 0 7 150 0 -1 0.000 0 0 -1 0 0 5" << endl;
    expFile << "     "
        << int(CanvasCX(minXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(minYNodeGrid*nodeSpacing)) << " "
        << int(CanvasCX(maxXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(minYNodeGrid*nodeSpacing)) << " "
        << int(CanvasCX(maxXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(maxYNodeGrid*nodeSpacing)) << " "
        << int(CanvasCX(minXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(maxYNodeGrid*nodeSpacing)) << " "
        << int(CanvasCX(minXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(minYNodeGrid*nodeSpacing)) << endl;

    // Merge the graph into a compound object

    expFile << "6 "
        << int(CanvasCX(minXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(minYNodeGrid*nodeSpacing)) << " "
        << int(CanvasCX(maxXNodeGrid*nodeSpacing)) << " "
        << int(CanvasCY(maxYNodeGrid*nodeSpacing))
        << endl;
}


unsigned long exportToXFig::Size() const throw()
{
    return
          sizeof(exportToXFig)
        + managedObject::Allocated();
}


unsigned long exportToXFig::Allocated() const throw()
{
    return 0;
}


void exportToXFig::DisplayLegenda(long xm,long ym,long radius) throw()
{
    int xl = xm-radius;
    int xr = xm+radius;
    int fillColour = 7;
    int forwardArrow = arrowPosMode!=ARROWS_CENTERED && G.Blocking(1);

    // Complete the graph compound object

    expFile << "-6" << endl;


    // Merge the legenda into a nested compound object

    expFile << "6 "
        << xl-int(nodeSize) << " " << ym-int(nodeSize) << " "
        << xr+int(nodeSize) << " " << ym+int(nodeSize) << " "
        << endl;

    expFile << "2 1 0 2 0 7 100 0 -1 0.000 0 0 -1 " << forwardArrow
        << " 0 2" << endl;
    if (forwardArrow) expFile << "2 1 3.00 120.00 150.00" << endl;
    expFile << "     "
        << xl+int(nodeSize) << " " << ym << " "
        << xr-int(nodeSize) << " " << ym << " " << endl;

    int textShift = int(arrowSize);
    if (!G.IsUndirected() && arrowPosMode==ARROWS_CENTERED)
    {
        WriteArrow(NoArc,xm+int(arrowSize),ym,1,0);
        textShift = int(2*arrowSize);
    }

    if (strcmp(arcLabelFormat,"")!=0)
    {
        int radius = int(nodeSize);

        expFile << "4 1 0 30 0 " << arcFontType << " " << int(arcFontSize) << " 0.000 4 "
            << 2*radius << " " << 2*radius << " "
            << int(xm) << " " << int(ym+5.0*arcFontSize-textShift) << " ";

        for (unsigned i=0;i<strlen(arcLabelFormat);i++)
        {
            if (arcLabelFormat[i]=='#' && i<strlen(arcLabelFormat)-1)
            {
                expFile << ArcLegenda(int(arcLabelFormat[i+1]-'0'));
                i++;
            }
            else
            {
                expFile << char(arcLabelFormat[i]);
            }
        }

        expFile << "\\001" << endl;
    }

    switch (nodeShapeMode)
    {
        case NODE_SHAPE_POINT:
        {
            WriteSmallNode(NoNode,xl,ym,fillColour);
            WriteSmallNode(NoNode,xr,ym,fillColour);
            WriteNodeLegenda(xl,ym+int(nodeSize),"u");
            WriteNodeLegenda(xr,ym+int(nodeSize),"v");

            return;
        }
        case NODE_SHAPE_BY_COLOUR:
        case NODE_SHAPE_CIRCULAR:
        {
            WriteCircularNode(NoNode,xl,ym,fillColour);
            WriteCircularNode(NoNode,xr,ym,fillColour);
            WriteNodeLegenda(xl,ym,"u");
            WriteNodeLegenda(xr,ym,"v");

            return;
        }
        case NODE_SHAPE_BOX:
        {
            WriteRectangularNode(NoNode,xl,ym,fillColour);
            WriteRectangularNode(NoNode,xr,ym,fillColour);
            WriteNodeLegenda(xl,ym,"u");
            WriteNodeLegenda(xr,ym,"v");

            return;
        }
    }
}


void exportToXFig::WriteNodeLegenda(int x,int y,char* index) throw()
{
    if (strcmp(nodeLabelFormat,"")==0) return;

    int radius = 100;

    expFile << "4 1 0 30 0 " << nodeFontType << " " << int(nodeFontSize) << " 0.000 4 "
        << 2*radius << " " << 2*radius << " "
        << int(x) << " " << int(y+5.0*nodeFontSize) << " ";

    for (unsigned i=0;i<strlen(nodeLabelFormat);i++)
    {
        if (nodeLabelFormat[i]=='#' && i<strlen(nodeLabelFormat)-1)
        {
            expFile << NodeLegenda(index,int(nodeLabelFormat[i+1]-'0'));
            i++;
        }
        else
        {
            expFile << char(nodeLabelFormat[i]);
        }
    }

    expFile << "\\001" << endl;
}


void exportToXFig::WritePolyLine(vector<double>& cx,vector<double>& cy,
    TLineStyle lineStyle,int width,TArrowDir displayedArrows,
    TIndex colourIndex,int depth) throw()
{
    int figColour = 0;

    if (colourIndex==OUTLINE_COLOUR)
    {
        figColour = 0;
    }
    else if (colourIndex==PRED_COLOUR)
    {
        figColour = 32;
    }
    else if (colourIndex==GRID_COLOUR)
    {
        figColour = 35;
    }
    else
    {
        TIndex edgeColour = colourIndex - ZERO_COLOUR;

        if (   ( arcColourMode==ARCS_FLOATING_COLOURS && edgeColour>=NUM_EDGE_COLOURS )
            || ( arcColourMode==ARCS_FIXED_COLOURS && colourIndex>=MAX_COLOUR )
           )
        {
            figColour = 0;
        }
        else
        {
            figColour = edgeColour+OFS_EDGE_COLOURS;
        }
    }

    float gap = ((lineStyle%4)==0) ? 0.000 : 3.000;

    int forwardArrow = (displayedArrows & ARROW_FORWARD) ? 1 : 0;
    int backwardArrow = (displayedArrows & ARROW_BACKWARD) ? 1 : 0;

    int nPoints = cx.size();

    if (arcShapeMode==ARC_SHAPE_SMOOTH)
    {
        expFile << "3 4 " << int(lineStyle) << " " << width << " "
            << figColour << " 7 " << depth << " 0 -1 "
            << gap << " 0 " << forwardArrow << " " << backwardArrow << " "
            << nPoints << endl;
    }
    else // if (arcShapeMode==ARC_SHAPE_POLYGONES)
    {
        expFile << "2 1 " << int(lineStyle) << " " << width << " "
            << figColour << " 7 " << depth << " 0 -1 "
            << gap << " 0 0 -1 " << forwardArrow << " " << backwardArrow << " "
            << nPoints << endl;
    }

    if (forwardArrow)
    {
        expFile << "2 1 " << width << " "
            <<       int(arrowSize*1.8/sqrt(double(width)))
            << " " << int(arrowSize*1.8/sqrt(double(width))) << endl;
    }

    if (backwardArrow)
    {
        expFile << "2 1 " << width << " "
            <<       int(arrowSize*1.8/sqrt(double(width)))
            << " " << int(arrowSize*1.8/sqrt(double(width))) << endl;
    }

    expFile << "    ";


    // Write coordinates
    vector<double>::iterator px = cx.begin();
    vector<double>::iterator py = cy.begin();

    while (px!=cx.end() && py!=cy.end())
    {
        expFile << " " << long(*px) << " " << long(*py);
        ++px;
        ++py;
    }

    expFile << endl;


    if (arcShapeMode==ARC_SHAPE_SMOOTH)
    {
        expFile << "     0.000";

        for (size_t i=0;i<cx.size()-2;++i) expFile << " 1.000";

        expFile << " 0.000" << endl;
    }
}


void exportToXFig::WriteArrow(TArc,long xtop,long ytop,double dx,double dy) throw()
{
    double ox = dy;
    double oy = -dx;

    expFile << "2 3 0 1 0 0 100 0 20 0.000 1 0 -1 0 0 5" << endl;
    expFile << "     "
        << int(xtop) << " "
        << int(ytop) << " "
        << int(xtop-(1.8*dx+0.7*ox)*arrowSize) << " "
        << int(ytop-(1.8*dy+0.7*oy)*arrowSize) << " "
        << int(xtop-1.4*dx*arrowSize) << " "
        << int(ytop-1.4*dy*arrowSize) << " "
        << int(xtop-(1.8*dx-0.7*ox)*arrowSize) << " "
        << int(ytop-(1.8*dy-0.7*oy)*arrowSize) << " "
        << int(xtop) << " "
        << int(ytop) << endl;
}


void exportToXFig::WriteArcLabel(TArc a,long xm,long ym) throw()
{
    int radius = int(nodeSize);

    expFile << "4 1 0 90 0 " << arcFontType << " " << int(arcFontSize) << " 0.000 4 "
        << 2*radius << " " << 2*radius << " "
        << int(xm) << " " << int(ym+5.0*arcFontSize) << " ";

    ComposeArcLabel(a,expFile);

    expFile << "\\001" << endl;
}


void exportToXFig::DisplayNode(TNode v) throw()
{
    int fillColour = 7;

    if (nodeColourMode==NODES_COLOUR_BY_DISTANCE)
    {
        if (G.Dist(v)==InfFloat || G.Dist(v)==-InfFloat) fillColour = 33;
    }
    else if (nodeColourMode==NODES_FIXED_COLOURS)
    {
        TNode maxColourIndex = MAX_COLOUR-NO_COLOUR-1;

        fillColour = (G.NodeColour(v)<=maxColourIndex)
            ? OFS_NODE_COLOURS+G.NodeColour(v) : OFS_NODE_COLOURS+maxColourIndex;
    }
    else if (nodeColourMode==NODES_COLOUR_BY_DEMAND)
    {
        if (G.Demand(v)>0) fillColour = OFS_NODE_COLOURS+1;
        if (G.Demand(v)<0) fillColour = OFS_NODE_COLOURS+2;
    }
    else if (nodeColourMode==NODES_FLOATING_COLOURS)
    {
        fillColour = G.NodeColour(v);
        if (fillColour>=int(NUM_NODE_COLOURS)) fillColour = 34;
        else fillColour += OFS_NODE_COLOURS;
    }

    int x = CanvasCXOfPoint(v);
    int y = CanvasCYOfPoint(v);

    switch (nodeShapeMode)
    {
        case NODE_SHAPE_POINT:
        {
            WriteSmallNode(v,x,y,fillColour);
            TNode p = G.ThreadSuccessor(v);

            if (p==NoNode)
            {
                WriteNodeLabel(v,x+int(nodeSize),y+int(nodeSize));
            }
            else
            {
                WriteNodeLabel(v,CanvasCXOfPoint(p),CanvasCYOfPoint(p));
            }

            return;
         }
         case NODE_SHAPE_CIRCULAR:
         {
             WriteCircularNode(v,x,y,fillColour);
             WriteNodeLabel(v,x,y);

             return;
         }
         case NODE_SHAPE_BOX:
         {
             WriteRectangularNode(v,x,y,fillColour);
             WriteNodeLabel(v,x,y);

             return;
         }
         case NODE_SHAPE_BY_COLOUR:
         {
             WriteRegularNode(v,x,y,fillColour);
             WriteNodeLabel(v,x,y);

             return;
         }
    }
}


void exportToXFig::WriteSmallNode(TNode v,int x,int y,int fillColour) throw()
{
    int radius = 60;

    expFile << "1 3 0 4 0 0 50 0 20 0.000 1 0.0000 "
        << x << " " << y << " "
        << radius << " " << radius << " "
        << x-radius << " " << y << " "
        << x+radius  << " " << y << endl;
}


void exportToXFig::WriteCircularNode(TNode v,int x,int y,int fillColour) throw()
{
    double radius = nodeSize;

    if (v!=NoNode && G.ThreadSuccessor(v)!=NoNode)
    {
        TNode w = G.ThreadSuccessor(v);
        radius += sqrt(G.C(w,0)*G.C(w,0)+G.C(w,1)*G.C(w,1))*zoom;
    }

    expFile << "1 3 0 1 0 " << fillColour
        << " 50 0 20 0.000 1 0.0000 "
        << x << " " << y << " "
        << RoundToInt(radius) << " " << RoundToInt(radius) << " "
        << RoundToInt(x-radius) << " " << y << " "
        << RoundToInt(x+radius) << " " << y << endl;
}


void exportToXFig::WriteRectangularNode(TNode v,int x,int y,int fillColour) throw()
{
    double width  = nodeSize;
    double height = nodeSize;

    if (v!=NoNode && G.ThreadSuccessor(v)!=NoNode)
    {
        TNode w = G.ThreadSuccessor(v);

        width  += G.C(w,0)*zoom;
        height += G.C(w,1)*zoom;
    }

    expFile << "2 2 0 1 0 " << fillColour
        << " 50 0 20 0.000 0 0 0 0 0 5 " << endl << "   "
        << RoundToInt(x-width) << " " << RoundToInt(y-height) << " "
        << RoundToInt(x-width) << " " << RoundToInt(y+height) << " "
        << RoundToInt(x+width) << " " << RoundToInt(y+height) << " "
        << RoundToInt(x+width) << " " << RoundToInt(y-height) << " "
        << RoundToInt(x-width) << " " << RoundToInt(y-height) << endl;
}


void exportToXFig::WriteRegularNode(TNode v,int x,int y,int fillColour) throw()
{
    if (G.NodeColour(v)==0 || G.NodeColour(v)>G.N())
    {
        return WriteCircularNode(v,x,y,fillColour);
    }

    unsigned nVertices = G.NodeColour(v)+3;
    double radius = nodeSize/cos(PI/nVertices);

    expFile << "2 3 0 1 0 " << fillColour
        << " 50 0 20 0.000 0 0 0 0 0 " << (nVertices+1) << endl << "   ";

    for (unsigned i=0;i<=nVertices;++i)
    {
        double xRel = radius*sin((i+0.5)*2*PI/nVertices);
        double yRel = radius*cos((i+0.5)*2*PI/nVertices);

        expFile << RoundToInt(x-xRel) << " " << RoundToInt(y+yRel) << " ";
    }

    expFile << endl;
}


void exportToXFig::WriteNodeLabel(TNode v,int x,int y) throw()
{
    if (strcmp(nodeLabelFormat,"")==0) return;

    int radius = 100;

    expFile << "4 1 0 30 0 " << nodeFontType << " " << int(nodeFontSize) << " 0.000 4 "
        << 2*radius << " " << 2*radius << " "
        << int(x) << " " << int(y+5.0*nodeFontSize) << " ";

    ComposeNodeLabel(v,expFile);

    expFile << "\\001" << endl;
}


exportToXFig::~exportToXFig() throw()
{
    expFile << endl;
    expFile.close();
}
