
/*******************************************************************************/
/* Copyright (C) 2008 Jonathan Moore Liles                                     */
/*                                                                             */
/* 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; see the file COPYING.  If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
/*******************************************************************************/

#include "Panner.H"
#include <FL/x.H>
#include <FL/Fl.H>
#include <stdio.h>
#include <math.h>

#include <FL/Fl_Shared_Image.H>
#include <FL/Fl_Tiled_Image.H>

#include "../../nonlib/debug.h"


/* 2D Panner widget. Supports various multichannel configurations. */

Panner::Point *Panner::drag;
int Panner::_range_mode = 1;
int Panner::_projection_mode = 0;

Panner::Panner ( int X, int Y, int W, int H, const char *L ) :
    Fl_Group( X, Y, W, H, L ),
    _bg_image(0),
    _bg_image_scaled(0),
    _bg_image_projection(0)
{
    _points.push_back( Point( 1, 0 ) );
    
    static float ranges[] = { 1,3,5,10,15 };
    { Fl_Choice *o = _range_choice = new Fl_Choice(X + 40,Y + H - 18,75,18,"Range:");
        o->box(FL_UP_FRAME);
        o->down_box(FL_DOWN_FRAME);
        o->textsize(9);
        o->labelsize(9);
        o->align(FL_ALIGN_LEFT);
        o->add("1 Meter",0,0,&ranges[0]);
        o->add("3 Meters",0,0,&ranges[1]);
        o->add("5 Meters",0,0,&ranges[2]);
        o->add("10 Meters",0,0,&ranges[3]);
        o->add("15 Meters",0,0,&ranges[4]);
	o->textcolor( fl_contrast( FL_GRAY0, FL_FOREGROUND_COLOR ));
        o->value(_range_mode);
        o->callback( cb_mode, this );
    }

    { Fl_Choice *o = _projection_choice = new Fl_Choice(X + W - 75,Y + H - 18,75,18,"Projection:");
        o->box(FL_UP_FRAME);
        o->down_box(FL_DOWN_FRAME);
        o->textsize(9);
        o->labelsize(9);
        o->align(FL_ALIGN_LEFT);
        o->add("Spherical");
        o->add("Planar");
        o->value(_projection_mode);
	o->textcolor( fl_contrast( FL_GRAY0, FL_FOREGROUND_COLOR ));
        o->callback( cb_mode, this );
    }
                  
    end();
}

Panner::~Panner (  )
{
    if ( _bg_image )
    {
        if ( _bg_image_scaled )
            delete _bg_image;
        else
            ((Fl_Shared_Image*)_bg_image)->release();
    }
}

static int find_numeric_menu_item( const Fl_Menu_Item *menu, int n )
{
    for ( unsigned int i = 0; menu[i].text; i++ )
    {
	if ( atoi( menu[i].text ) == n )
            return i;
    }

    return -1;
}

void
Panner::cb_mode ( Fl_Widget *w, void *v )
{
    ((Panner*)v)->cb_mode( w );
}

void
Panner::cb_mode ( Fl_Widget *w )
{
    if ( w == _range_choice )
        _range_mode = _range_choice->value();
    else if ( w == _projection_choice )
        _projection_mode = _projection_choice->value();
}

void
Panner::range ( float v )
{
    int i = find_numeric_menu_item( _range_choice->menu(), v );
    
    _range_choice->value( i );

    _range_mode = i;
}

/** set X, Y, W, and H to the bounding box of point /p/ in screen coords */
void
Panner::point_bbox ( const Point *p, int *X, int *Y, int *W, int *H ) const
{
    int tx, ty, tw, th;

    bbox( tx, ty, tw, th );

    float px, py;
    float s = 1.0f;
       
    if ( projection() == POLAR )
    {
        project_polar( p, &px, &py, &s );
    }
    else
    {
        project_ortho( p, &px, &py, &s );
    }

    const float htw = float(tw)*0.5f;
    const float hth = float(th)*0.5f;


    *W = *H = tw * s;
     
    if ( *W < 8 )
        *W = 8;
    if ( *H < 8 )
        *H = 8;

    *X = tx + (htw * px + htw) - *W/2;
    *Y = ty + (hth * py + hth) - *H/2;

}

Panner::Point *
Panner::event_point ( void )
{
    for ( int i = _points.size(); i--; )
    {
       
        int px, py, pw, ph;

        Point *p = &_points[ i ];

        if ( ! p->visible )
            continue;

        point_bbox( p, &px, &py, &pw, &ph );

//        printf( "%d, %d -- %d %d %d %d\n", Fl::event_x(), Fl::event_y(),  px, py, pw, ph );

        if ( Fl::event_inside( px, py, pw, ph ) )
            return p;
    }

    return NULL;
}


void
Panner::draw_the_box ( int tx, int ty, int tw, int th )
{
    draw_box();

    Fl_Image *i = 0;

    if ( _bg_image && ( _bg_image->h() != th || projection() != _bg_image_projection ) )
    {
        if ( _bg_image_scaled )
            delete _bg_image;
        else
            ((Fl_Shared_Image*)_bg_image)->release();

        _bg_image = 0;
    }

    if ( ! _bg_image )
    {
        if ( projection() == POLAR )
        {
            if ( th <= 92 )
                i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer-xt/panner-sphere-92x92.png" );
            else if ( th <= 502 )
                i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer-xt/panner-sphere-502x502.png" );
            else if ( th <= 802 )
                i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer-xt/panner-sphere-802x802.png" );
        }
        else
        {
            if ( th <= 92 )
                i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer-xt/panner-plane-92x92.png" );
            else if ( th <= 502 )
                i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer-xt/panner-plane-502x502.png" );
            else if ( th <= 802 )
                i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer-xt/panner-plane-802x802.png" );
        }

        if ( i && i->h() != th )
        {
            Fl_Image *scaled = i->copy( th, th );

            _bg_image = scaled;
            _bg_image_scaled = true;
        }
        else
        {
            _bg_image = i;
            _bg_image_scaled = false;
        }
    }
    
    _bg_image_projection = projection();

    if ( _bg_image )
        _bg_image->draw( tx, ty );
}

/** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */
void
Panner::project_polar ( const Point *p, float *X, float *Y, float *S ) const
{
    float xp = 0.0f;
    float yp = 0.0f;
    float zp = 8.0f;

    float x = 0 - p->y;
    float y = 0 - p->x;
    float z = 0 - p->z;
    
    x /= range();
    y /= range();
    z /= range();
                
    *X = ((x-xp) / (z + zp)) * (zp);
    *Y = ((y-yp) / (z + zp)) * (zp);
    *S = (0.025f / (z + zp)) * (zp);
}

/** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */
void
Panner::project_ortho ( const Point *p, float *X, float *Y, float *S ) const
{
    const float x = ( 0 - p->y ) / range();
    const float y = ( 0 - p->x ) / range();
//    const float z = p->z;
    
//    float zp = 4.0f;
    
    *X = x;
    *Y = y;
                
    *S = 0.025f;
}


void
Panner::set_ortho ( Point *p, float x, float y )
{
    y = 0 - y;

    y *= 2;
    x *= 2;
    
    p->x = (y) * range();
    p->y = (0 - x) * range();
}

void
Panner::set_polar ( Point *p, float x, float y )
{
    /* FIXME: not quite the inverse of the projection... */
    x = 0 - x;
    y = 0 - y;
    x *= 2;
    y *= 2;

    float r = powf( x,2 ) + powf(y,2 );
    float X = ( 2 * x ) / ( 1 + r );
    float Y = ( 2 * y ) / ( 1 + r );
    float Z = ( -1 + r ) / ( 1 + r );

    float S = p->radius() / range();

    X *= S;
    Y *= S;
    Z *= S;

    p->azimuth( -atan2f( X,Y ) * ( 180 / M_PI ) );

    if ( p->elevation() > 0.0f )
        p->elevation( -atan2f( Z, sqrtf( powf(X,2) + powf( Y, 2 ))) * ( 180 / M_PI ) );
    else
        p->elevation( atan2f( Z, sqrtf( powf(X,2) + powf( Y, 2 ))) * ( 180 / M_PI ) );
}



void
Panner::set_polar_radius ( Point *p, float x, float y )
{
    y = 0 - y;
    
    float r = sqrtf( powf( y, 2 ) + powf( x, 2 ) );

    if ( r > 1.0f )
        r = 1.0f;
    
    r *= range() * 2;

    if ( r > range() )
        r = range();

    p->radius( r );
}

void
Panner::draw ( void )
{
    int tw, th, tx, ty;

    bbox( tx, ty, tw, th );

    fl_push_clip( x(),y(),w(),h() );

    draw_the_box( tx, ty, tw, th );

//    draw_box();
    draw_label();

    /* if ( _bypassed ) */
    /* { */
    /*     draw_box(); */
    /*     fl_color( 0 ); */
    /*     fl_font( FL_HELVETICA, 12 ); */
    /*     fl_draw( "(bypass)", x(), y(), w(), h(), FL_ALIGN_CENTER ); */
    /*     goto done; */
    /* } */
   
    /* tx += b; */
    /* ty += b; */
    /* tw -= b * 2; */
    /* th -= b * 2; */

    fl_line_style( FL_SOLID, 1 );

    fl_color( FL_WHITE );

    for ( unsigned int i = 0; i < _points.size(); i++ )
    {
        Point *p = &_points[i];

        if ( ! p->visible )
            continue;

        Fl_Color c = fl_color_add_alpha( p->color, 150 );

        fl_color(c);

        int px, py, pw, ph;
        point_bbox( p, &px, &py, &pw, &ph );
      
        {
            float po = 5;

            fl_push_clip( px - ( po * 12 ), 
                          py - ( po * 12 ),
                          pw + ( po * 24 ), ph + (po * 24 ));

            fl_pie( px + 5, py + 5, pw - 10, ph - 10, 0, 360 );


            fl_pie( px, py, pw, ph, 0, 360 );

            fl_pop_clip();

            if ( projection() == POLAR )
            {
           
                fl_color( fl_color_average( fl_rgb_color( 127,127,127 ), p->color, 0.50 ) );
                fl_begin_loop();
                fl_circle( tx + tw/2, ty + th/2, tw/2.0f * ( ( p->radius() / range() )));
                fl_end_loop();
            }

        }
    
        const char *s = p->label;

        fl_color( fl_color_add_alpha( fl_rgb_color( 220,255,255 ), 127 ) );
        fl_font( FL_HELVETICA_BOLD_ITALIC, 10 );
        fl_draw( s, px + 20, py + 1, 50, ph - 1, FL_ALIGN_LEFT );

        if ( tw > 100 )
        {
            char pat[50];
            snprintf( pat, sizeof(pat), "%.1f°:%.1f° %.1fm", p->azimuth(), p->elevation(), p->radius() );
            
//        fl_color( fl_color_add_alpha( fl_rgb_color( 220,255,255 ), 127 ) );
            fl_font( FL_COURIER, 9 );

            fl_draw( pat, px + 20, py + 15, 50, ph - 1, FL_ALIGN_LEFT | FL_ALIGN_WRAP );

            /* fl_font( FL_HELVETICA_ITALIC, 9 ); */
            /* snprintf(pat, sizeof(pat), "range: %.1f meters", range() ); */
            /* fl_draw( pat, tx, ty, tw, th, FL_ALIGN_LEFT | FL_ALIGN_BOTTOM | FL_ALIGN_INSIDE ); */
                
            /* if ( _projection == POLAR ) */
            /* { */
            /*     fl_draw( "Polar perspective; azimuth, elevation and radius input. Right click controls radius.", tx, ty, tw, th, FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ); */
            /* } */
            /* else */
            /* { */
            /*     fl_draw( "Polar orthographic; angle and distance input.", tx, ty, tw, th, FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ); */
            /* } */
        }

    }
    
    if ( tw > 200 )
        draw_children();

    fl_line_style( FL_SOLID, 0 );

    fl_pop_clip();
}

/* return the current gain setting for the path in/out  */
Panner::Point *
Panner::point( int i )
{
    return &_points[ i ];
}

int
Panner::handle ( int m )
{
    int r = Fl_Group::handle( m );

    switch ( m )
    {
        case FL_ENTER:
        case FL_LEAVE:
            _projection_choice->value(_projection_mode);
            _range_choice->value(_range_mode);
            redraw();
            return 1;
        case FL_PUSH:
        {
            if ( Fl::event_button1() || Fl::event_button3() )
                drag = event_point();

            if ( Fl::event_button2() )
            {
                /* if ( _projection == POLAR ) */
                /*     _projection = ORTHO; */
                /* else */
                /*     _projection = POLAR; */
            }
            return 1;
        }
        case FL_RELEASE:
            if ( drag )
            {
                do_callback();
                drag = NULL;
                redraw();
                return 1;
            }
            else
                return 0;
        case FL_MOUSEWHEEL:
        {
/*             Point *p = event_point(); */

/*             if ( p ) */
/*                 drag = p; */

/*             if ( drag ) */
/*             { */
/* //                drag->elevation( drag->elevation() + Fl::event_dy()); */
/*                 drag->elevation( 0 - drag->elevation() ); */
/*                 do_callback(); */
/*                 redraw(); */
/*                 return 1; */
/*             } */

            return 1;
        }
        case FL_DRAG:
        {
            if ( ! drag )
                return 0;

            int tx, ty, tw, th;
            bbox( tx, ty, tw, th );

            float X = (float(Fl::event_x() - tx) / tw ) - 0.5f;
            float Y = (float(Fl::event_y() - ty) / th) - 0.5f;
            
            if ( Fl::event_button1() )
            {
                if ( POLAR == projection() )
                    set_polar( drag,X,Y );
                else
                {
                    if ( fabsf( X ) < 0.5f &&
                         fabsf( Y ) < 0.5f ) 
                        set_ortho( drag, X,Y );
                }
            }
            else
                set_polar_radius( drag,X,Y );

            if ( when() & FL_WHEN_CHANGED )
                do_callback();

            damage(FL_DAMAGE_EXPOSE);

            return 1;
        }

    }

    return r;

//    return 0;
}
