#include <../../nrnconf.h>
#if HAVE_IV  // to end of file

#include <stdio.h>
#include <ivstream.h>
#include <InterViews/color.h>
#include <InterViews/brush.h>
#include <InterViews/font.h>
#include <IV-look/kit.h>
#include <InterViews/transformer.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <OS/string.h>

#include "scenevie.h"
#include "mymath.h"
#include "idraw.h"

#define out *OcIdraw::idraw_stream

extern "C" {
extern const char* expand_env_var(const char*);
}  // extern "C"

bool OcIdraw::closed_ = false;
bool OcIdraw::curved_ = false;
int OcIdraw::ipath_ = 0;
int OcIdraw::capacity_ = 0;
Coord* OcIdraw::xpath_ = 0;
Coord* OcIdraw::ypath_ = 0;

void OcIdraw::prologue() {
    filebuf ibuf;
    Style* s = Session::instance()->style();
    CopyString name;
    if (!s->find_attribute("pwm_idraw_prologue", name)) {
        printf("can't find the \"pwm_idraw_prologue\" attribute\n");
        printf("will have to prepend the prologue by hand before reading with idraw.\n");
        return;
    }
    name = expand_env_var(name.string());
#if defined(WIN32) || defined(MAC)
    if (!ibuf.open(name.string(), ios::in)) {
#else
    if (!ibuf.open(name.string(), IOS_IN)) {
#endif
        printf("can't open the idraw prologue in %s\n", name.string());
        return;
    }
    out << &ibuf << endl;
    ibuf.close();
    if (!xpath_) {
        capacity_ = 10;
        xpath_ = new Coord[capacity_];
        ypath_ = new Coord[capacity_];
    }
}

void OcIdraw::epilog() {
    out << "\
End %I eop\n\
showpage\n\n\
%%Trailer\n\n\
end\
" << endl;
}

static void transformer(const Transformer& t) {
    float a00, a01, a10, a11, a20, a21;
    t.matrix(a00, a01, a10, a11, a20, a21);
    char buf[200];
    sprintf(buf, "[ %g %g %g %g %g %g ] concat", a00, a01, a10, a11, a20, a21);
    out << buf << endl;
}

static char* hidepar(const char* s) {
    static char buf[256];
    const char* ps;
    char* pbuf;
    for (ps = s, pbuf = buf; *ps;) {
        if (*ps == '(' || *ps == ')') {
            *pbuf++ = '\\';
        }
        *pbuf++ = *ps++;
    }
    *pbuf = '\0';
    return buf;
}

static void rgbcolor(const Color* c, ColorIntensity& r, ColorIntensity& g, ColorIntensity& b) {
    // always use black for default_foreground.
    if (c == Scene::default_foreground()) {
        r = 0;
        g = 0;
        b = 0;
    } else {
        c->intensities(r, g, b);
    }
}

static void common_pict() {
    out << "\n\
Begin %I Pict\n\
%I b u\n\
%I cfg u\n\
%I cbg u\n\
%I f u\n\
%I p u\
" << endl;
}

void OcIdraw::pict() {
    common_pict();
    out << "%I t u" << endl;
}

void OcIdraw::pict(const Transformer& t) {
    common_pict();
    out << "%I t" << endl;
    transformer(t);
}

void OcIdraw::end() {
    out << "End %I eop" << endl;
}

void OcIdraw::text(Canvas*,
                   const char* s,
                   const Transformer& t,
                   const Font* font,
                   const Color* color) {
#if 1
    // ZFM tried to allow colors and fonts, but doesn't seem to work
    // 3/12/95
    char buf[100];
    ColorIntensity r = 0, g = 0, b = 0;
    if (color) {
        rgbcolor(color, r, g, b);
    }

    // idraw needs hex
    sprintf(buf,
            "%%I cfg %x%x%x\n%f %f %f SetCFg\n",
            int(r * 256),
            int(g * 256),
            int(b * 256),
            r,
            g,
            b);
    out << "Begin %I Text\n";
    out << buf;
    if (font) {
        out << "%I f " << font->encoding() << "\n";
        out << font->name() << font->size() << "SetF\n";
    } else {
        out << "\
%I f -*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*\n\
Helvetica 12 SetF\n\
";
    };
    out << "%I t" << endl;
#else
    out << "\n\
Begin %I Text\n\
%I cfg Black\n\
0 0 0 SetCFg\n\
%I f -*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*\n\
Helvetica 12 SetF\n\
%I t\
" << endl;
#endif
    Glyph* l = WidgetKit::instance()->label(s);
    Requisition req;
    l->request(req);
    l->unref();
    Requirement& ry = req.y_requirement();
    float y = (1. - ry.alignment()) * ry.natural();
    float x = 0;
    Transformer tr(t);
    tr.translate(x, y);
    transformer(tr);
    out << "%I\n[" << endl;
    out << "(" << hidepar(s) << ")" << endl;
    out << "] Text\nEnd" << endl;
}

void OcIdraw::mline(Canvas*,
                    int count,
                    const Coord* x,
                    const Coord* y,
                    const Color* color,
                    const Brush* b) {
#define cnt_group 200
    int ixd[cnt_group], iyd[cnt_group];
    int i, size;
    float xmax = x[0], xmin = x[0], ymax = y[0], ymin = y[0];
#if 0
	for (i = 1; i < count; ++i) {
		if (x[i] > xmax) xmax = x[i];
		if (x[i] < xmin) xmin = x[i];
		if (y[i] > ymax) ymax = y[i];
		if (y[i] < ymin) ymin = y[i];
	}
#endif
    XYView* v = XYView::current_draw_view();
    xmax = v->right();
    xmin = v->left();
    ymax = v->top();
    ymin = v->bottom();

    float scalex, scaley;
    if (xmax != xmin) {
        scalex = 10000 / (xmax - xmin);
    } else {
        scalex = 1;
    }
    if (ymax != ymin) {
        scaley = 10000 / (ymax - ymin);
    } else {
        scaley = 1;
    }
    Transformer t;
    t.translate(-xmin, -ymin);
    t.scale(scalex, scaley);
    t.invert();
    if (count > cnt_group) {
        pict();
    }
    for (i = 0; i < count;) {
        int ixold, iyold;
        ixold = iyold = -20000;
        for (; i < count; ++i) {
            if (MyMath::inside(x[i], y[i], xmin, ymin, xmax, ymax)) {
                break;
            }
        }
        for (size = 0; i < count;) {
            float x1, y1;
            int ix, iy;
            t.inverse_transform(x[i], y[i], x1, y1);
            if (x1 > 20000.)
                x1 = 20000.;
            if (x1 < -20000.)
                x1 = -20000.;
            if (y1 > 20000.)
                y1 = 20000.;
            if (y1 < -20000.)
                y1 = -20000.;
            ix = int(x1);
            iy = int(y1);
            if (ix != ixold || iy != iyold) {
                ixd[size] = ix;
                iyd[size] = iy;
                ++size;
            }
            if (size >= cnt_group) {
                break;
            }
            ixold = ix;
            iyold = iy;
            ++i;
        }
        if (size < 2) {
            break;
        }
#if 1
        out << "\nBegin %I MLine\n";
        brush(b);
        ifill(color, false);
        out << "%I t" << endl;
#else
        out << "\n\
Begin %I MLine\n\
%I b 65535\n\
0 0 0 [] 0 SetB\n\
%I cfg Black\n\
0 0 0 SetCFg\n\
%I cbg White\n\
1 1 1 SetCBg\n\
none SetP %I p n\n\
%I t\
" << endl;
#endif
        transformer(t);
        out << "%I " << size << endl;

        for (int j = 0; j < size; ++j) {
            out << ixd[j] << " " << iyd[j] << endl;
        }

        out << size << " MLine\n%I 1\nEnd" << endl;
    }

    if (count > cnt_group) {
        end();
    }
}

void OcIdraw::rect(Canvas* c,
                   Coord x1,
                   Coord y1,
                   Coord x2,
                   Coord y2,
                   const Color* color,
                   const Brush* b,
                   bool f) {
    Coord x[4], y[4];
    x[0] = x1;
    y[0] = y1;
    x[1] = x2;
    y[1] = y1;
    x[2] = x2;
    y[2] = y2;
    x[3] = x1;
    y[3] = y2;
    polygon(c, 4, x, y, color, b, f);
}

void OcIdraw::polygon(Canvas*,
                      int count,
                      const Coord* x,
                      const Coord* y,
                      const Color* color,
                      const Brush* b,
                      bool f) {
    char buf[100];
    out << "\nBegin %I Poly\n";
    poly(count, x, y, color, b, f);
    sprintf(buf, "%d Poly\nEnd", count);
    out << buf << endl;
}

void OcIdraw::bspl(Canvas*,
                   int count,
                   const Coord* x,
                   const Coord* y,
                   const Color* color,
                   const Brush* b) {
    char buf[100];
    out << "\nBegin %I BSpl\n";
    poly(count, x, y, color, b, false);
    sprintf(buf, "%d BSpl\n%%I 1\nEnd", count);
    out << buf << endl;
}

void OcIdraw::cbspl(Canvas*,
                    int count,
                    const Coord* x,
                    const Coord* y,
                    const Color* color,
                    const Brush* b,
                    bool f) {
    char buf[100];
    out << "\nBegin %I CBSpl\n";
    poly(count, x, y, color, b, f);
    sprintf(buf, "%d CBSpl\nEnd", count);
    out << buf << endl;
}

void OcIdraw::poly(int count,
                   const Coord* x,
                   const Coord* y,
                   const Color* color,
                   const Brush* b,
                   bool f) {
    brush(b);
    ifill(color, f);
    out << "%I t" << endl;


    float x1, x2, y1, y2;
    x1 = MyMath::min(count, x);
    x2 = MyMath::max(count, x);
    y1 = MyMath::min(count, y);
    y2 = MyMath::max(count, y);
    float scalex, scaley;
    if (Math::equal(x1, x2, float(.0001))) {
        scalex = 1;
    } else {
        scalex = (x2 - x1) / 10000.;
    }
    if (Math::equal(y1, y2, float(.0001))) {
        scaley = 1;
    } else {
        scaley = (y2 - y1) / 10000.;
    }
    Transformer t;
    t.scale(scalex, scaley);
    t.translate(x1, y1);
    transformer(t);
    out << "%I " << count << endl;
    char buf[100];
    for (int i = 0; i < count; ++i) {
        float a, b;
        t.inverse_transform(x[i], y[i], a, b);
        sprintf(buf, "%d %d\n", int(a), int(b));
        out << buf;
    }
}

void OcIdraw::line(Canvas*,
                   Coord x1,
                   Coord y1,
                   Coord x2,
                   Coord y2,
                   const Color* color,
                   const Brush* b) {
#if 1
    out << "\nBegin %I Line\n";
    brush(b);
    ifill(color, false);
    out << "%I t" << endl;
#else
    out << "\n\
Begin %I Line\n\
%I b 65535\n\
0 0 0 [] 0 SetB\n\
%I cfg Black\n\
0 0 0 SetCFg\n\
%I cbg White\n\
1 1 1 SetCBg\n\
none SetP %I p n\n\
%I t\
" << endl;
#endif

    float scalex, scaley;
    if (Math::equal(x1, x2, float(.0001))) {
        scalex = 1;
    } else {
        scalex = (x2 - x1) / 10000;
    }
    if (Math::equal(y1, y2, float(.0001))) {
        scaley = 1;
    } else {
        scaley = (y2 - y1) / 10000;
    }
    Transformer t;
    t.scale(scalex, scaley);
    t.translate(x1, y1);
    transformer(t);
    out << "%I" << endl;
    float a, bb, x, y;
    t.inverse_transform(x1, y1, a, bb);
    t.inverse_transform(x2, y2, x, y);
    out << int(a) << " " << int(bb) << " " << int(x) << " " << int(y);

    out << " Line\n%I 1\nEnd" << endl;
}

void OcIdraw::ellipse(Canvas*,
                      Coord x1,
                      Coord y1,
                      Coord width,
                      Coord height,
                      const Color* color,
                      const Brush* b,
                      bool f) {
    out << "\nBegin %I Elli\n";
    brush(b);
    ifill(color, f);
    out << "%I t" << endl;

    float y = y1;
    float x = x1;
    Transformer tr;
    tr.scale(.01, .01);
    tr.translate(x, y);
    transformer(tr);
    char buf[100];
    sprintf(buf, "%%I\n0 0 %d %d Elli\nEnd", int(width * 100), int(height * 100));
    out << buf << endl;
}

void OcIdraw::brush(const Brush* b) {
    char buf[100];
    Coord w = b ? b->width() : 0;
    int i, p;

    p = 0;
#if !MAC
    if (b)
        for (i = 0; i < b->dash_count(); ++i) {
            int nbit = b->dash_list(i);
            for (int j = 0; j < nbit; ++j) {
                p = ((p << 1) | ((i + 1) % 2));
            }
        }
#endif
    sprintf(buf, "%%I b %d\n%d 0 0 [", p, int(w));
    out << buf;
#if !MAC
    if (b)
        for (i = 0; i < b->dash_count(); ++i) {
            out << b->dash_list(i) << " ";
        }
#endif
    sprintf(buf, "] 0 SetB");
    out << buf << endl;
}

void OcIdraw::ifill(const Color* color, bool f) {
    char buf[100];
    ColorIntensity r = 0, g = 0, b = 0;
    if (color) {
        rgbcolor(color, r, g, b);
    }
    //	sprintf(buf, "%%I cfg %s\n%d %d %d SetCFg", "Black", 0,0,0);
    // idraw needs hex
    sprintf(
        buf, "%%I cfg %x%x%x\n%f %f %f SetCFg", int(r * 256), int(g * 256), int(b * 256), r, g, b);
    out << buf << endl;
    if (f) {
        //		sprintf(buf, "%%I cbg %s\n%d %d %d SetCBg\n%%I p\n1 SetP",
        //		 "Black", 0,0,0);
        sprintf(buf,
                "%%I cbg %x%x%x\n%f %f %f SetCBg\n%%I p\n1 SetP",
                int(r * 256),
                int(g * 256),
                int(b * 256),
                r,
                g,
                b);
    } else {
        sprintf(buf, "%%I cbg %s\n%d %d %d SetCBg\nnone SetP %%I p n", "White", 1, 1, 1);
    }
    out << buf << endl;
}

// only implemented for continuous lines, polygons, and curves.
// i.e. only one move_to at beginning and cannot mix line_to and curve_to

void OcIdraw::new_path() {
    curved_ = false;
    closed_ = false;
    ipath_ = 0;
}

void OcIdraw::move_to(Coord x, Coord y) {
    add(x, y);
}

void OcIdraw::line_to(Coord x, Coord y) {
    add(x, y);
}

void OcIdraw::curve_to(Coord x, Coord y, Coord x1, Coord y1, Coord x2, Coord y2) {
    curved_ = true;
#if 0
	add(x1, y1);
	add(x2, y2);
	add(x, y);
#else  // http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm
    rcurve(0, x, y, x1, y1, x2, y2);  // first arg is recursion level
#endif
}

void OcIdraw::rcurve(int r, Coord x, Coord y, Coord x1, Coord y1, Coord x2, Coord y2) {
    if (r < 2) {
        // split into two bezier
        Coord m01x = (xpath_[ipath_ - 1] + x1) / 2;
        Coord m01y = (ypath_[ipath_ - 1] + y1) / 2;
        Coord m12x = (x1 + x2) / 2;
        Coord m12y = (y1 + y2) / 2;
        Coord m23x = (x2 + x) / 2;
        Coord m23y = (y2 + y) / 2;
        Coord ax = (m01x + m12x) / 2;
        Coord ay = (m01y + m12y) / 2;
        Coord bx = (m12x + m23x) / 2;
        Coord by = (m12y + m23y) / 2;
        Coord cx = (ax + bx) / 2;
        Coord cy = (ay + by) / 2;
        rcurve(r + 1, cx, cy, m01x, m01y, ax, ay);
        rcurve(r + 1, x, y, bx, by, m23x, m23y);
    } else {
        add((x1 + x2) / 2, (y1 + y2) / 2);
        add(x, y);
    }
}

void OcIdraw::close_path() {
    closed_ = true;
    if (curved_) {
        curve_to(
            xpath_[0], ypath_[0], xpath_[ipath_ - 1], ypath_[ipath_ - 1], xpath_[0], ypath_[0]);
    }
}

void OcIdraw::stroke(Canvas* can, const Color* c, const Brush* b) {
    if (closed_) {
        if (curved_) {
            cbspl(can, ipath_, xpath_, ypath_, c, b, false);
        } else {
            polygon(can, ipath_, xpath_, ypath_, c, b, false);
        }
    } else {
        if (curved_) {
            bspl(can, ipath_, xpath_, ypath_, c, b);
        } else {
            mline(can, ipath_, xpath_, ypath_, c, b);
        }
    }
}

void OcIdraw::fill(Canvas* can, const Color* c) {
    if (curved_) {
        cbspl(can, ipath_, xpath_, ypath_, c, NULL, true);
    } else {
        polygon(can, ipath_, xpath_, ypath_, c, NULL, true);
    }
}

void OcIdraw::add(Coord a, Coord b) {
    if (ipath_ >= capacity_) {
        capacity_ *= 2;
        Coord* x = new Coord[capacity_];
        Coord* y = new Coord[capacity_];
        for (int i = 0; i < ipath_; ++i) {
            x[i] = xpath_[i];
            y[i] = ypath_[i];
        }
        delete[] xpath_;
        delete[] ypath_;
        xpath_ = x;
        ypath_ = y;
    }
    xpath_[ipath_] = a;
    ypath_[ipath_] = b;
    ++ipath_;
}

#endif
