#include <math.h>
#include <sys/time.h>

#include "Bar.h"

/* Posicionamiento de la ventana */
#define MARGEN 4

using namespace std;
extern Pixmap rootmap;

/* Bar Constructor & Destructor *//*{{{*/
Bar::Bar(XWin *win, string barImg, int iSize, int iDist, float zFactor,
	float jFactor, int bOrient, int bPosition, int nAnim) :

    /* Initialization */
    buffer(0), cleaning_buffer(0), barback(0), bar(0), window(win), icon_dist(iDist),
    icon_size(iSize), icon_anim(nAnim), jump_factor(jFactor), zoom_factor(zFactor),
    orientation(bOrient), position(bPosition), zoomed_icon(-1), focused(1){

    /* Load Bar back ground */
    if( !(bar = LOAD_IMAGE(barImg.c_str())) )
	throw (barImg + " -> Image not found. Maybe using a relative path?").c_str();

    /* Get Bar Dimensions */
    USE_IMAGE(bar);

    owidth = IMAGE_WIDTH(); oheight = IMAGE_HEIGHT();
    if( orientation == 1 ) IMAGE_TRANSPOSE();

    /* Enable Bar's alpha channel */
    IMAGE_ENABLE_ALPHA(1);

    /* set bar initial values */
    scale();
}

Bar::~Bar(){
    FREE_IMAGE(buffer);
    FREE_IMAGE(barback);
    FREE_IMAGE(bar);
    FREE_IMAGE(cleaning_buffer);

    while( !icons.empty() ){
	delete icons.back();
	icons.pop_back();
    }
}
/*}}}*/

/* Add Icon *//*{{{*/
void Bar::addIcon(string path, string comm){

    icons.push_back( new Icon(path, comm,
	(int)icon_offset + icon_size/2 + icons.size() * icon_unit, // x coord
	y + (int)(0.125 * icon_size)) ); // y coord

    /* not efficient to call it here but ... user doesn't have to call it */
    scale();
}
/*}}}*/

/* Scale bar *//*{{{*/
void Bar::scale(bool updateBG){
    float up_growth, dn_growth, b_scl_c;

    /* unit icon */
    icon_unit = (icon_size + icon_dist);
    /* icons on side */
    icon_ansd = (icon_anim-1)/2;
    /* icon offset constant */
    icon_offset =  icon_ansd * ( (zoom_factor>1)?(zoom_factor-1) : 0.5 ) * icon_unit;
    /* Icon scaling constant */
    b_dd = (zoom_factor>1)? (zoom_factor-1)*icon_unit : zoom_factor*icon_unit;

    // x0 = (a + (icon_anim + 1)/2) * icon_unit;
    // x1 = (a - (icon_anim - 1)/2) * icon_unit;
    // xm = (a + 0.5) * icon_unit;

#ifdef COS_ZOOM
    b_scl_b = 3.14159 / icon_unit / icon_anim;
#else
    /* Estos calculos de escala son en base a la funcion sigmoidea f = 1/(1+exp(a+bx^2))*/
    b_scl_c = 0.01; // mientras mas cercano a 0 mas se desplaza chico
    b_scl_d = 0.20; // mientras mas cercano a 1 mas se desplaza grande -> si quiero pico lo achico
    b_scl_a = logf((1.0-b_scl_d)/b_scl_d);
    //b_scl_b = (logf((1.0-b_scl_c)/b_scl_c) - b_scl_a)/(x0-xm)^2;
    b_scl_b = 4*(logf((1.0-b_scl_c)/b_scl_c) - b_scl_a)/
	(icon_anim*icon_anim*icon_unit*icon_unit);
#endif
#ifdef LINEAR_TRASL
    //float m = (icon_anim-1)*b_dd / (x1 - x0);
    b_pos_m = -icon_ansd * 2 * b_dd / icon_unit / icon_anim;
#else
    b_pos_m = - 3.14159 / icon_unit / icon_anim;
#endif

    /* bar dimensions */
    width = icon_unit * (icons.size() + 1) - 2 * icon_dist;
    height=(int)(icon_size * 1.25);

    /* Space needed for growing */
    up_growth = jump_factor*icon_size*(zoom_factor-1);
    dn_growth = icon_size*(zoom_factor-1) - up_growth;

    /* substract growth superposed on bar */
    up_growth-= icon_size*0.125;
    dn_growth-= icon_size*0.125;

    /* Set new window width */
    window->w = (int)(width + 2*icon_offset);
    window->h = height + 2*MARGEN +
	(int)((up_growth>0.0? up_growth : 0.0))+
	(int)((dn_growth>0.0? dn_growth : 0.0));

    /* Initial Bar position */
    y = MARGEN + (int)(up_growth>0.0? up_growth : 0.0);
    x = (int)icon_offset;

    if(updateBG)
	acquireBack();
}
/*}}}*/

/* Acquire background *//*{{{*/
void Bar::acquireBack(){
    int t_w, t_h;

    t_w = (orientation == 0)? window->w : window->h;
    t_h = (orientation == 0)? window->h : window->w;

    /* Update win x && y for getting root img *//*{{{*/
    switch(position){
	case 1:	case 2:	case 5:
	    window->x = (window->screenWidth() - t_w)/2;
	    break;

	case 3:	case 7:	case 9:
	    window->x = 0;
	    break;

	case 4:	case 6:	case 8:
	    window->x = window->screenWidth() - t_w;
	    break;
    }

    switch(position){
	case 3:	case 4:	case 5:
	    window->y = (window->screenHeight() - t_h)/2;
	    break;

	case 1:	case 6:	case 7:
	    window->y = window->screenHeight() - t_h;
	    break;

	case 2:	case 8:	case 9:
	    window->y = 0;
	    break;
    }
    /*}}}*/

    /* Move & resize win for fitting new icons */
    window->moveNresize(window->x, window->y, t_w, t_h);

    if(barback) FREE_IMAGE(barback);
    if(buffer) FREE_IMAGE(buffer);

    buffer = CREATE_IMAGE(t_w, t_h);

    /* Get background Image */
    if (rootmap==None)
	USE_DRAWABLE(DefaultRootWindow(window->display));
    else
	USE_DRAWABLE(rootmap);
    barback = IMAGE_FROM_DRAWABLE(window->x, window->y, t_w, t_h);
    USE_DRAWABLE(window->window);
}
/*}}}*/

/* Icons Scaling *//*{{{*/
void Bar::transform(int mousex){
#ifdef LINEAR_TRASL
    int x0 = (icon_ansd+1) * icon_unit; // last movable pos
#endif

    int xx = mousex + (int)(icon_offset + icon_size/2);
    int	rx = mousex - icon_unit/2;
    int i_num = mousex / icon_unit;
    int t_x, t_y, a;
    Icon *cur_ic=0;

    zoomed_icon = -1 ;

#ifdef LINEAR_TRASL
    for(a=0; a<(int)icons.size(); a++, rx -= icon_unit, x0 += icon_unit ){
#else
    for(a=0; a<(int)icons.size(); a++, rx -= icon_unit ){
#endif

	cur_ic = icons[a];

	/* Check if over the icon */
	if(zoomed_icon == -1)
	    if(xx >= cur_ic->x && xx < cur_ic->x + cur_ic->size)
		zoomed_icon = a;

	if( ((a<i_num)?i_num-a:a-i_num) > icon_ansd ){
	    t_x = (int)(cur_ic->ox + ((a<i_num)?-1:1) * icon_ansd * b_dd );
	    t_y = cur_ic->oy;

	    if(cur_ic->size != icon_size || cur_ic->x != t_x || cur_ic->y != t_y){

		cur_ic->cx = cur_ic->x;
		cur_ic->csize = cur_ic->size;

		cur_ic->size = icon_size;
		cur_ic->x = t_x;
		cur_ic->y = t_y;
		cur_ic->need_update = 1;
	    }

	}else{

	    cur_ic->cx = cur_ic->x;
	    cur_ic->csize = cur_ic->size;

#ifdef COS_ZOOM
	    cur_ic->size = (int)( icon_size *
		    ( 1.0 + (zoom_factor-1.0)*cosf(b_scl_b * rx)));
#else
	    cur_ic->size = (int)( icon_size *
		( 1.0 + (zoom_factor-1.0)/(1.0 + expf(b_scl_a + b_scl_b*rx*rx))/b_scl_d));
#endif

#ifdef LINEAR_TRASL
	    cur_ic->x = cur_ic->ox - (int)(icon_ansd * b_dd - b_pos_m * (mousex - x0));
#else
	    cur_ic->x = cur_ic->ox + (int)(icon_ansd * b_dd * sinf(b_pos_m * rx));
#endif


	    cur_ic->y = cur_ic->oy - (int)(jump_factor*(cur_ic->size-icon_size));
	    cur_ic->need_update = 1;
	}
    }
}
/*}}}*/

/* Clean the Bar *//*{{{*/
void Bar::cleanBack(){
    Icon *cur_ic, *next_ic;

    USE_IMAGE(buffer);
    SET_BLEND(0);

    for(size_t a=0; a<icons.size(); a++){
	cur_ic = icons[a];

	if(cur_ic->need_update == 0) continue;

	/* force next icons redraw if stepped on them */
	for(size_t b=a+1; b<icons.size(); b++){
	    next_ic = icons[b];

	    if(next_ic->need_update == 1)
		continue;

	    if(cur_ic->cx + cur_ic->csize >= next_ic->x){
		next_ic->cx = next_ic->x;
		next_ic->csize = next_ic->csize;
		next_ic->need_update = 1;
	    }else
		break;
	}

	/* Copy Root Background */
	if(orientation == 0)
	    BLEND_IMAGE(cleaning_buffer, cur_ic->cx, 0, cur_ic->csize, window->h,
		    cur_ic->cx, 0, cur_ic->csize, window->h);
	else
	    BLEND_IMAGE(cleaning_buffer, 0, cur_ic->cx, window->h, cur_ic->csize,
		    0, cur_ic->cx, window->h, cur_ic->csize);
    }
}

void Bar::drawBack(){

    if(cleaning_buffer) FREE_IMAGE(cleaning_buffer);

    USE_IMAGE(buffer);

    /* Copy Root Background */
    SET_BLEND(0);

    if(orientation == 0){
	BLEND_IMAGE(barback, 0, 0, window->w, window->h,
		0, 0, window->w, window->h);

	/* Blend the bar */
	SET_BLEND(1);
	BLEND_IMAGE(bar, 0, 0, owidth, oheight, x, y, width, height);

    }else{
	BLEND_IMAGE(barback, 0, 0, window->h, window->w,
		0, 0, window->h, window->w);

	/* Blend the bar */
	SET_BLEND(1);
	BLEND_IMAGE(bar, 0, 0, oheight, owidth, y, x, height, width);
    }

    cleaning_buffer = CLONE_IMAGE();
}
/*}}}*/

/* Render the Bar *//*{{{*/
void Bar::render(){
    Icon *cur_ic=0;

    /* Set work area */
    USE_IMAGE(buffer);
    SET_BLEND(1);

    /* Blend the zoomed icon last */
    if(zoomed_icon != -1){
	cur_ic = icons.back();
	icons.back() = icons[zoomed_icon];
	icons[zoomed_icon] = cur_ic;
    }

    /* Blend Icons */
    for(size_t a=0; a<icons.size(); a++){
	cur_ic = icons[a];

	/* If Icon needs update => blend it */
	if(cur_ic->need_update == 1){

	    cur_ic->need_update = 0;

	    if(orientation == 0)
		BLEND_IMAGE(cur_ic->icon, 0, 0, cur_ic->osize, cur_ic->osize,
		    cur_ic->x, cur_ic->y, cur_ic->size, cur_ic->size);
	    else
		BLEND_IMAGE(cur_ic->icon, 0, 0, cur_ic->osize, cur_ic->osize,
		    cur_ic->y, cur_ic->x, cur_ic->size, cur_ic->size);
	}
    }

    if(zoomed_icon != -1){
	cur_ic = icons.back();
	icons.back() = icons[zoomed_icon];
	icons[zoomed_icon] = cur_ic;
    }

    /* Show the buffer */
    SET_BLEND(0);
    RENDER_TO_DRAWABLE(0, 0);

}
/*}}}*/

/* Refresh *//*{{{*/
void Bar::refresh(int mouse_x){
  /* Relative coords */
  mouse_x -= (int)(icon_offset + icon_size/2.0);

  /* on the bar */
  if(mouse_x > 0 && mouse_x < (int)icons.size() * icon_unit){

    transform(mouse_x);

    if(!focused) {
      y -= icon_size;
      focus();
    }

    cleanBack();

    /* out of the bar */
  }else if(focused) {
    y += icon_size;
    unfocus();
  }

  render();
}
/*}}}*/

/* In & out of the bar *//*{{{*/
inline void Bar::unfocus(){
    focused = 0;
    x = (int)icon_offset;
    width = (int)(window->w - 2*icon_offset);
    zoomed_icon = -1;

    drawBack();
#ifndef NO_EXPAND
    expand(true);
#endif
}

inline void Bar::focus(){
    focused = 1;
    x=0;
    width = window->w;

    drawBack();
#ifndef NO_EXPAND
    expand(false);
#endif
}

#ifndef NO_EXPAND
void Bar::expand(bool inverse){
    Icon *cur_ic=0;
    /* animation time in ms */
    int t=0, anim_time = 80;
    struct timeval tv0, tv;

    if(inverse) {
	for(size_t i = 0; i<icons.size(); i++){
	    cur_ic = icons[i];
	    cur_ic->oy += icon_size;
	    cur_ic->vx = -(cur_ic->x - cur_ic->ox) / (float)anim_time;
	    cur_ic->vy = -(cur_ic->y - cur_ic->oy) / (float)anim_time;
	    cur_ic->vs = -(cur_ic->size - icon_size) / (float)anim_time;

	    cur_ic->bx = cur_ic->x;
	    cur_ic->by = cur_ic->y;
	    cur_ic->bs = cur_ic->size;
	}
    }
    else {
	for(size_t i = 0; i<icons.size(); i++){
	    cur_ic = icons[i];
	    cur_ic->y -= icon_size;
	    cur_ic->vx = (cur_ic->x - cur_ic->ox) / (float)anim_time;
	    cur_ic->vy = (cur_ic->y - cur_ic->oy) / (float)anim_time;
	    cur_ic->vs = (cur_ic->size - icon_size) / (float)anim_time;

	    cur_ic->bx = cur_ic->ox;
	    cur_ic->by = cur_ic->oy;
	    cur_ic->bs = icon_size;
	    cur_ic->oy -= icon_size;
	}
    }

    gettimeofday(&tv0, NULL);

    while(t < anim_time){
	gettimeofday(&tv, NULL);

	t = (int)((tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec)/1000);
	if(t > anim_time) t = anim_time;

	for(size_t j = 0; j< icons.size(); j++){
	    cur_ic = icons[j];
	    cur_ic->cx = cur_ic->x;
	    cur_ic->csize = cur_ic->size;

	    cur_ic->x = cur_ic->bx + (int)(t * cur_ic->vx);
	    cur_ic->y = cur_ic->by + (int)(t * cur_ic->vy);
	    cur_ic->size = cur_ic->bs + (int)(t * cur_ic->vs);

	    cur_ic->need_update = 1;
	}
	cleanBack();
	render();

    }
}
#endif
/*}}}*/

/* Icon press / release events *//*{{{*/
inline void Bar::iconPress(int i_num, int offs){
    Icon *cur_ic;

    if(i_num<0 || i_num>=(int)icons.size()) return;

    cur_ic = icons[i_num];

    cur_ic->cx = cur_ic->x;
    cur_ic->csize = cur_ic->size;

    cur_ic->x += offs;
    cur_ic->y += offs;

    cur_ic->need_update = 1;

    cleanBack();
    render();
}

void Bar::iconUp(int i_num){
    iconPress(i_num, -2);
}

void Bar::iconDown(int i_num){
    iconPress(i_num, 2);
}
/*}}}*/

/* Position the Bar *//*{{{*/
void Bar::setPosition(string pos){

    if( !pos.compare("bottom") ) position = 1;
    else if( !pos.compare("top") ) position = 2;
    else if( !pos.compare("left") ) position = 3;
    else if( !pos.compare("right") ) position = 4;
    else if( !pos.compare("center") ) position = 5;
    else if( !pos.compare("bot-right") ) position = 6;
    else if( !pos.compare("bot-left") ) position = 7;
    else if( !pos.compare("top-right") ) position = 8;
    else if( !pos.compare("top-left") ) position = 9;
    else throw (pos + " -> Invalid bar position.").c_str();

    acquireBack();
}
/*}}}*/

/* Icon info *//*{{{*/
string Bar::iconCommand(int i_num){

    if(i_num<0 || i_num>=(int)icons.size()) return NULL;

    return icons[i_num]->command;
}

int Bar::iconIndex(int mousex){
    Icon *cur_ic;

    for(int a=0; a<(int)icons.size(); a++){

	cur_ic = icons[a];

	/* Check if over the icon */
	if(mousex >= cur_ic->x && mousex < cur_ic->x + cur_ic->size)
	    return a;
    }

    return -1;
}
/*}}}*/

#if 0
/* Zoom beans *//*{{{*/
void Bar::setZoom(float zoomf){
    b_zoom_f = zoomf;
    scale(false);
}

float Bar::getZoom(){
    return b_zoom_f;
}
/*}}}*/
#endif

