// -*-c++-*-

// fixg2sxd - a utility to convert fig to sxd format

// Copyright (C) 2003-2008 Alexander Bürger, acfb@users.sourceforge.net

// 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 "styles.h"

#include "xmlwrite.h"
#include "check.h"
#include "colors.h"
#include "misc.h"

extern unsigned int *hexcolor;

// TODO: cap styles

using namespace std;

namespace {
    struct Dash {
        int line_style;
        int style_val; // xfig uses float
        Dash( int ls, int sv ) : line_style( ls ), style_val( sv ) { }
        bool operator<(Dash const& other) const;
        string GetNameAndWrite(Node& out) const;
    };

    set<Dash> written_dashes;

    string Dash::GetNameAndWrite(Node& out) const
    {
        if( line_style < 1 || line_style > 5 )
            fail( "bad line_style < 1 or > 5 for dash" );

        const char* names[6] = { 0/*solid*/, "dashed", "dotted", "dash-dotted",
                                 "dash-double-dotted", "dash-triple-dotted" };
        ostringstream name;
        name << names[line_style] << '_' << style_val;
        if( written_dashes.find( *this ) == written_dashes.end() ) {
            written_dashes.insert( *this );

            Node& dash = out.subnode("draw:stroke-dash");
            dash["draw:dots1"] << "1";
            dash["draw:dots1-length"] << (line_style == 2 ? 0.05:0.2) << "cm";
            dash["draw:distance"] << tr80(style_val) << "cm";
            dash["draw:style"] << "rect";
            if( line_style >= 3 ) {
                dash["draw:dots2"] << (line_style-2);
                dash["draw:dots2-length"] << "0.05cm";
            }
            dash["draw:name"] << name.str();
        }
        return name.str();
    }

    // ------------------------------------------------------------

    struct Hatch {
        int style;
        int angle;
        int density;
        bool operator<(Hatch const& other) const;
    };

    Hatch hatches[] = {
        { 1,150,3 }, { 1,30,3 }, { 2, 30,3 }, { 1,135,2 }, { 1, 45,2 },
        { 2, 45,2 }, { 2,10,2 }, { 2,100,2 }, { 1,  0,3 }, { 1, 90,3 },
        { 2,  0,3 }, { 2,20,3 }, { 2, 70,3 }, { 2, 15,3 }, { 2,115,3 },
        { 2, 30,3 }, { 3, 0,4 }, { 2,  0,1 }, { 2, 45,1 }, { 2,  0,2 },
        { 2, 40,2 }, { 2,50,2 }
    };

    typedef pair<Hatch,int> hatch_color;
    set<hatch_color> written_hatches;

    string write_hatch(Node& out, int h, int color)
    {
        ostringstream name;
        name << "Hatch_" << hatches[h].style << '_'
             << hatches[h].angle << '_' << hatches[h].density
             << '_' << color+1;
        hatch_color hc( hatches[h], color );
        if( written_hatches.find(hc) == written_hatches.end() ) {
            Node& hatch = out.subnode("draw:hatch");
            hatch["draw:name"] << name.str();
            static const char *styles[3] = { "single", "double", "triple" };
            hatch["draw:style"] << styles[hatches[h].style-1];
            hatch["draw:distance"] << 0.3/hatches[h].density << "cm";
            hatch["draw:rotation"] << 10*hatches[h].angle;
            hatch["draw:color"] << colorstring(color);
            written_hatches.insert( hc );
        }
        return name.str();
    }
}

const char* LineFillStyle::base = "Fig2SxdLine";

int LineFillStyle::number = 10;

void LineFillStyle::read( istream& figfile, int& sub_type, int& depth, bool join )
{
    figfile >> sub_type
            >> line_style
            >> line_thickness
            >> pen_color
            >> fill_color
            >> depth
            >> pen_style
            >> area_fill
            >> style_val;
    if( join )
        figfile >> linejoin;
    else
        linejoin = 0;

    keep_range( line_style,     "line_style",      -1, 5 );
    keep_range( line_thickness, "line_thickness", 0.0f );
    keep_range( pen_color,      "pen_color",       -1, 543 );
    keep_range( fill_color,     "fill_color",      -1, 543 );
    // pen style is unused, so ignore its value
    if( fill_color<=0 || fill_color==7 )
        keep_range( area_fill,  "area_fill", -1, 56 );
    else
        keep_range( area_fill,  "area_fill", -1, 62 );
    keep_range( style_val,      "style_val", 0.f );
    if( join )
        keep_range( linejoin,  "line_join", 0, 2 );

    // do not check upper limit for depth, but it should be >= 0
    keep_range( depth, "depth", 0 );
}

std::string LineFillStyle::stylename() const
{
    if( mynumber == 0 )
        mynumber = ++number;
    ostringstream tmp;
    tmp << base << mynumber;
    return tmp.str();
}

std::string LineFillStyle::stylename_line() const
{
    if( mynumber_line == 0 )
        mynumber_line = ++number;
    ostringstream tmp;
    tmp << base << mynumber_line;
    return tmp.str();
}

std::string LineFillStyle::stylename_fill() const
{
    if( mynumber_fill == 0 )
        mynumber_fill = ++number;
    ostringstream tmp;
    tmp << base << mynumber_fill;
    return tmp.str();
}

void LineFillStyle::read_arrow( istream& figfile, bool forward )
{
    Arrow tmp;
    tmp.read( figfile );
    arrowset::iterator a = arrows.insert( tmp ).first;
    if( forward )
        forward_arrow = a;
    else
        backward_arrow = a;
}

bool LineFillStyle::hasLine() const
{
    return line_thickness>0;
}

bool LineFillStyle::hasFill() const
{
    return area_fill!=-1;
}

float LineFillStyle::linewith1 = 2.54/80.0;

Node& LineFillStyle::write( Node& out ) const
{
    int numbers[3] = { mynumber, mynumber_line, mynumber_fill };
    string dashname;
    if( line_style>=1 && ( mynumber || mynumber_line ) ) {
        Dash dash( line_style, int(style_val) );
        dashname = dash.GetNameAndWrite( out );
    }
    for( int n=0; n<3; ++n ) {
        if( numbers[n] == 0 )
            continue;
        Node& style = out.subnode("style:style");
        style["style:name"] << base << numbers[n];
        style["style:family"] << "graphics";
        style["style:parent-style-name"] << base;
        Node& prop = style.subnode("style:properties");
        if( n==0 || n==1 ) {
            if( forward_arrow != arrows.end() ) {
                prop["draw:marker-end"] << forward_arrow->name();
                prop["draw:marker-end-width"]
                    << tr(forward_arrow->arrow_width) << "cm";
            }
            if( backward_arrow != arrows.end() ) {
                prop["draw:marker-start"] << backward_arrow->name();
                prop["draw:marker-start-width"]
                    << tr(backward_arrow->arrow_width) << "cm";
            }
        }
        if( line_thickness==0 || n==2 ) {
            prop["draw:stroke"] << "none";
        } else {
            if( linejoin >= 0 ) {
                const char* join[3] = { "miter", "round", "bevel" };
                prop["svg:stroke-linejoin"] << join[linejoin];
            }
            if( line_thickness == 1  )
                prop["svg:stroke-width"] << linewith1 << "cm";
            else
                prop["svg:stroke-width"] << tr80(line_thickness) << "cm";
            prop["svg:stroke-color"] << colorstring(pen_color);
            if( dashname.length() ) {
                prop["draw:stroke"] << "dash";
                prop["draw:stroke-dash"] << dashname;
            } else {
                prop["draw:stroke"] << "solid";
            }
        }
        if( n==1 || area_fill == -1 ) {
            prop["draw:fill"] << "none";
        } else if( area_fill >= 0 && area_fill <= 40 ) {
            unsigned int r= (hexcolor[fill_color] >> 16) & 0xff;
            unsigned int g= (hexcolor[fill_color] >> 8 ) & 0xff;
            unsigned int b= (hexcolor[fill_color]      ) & 0xff;
            if( area_fill < 20 ) {
                r = (unsigned int)(r*area_fill/20.0);
                g = (unsigned int)(g*area_fill/20.0);
                b = (unsigned int)(b*area_fill/20.0);
            } else if( area_fill > 20 ) {
                r = 0xff - (unsigned int)((0xff-r)*area_fill/20.0);
                g = 0xff - (unsigned int)((0xff-g)*area_fill/20.0);
                b = 0xff - (unsigned int)((0xff-b)*area_fill/20.0);
            }
            unsigned int fill = (r << 16) | (g << 8) | b;
            prop["draw:fill"] << "solid";
            prop["draw:fill-color"] << color2string( fill );
        } else if( area_fill >= 41 && area_fill <= 62 ) {
            prop["draw:fill"] << "hatch";
            prop["draw:fill-hatch-name"]
                << write_hatch( out, area_fill-41, pen_color );
            prop["draw:fill-hatch-solid"] << "true";
            prop["draw:fill-color"] << colorstring( fill_color );
        }
    }
    return out;
}

#define cmp(x) if( x < o.x ) return true; if( x > o.x ) return false
#define cmpi(x,s) if( x==s.end() ) { if( o.x!=s.end() ) return true; } \
    else { if( o.x==s.end() ) return false; else if( *x<*o.x ) return true; }
bool LineFillStyle::operator<(LineFillStyle const& o) const
{
    cmp( line_style );
    cmp( line_thickness );
    cmp( pen_color );
    cmp( fill_color );
    cmp( fill_color );
    cmp( pen_style );
    cmp( area_fill );
    cmp( style_val );
    cmp( linejoin );
    cmpi( forward_arrow, arrows );
    cmpi( backward_arrow, arrows );
    if( forward_arrow!=arrows.end() && o.forward_arrow!=arrows.end() )
        cmp( forward_arrow->arrow_width );
    if( backward_arrow!=arrows.end() && o.backward_arrow!=arrows.end() )
        cmp( backward_arrow->arrow_width );
    return false;
}

namespace {
    bool Dash::operator<(Dash const& o) const
    {
        cmp( line_style );
        cmp( style_val );
        return false;
    }
    bool Hatch::operator<(Hatch const& o) const
    {
        cmp( style );
        cmp( angle );
        cmp( density );
        return false;
    }
}

lfsset linefillstyles;
