/* karteibutton.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2004-2019 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "karteibutton.h"
#include "awindow.h"
#include "guielement.h"
#include "kartei.h"
#include "drawablecont.hh"

const char *KarteiButton::type="KarteiButton";

KarteiButton::~KarteiButton()
{
}

KarteiButton::KarteiButton( AGUIX *taguix,
			    int tx,
			    int ty,
			    int width,
			    int tdata ) : CycleButton( taguix,
						       tx,
						       ty,
						       width,
						       tdata )
{
  int bw;

  optionChangeCallback = NULL;
  k2 = NULL;
  focusPos = 0;

  bw = 2;

  _h = taguix->getCharHeight() + 2 * bw;

  fg = _aguix->getFaces().getColor( "default-fg" );
  bg = _aguix->getFaces().getColor( "default-bg" );
}

KarteiButton::KarteiButton( AGUIX *taguix,
			    int tx,
			    int ty,
			    int width,
			    int height,
			    int tdata ) : CycleButton( taguix,
						       tx,
						       ty,
						       width,
						       height,
						       tdata )
{
  optionChangeCallback = NULL;
  k2 = NULL;
  focusPos = 0;

  fg = _aguix->getFaces().getColor( "default-fg" );
  bg = _aguix->getFaces().getColor( "default-bg" );
}

void KarteiButton::redraw()
{
  GC usegc;
  int ch;
  int basex, basey, midy, delx, i, dx, dx2, sx, sy, ex, ey;
  int act_x1 = -1, act_x2 = -1;

  if ( isCreated() == false ) return;
  if ( win == 0 ) return;

  _aguix->SetWindowBG( win, bg );
  _aguix->ClearWin( win );
  
  if ( font == NULL ) usegc = 0; else usegc = font->getGC();

  basey = _h - 1;
  midy = _h / 2 - 1;
  delx = _h / 4;

  if ( font == NULL ) {
    ch = _aguix->getCharHeight();
  } else {
    ch = font->getCharHeight();
  }

  DrawableCont dc( _aguix, win );
  AFontWidth lencalc( _aguix, font );

  m_width_per_option.clear();
  m_width_per_option.resize( getNrOfOptions(), 0 );

  if ( m_shrinker.get() != NULL ) {
      // calculate widths for each option
      int rem_width = 0;
      int rem_options = getNrOfOptions();

      rem_width = getWidth() - 2 * delx - rem_options * 4 * delx;

      std::vector<int> needed_diff;

      needed_diff.resize( rem_options, 0 );

      int additional_width = 0;
      int sum_needed_diff = 0;

      // first consider equal distribution
      // but store needed width for longer options
      // and additional width for shorter options
      for ( int i = 0; i < (int)options.size(); i++ ) {
          int use_width = rem_width / rem_options;
          int full_width = _aguix->getTextWidth( options[i].c_str(), font );
          if ( full_width <= use_width ) {
              m_width_per_option[i] = full_width;
              needed_diff[i] = 0;
              additional_width += use_width - full_width;
          } else {
              m_width_per_option[i] = use_width;
              needed_diff[i] = full_width - use_width;
              sum_needed_diff += needed_diff[i];
          }

          // only consider use_width and not actual value used
          rem_width -= use_width;
          rem_options--;
      }

      // now distribute additional_width to longer options
      if ( sum_needed_diff <= additional_width ) {
          // more space available than needed
          // just use max values
          for ( int i = 0; i < (int)options.size(); i++ ) {
              if ( needed_diff[i] > 0 ) {
                  m_width_per_option[i] += needed_diff[i];
              }
          }
      } else{
          int assigned_width = 0;
          
          // based on the fraction of needed space
          // assign additional width
          for ( int i = 0; i < (int)options.size(); i++ ) {
              if ( needed_diff[i] > 0 ) {
                  int tw = needed_diff[i] * additional_width;
                  tw /= sum_needed_diff;
                  m_width_per_option[i] += tw;
                  needed_diff[i] -= tw;
                  assigned_width += tw;
              }
          }
          
          // if there are some unassigned pixels left (due to rounding
          // errors) just run over all entries and add 1 if needed
          for ( int i = 0; i < (int)options.size(); i++ ) {
              if ( additional_width - assigned_width <= 0 ) break;
              if ( needed_diff[i] > 0 ) {
                  m_width_per_option[i]++;
                  assigned_width++;
              }
          }
      }
  } else {
      for ( int i = 0; i < (int)options.size(); i++ ) {
          m_width_per_option[i] = _aguix->getTextWidth( options[i].c_str(), font );
      }
  }

  // make two runs
  // first to draw the bg for inactive options in dark grey
  // second to draw lines and text
  for ( int run = 0; run < 2; run++ ) {
    basex = 0;
    for ( i = 0; i < (int)options.size(); i++ ) {
      sx = basex;
      dx = 2 * delx;
      
      if ( ( i != 0 ) && ( i != act_opt ) ) {
	sx += delx + 1;
	dx = delx - 1;
      }
      
      if ( ( i == 0 ) ||
	   ( i == act_opt ) ) {
	sy = basey;
      } else {
	sy = midy;
      }
      
      if ( run == 0 ) {
	// left triangle
	if ( i != act_opt ) {
        // dark grey
        _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-inactive-bg" ) );

	  // top part of the triangle is always visible
	  _aguix->DrawTriangleFilled( win, usegc, sx , sy, sx + dx, sy, sx + dx, 0 );
	  if ( i != 0 ) {
	    // for any option but first the bottom part is mostly hidden by
	    // previous option so just draw visible part (which is
	    // actually a triangle itself)
	    _aguix->DrawTriangleFilled( win, usegc, sx , sy, sx + dx, sy, sx + dx, basey );
	  }
	} else if ( i == act_opt ) {
            _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-active-bg" ) );
	  
	  // for active option overdraw right part of previous option
	  _aguix->DrawTriangleFilled( win, usegc, basex , basey, sx + dx, basey, sx + dx, 0 );
	}
      } else {
          _aguix->setFG( usegc, _aguix->getFaceCol_3d_bright() );
          _aguix->DrawLine( win, usegc,
                            sx, sy,
                            sx + dx, 0 );
      }
      
      const char *tstr = options[i].c_str();
      
      dx2 = m_width_per_option[i];
      dx2 += 2 * delx;
      
      if ( run == 0 ) {
	if ( i != act_opt ) {
            // dark grey
            // this is the bg of an inactive option
            _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-inactive-bg" ) );
	} else {
            _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-active-bg" ) );
        }
        _aguix->FillRectangle( win, usegc, sx + dx, 0, dx2, basey + 1 );
      } else {
	_aguix->DrawLine( win, usegc,
			  sx + dx, 0,
			  sx + dx + dx2, 0 );
      }

      if ( ( i + 1 ) == act_opt ) {
	ex = sx + dx + dx2 + delx;
	ey = midy;
      } else {
	ex = sx + dx + dx2 + 2 * delx;
	ey = basey;
      }

      if ( run == 0 ) {
	if ( i != act_opt ) {
          // dark grey
          _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-inactive-bg" ) );
        } else {
          _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-active-bg" ) );
        }

        // finally the right triangle
        _aguix->DrawTriangleFilled( win, usegc, sx + dx + dx2 , 0, sx + dx + dx2, basey, sx + dx + dx2 + 2 * delx, basey );
      } else {
          _aguix->setFG( usegc, _aguix->getFaceCol_3d_dark() );
          _aguix->DrawLine( win, usegc,
                            sx + dx + dx2, 0,
                            ex, ey );
      }
      
      if ( i == act_opt ) {
	act_x1 = basex;
	act_x2 = ex;
      }
      
      if ( run != 0 ) {
          AGUIXColor col = _aguix->getFaces().getAGUIXColor( "tab-inactive-fg" );
          if ( i == act_opt ) {
              col = _aguix->getFaces().getAGUIXColor( "tab-active-fg" );
          }

          if ( m_shrinker.get() != NULL ) {
              std::string current_text = m_shrinker->shrink( tstr, m_width_per_option[i], lencalc );
              _aguix->DrawText( dc, font, current_text.c_str(), sx + dx + delx, 1,
                                col );
          } else {
              _aguix->DrawText( dc, font, tstr, sx + dx + delx, 1,
                                col );
          }
      }

      if ( run != 0 ) {
	if ( ( getAcceptFocus() == true ) &&
	     ( getHasFocus() == true ) &&
	     ( focusPos == i ) ) {
	  _aguix->setDottedFG( _aguix->getFaces().getAGUIXColor( "tab-active-fg" ) );
	  _aguix->DrawDottedRectangle( win, sx + dx + delx - 2, 1, dx2 - 2 * delx + 4, 1 + ch + 1 );
	}
      }
      
      basex = sx + dx + dx2;
    }
  }
  
  if ( ( act_x1 >= 0 ) && ( act_x2 > act_x1 ) ) {
    _aguix->setFG( usegc, _aguix->getFaceCol_3d_bright() );
    _aguix->DrawLine( win, usegc, 0, basey, act_x1, basey );
    _aguix->DrawLine( win, usegc, act_x2 + 1, basey, _w, basey );
  }
  
  _aguix->Flush();
}

void KarteiButton::flush()
{
}

bool KarteiButton::handleMessage(XEvent *E,Message *msg)
{
  bool returnvalue;
  AGMessage *agmsg;
  
  if ( isCreated() == false ) return false;

  returnvalue = false;

  if ( ( msg->type == ButtonPress ) ||
       ( msg->type == ButtonRelease ) ) {
    if ( msg->window == win ) {
      int mx, opt;

      takeFocus();

      mx = msg->mousex;
      opt = findClickedOption( mx );
      if(msg->type==ButtonPress) {
	if ( opt >= 0 ) {
	  focusPos = opt;
	  //redraw();
	}
	setState( 1 );
	instate = 1;
	returnvalue = true;
      } else {
	if ( ( state != 0 ) &&
	     ( instate != 0 ) &&
	     ( opt >= 0 ) ) {
	  if ( options.size() > 0 ) {
	    if ( msg->button == Button1 ) {
	      act_opt = opt;
	      if ( act_opt >= (int)options.size() ) act_opt = 0;
	    }
	  }
	  
	  agmsg = AGUIX_allocAGMessage();
	  agmsg->type=AG_KARTEIBUTTONCLICKED;
	  agmsg->karteibutton.karteibutton=this;
	  agmsg->karteibutton.option = opt;
	  agmsg->karteibutton.mousebutton = msg->button;
          msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );

	  if ( ( optionChangeCallback != NULL ) && ( act_opt >= 0 ) ) {
	    optionChangeCallback( k2, (unsigned int)act_opt );
	  }
	}
	if ( instate != 0 ) {
	  setState( 0 );
	  instate = 0;
	  returnvalue = true;
	}
      }
    }
  } else if ( msg->type == EnterNotify ) {
    // alles hier und alles mit instate wird benutzt, damit Button sich anpaßt, wenn
    // Mauszeiger im Button oder außerhalb des Buttons ist
    if ( msg->window == win ) {
      if ( instate != 0 ) {
	if ( state != instate ) {
	  setState( instate );
	}
      }
    }
  } else if ( msg->type == LeaveNotify ) {
    // alles hier und alles mit instate wird benutzt, damit Button sich anpaßt, wenn
    // Mauszeiger im Button oder außerhalb des Buttons ist
    if ( msg->window == win ) {
      if ( instate != 0 ) {
	setState( 0 );
      }
    }
  } else if ( msg->type == Expose ) {
    if ( msg->window == win ) {
      redraw();
    }
  } else if ( msg->type == KeyPress ) {
    if ( ( getAcceptFocus() == true ) && ( getHasFocus() == true ) ) {
      if ( options.size() > 0 ) {
	if ( isVisible() == true ) {
	  if ( _parent->isTopParent( msg->window ) == true ) {
	    switch ( msg->key ) {
	    case XK_space:
	      if ( act_opt != focusPos ) {
		act_opt = focusPos;
		if ( act_opt >= (int)options.size() ) act_opt = 0;
		
		agmsg = AGUIX_allocAGMessage();
		agmsg->type=AG_KARTEIBUTTONCLICKED;
		agmsg->karteibutton.karteibutton=this;
		agmsg->karteibutton.option=act_opt;
                agmsg->karteibutton.mousebutton = 0;
                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );

                if ( ( optionChangeCallback != NULL ) && ( act_opt >= 0 ) ) {
                  optionChangeCallback( k2, (unsigned int)act_opt );
                }
		if ( instate != 0 ) {
		  setState( 0 );
		  instate = 0;
		  returnvalue = true;
		} else redraw();  // setState will make this so just in the else-case
	      }
	      break;
	    case XK_Left:
	      if ( focusPos > 0 ) {
		focusPos--;
		redraw();
	      }
	      break;
	    case XK_Right:
                if ( ( focusPos + 1 ) < (int)options.size() ) {
		focusPos++;
		redraw();
	      }
	      break;
	    }
	  }
	}
      }
    }
  }
  if ( returnvalue == true ) {
    // jetzt noch die Message mit den Werten füllen
    msg->gadget = this;
    msg->gadgettype = BUTTON_GADGET;
  }
//  return returnvalue;
  return false;  // we return false because an other element can take use of
                 // this message (f.e. StringGagdet for deactivating)
}

const char *KarteiButton::getType() const
{
  return type;
}

bool KarteiButton::isType(const char *qtype) const
{
  if(strcmp(type,qtype)==0) return true;
  return false;
}

int KarteiButton::getMaxSize() const
{
  const char *tstr;
  int delx, dx2, tw;

  delx = _h / 4;
  tw = 2 * delx;
  
  for ( unsigned int i = 0; i < options.size(); i++ ) {
    tstr = options[i].c_str();
    dx2 = _aguix->getTextWidth( tstr, font );
    dx2 += 2 * delx;
    
    tw += dx2 + 2 * delx;
  }
  return tw + 1;
}

void KarteiButton::setOptionChangeCallback( void (*optionChangeCallback_arg)( class Kartei *k1,
									      unsigned int option ),
				            Kartei *k2_arg )
{
  optionChangeCallback = optionChangeCallback_arg;
  k2 = k2_arg;
}

int KarteiButton::findClickedOption( int mx )
{
  int delx, i, dx2, tw, basex;
  int opt = -1;

  delx = _h / 4;
  basex = 0;

  if ( (int)m_width_per_option.size() < getNrOfOptions() ) return opt;

  for ( i = 0; i < (int)options.size(); i++ ) {
    dx2 = m_width_per_option[i];
    dx2 += 2 * delx;
    
    tw = dx2 + 2 * delx;
    if ( ( i == 0 ) || ( i == (int)options.size() - 1 ) ) {
      tw += delx;
    }

    if ( ( mx >= basex ) &&
	 ( mx < ( basex + tw ) ) ) {
      opt = i;
      break;
    }

    basex += tw;
  }
  return opt;
}

void KarteiButton::setOption( int nv )
{
  int old_opt = act_opt;
  
  if ( ( nv >= 0 ) && ( options.size() > 0 ) ) {
    act_opt = nv;
    if ( act_opt >= (int)options.size() ) act_opt = 0;

    if ( old_opt != act_opt ) {
      if ( ( optionChangeCallback != NULL ) && ( act_opt >= 0 ) ) {
	optionChangeCallback( k2, (unsigned int)act_opt );
      }
      redraw();
    }
  }
}

void KarteiButton::setTextShrinker( RefCount<TextShrinker> shrinker )
{
    m_shrinker = shrinker;
}

int KarteiButton::setFont( const char *fontname )
{
    int bw = 2;

    int res = CycleButton::setFont( fontname );

    if ( font == NULL ) {
        resize( getWidth(), _aguix->getCharHeight() + 2 * bw );
    } else {
        resize( getWidth(), font->getCharHeight() + 2 * bw );
    }

    return res;
}
