/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/


#include "gui/text_input.hh"

#include "time/timedev.hh"
#include "device/event.hh"
#include "window/win_evt.hh"
#include "device/keys.hh"
#include "device/key_man.hh"

class cursor_blink_class : public i4_object_message_event_class
{
  public :
  cursor_blink_class(void *object) : i4_object_message_event_class(object) {}
  virtual i4_event  *copy() { return new cursor_blink_class(object); }  
} ;

sw32 i4_text_input_class::get_number()
{
  i4_str::iterator is=get_edit_string()->begin();
  return is.read_number();
}

i4_text_input_class::i4_text_input_class(i4_graphical_style_class *style,
                                         const i4_const_str &default_string,
                                         w32 width,       // width in pixels of window
                                         w32 _max_width,
                                         i4_event_handler_class *change_notify,
                                         i4_font_class *_font)
  : style(style),
    i4_window_class(0,0),
    change_notify(change_notify)

{
  if (!_font)
    font=style->font_hint->normal_font;
  else
    font=_font;

  max_width=_max_width;
  sent_change=i4_F;
  changed=i4_F;
  selected=i4_F;
  cursor_on=i4_F;
  selecting=i4_F;
  need_cancel_blink=i4_F;
    
  w32 l,t,r,b;
  style->get_in_deco_size(l,t,r,b);
  private_resize(width+l+r, font->height(default_string)+1+t+b);

  if (max_widthdraw_in_deco(local_image, 0,0, width()-1, height()-1, selected, context);
}

void i4_text_input_class::draw(i4_draw_context_class &context)
{        
  sw32 cx1,cy1,cy2;
  w32 char_on=0;
  w32 x;
  i4_color fg;
  i4_const_str::iterator c=st->begin();
  w32 l,t,r,b;

  local_image->add_dirty(0,0, width()-1, height()-1, context);

  style->get_in_deco_size(l,t,r,b);
  cx1=l;
  cy1=t;
  cy2=height()-1-b;


  for (x=0; xdeco_neutral_fill(local_image, 0,0, width()-1, height()-1, context);
  //  local_image->clear(style->color_hint->text_background,context);
  w32 proper_hi_x1=hi_x1>hi_x2 ? hi_x2 : hi_x1;
  w32 proper_hi_x2=hi_x1>hi_x2 ? hi_x1 : hi_x2;
      

  for (x=0; xend(); )
  {

    if (char_on>=proper_hi_x1 && char_oncolor_hint->selected_text_foreground;
      local_image->bar(cx1+x,               cy1+1,
                       cx1+x+font->width(c.get()), cy2,
                       style->color_hint->selected_text_background,
                       context);
    } else
      fg=style->color_hint->text_foreground;

    font->set_color(fg);
    if (c!=st->end())
    {
      font->put_character(local_image,cx1+x,cy1+1,c.get(),context);
      if (cursor_on && cursor_x==char_on)
        local_image->bar(cx1+x,cy1+1,
                         cx1+x,cy2-2,style->color_hint->white,context);

      x+=font->width(c.get());
      ++c;
    }
    else if (cursor_on && cursor_x==char_on)
        local_image->bar(cx1+x,cy1+1, cx1+x,cy2-2,style->color_hint->white,context);



    char_on++;
  }

  if (cursor_on && cursor_x==char_on)
    local_image->bar(cx1+x,cy1+1,
                     cx1+x,cy2-2,style->color_hint->white,context);



  draw_deco(context);

}


// this will find the character the mouse is on, this should work for non-constantly spaced
// character strings as well
w32 i4_text_input_class::mouse_to_cursor()
{
  i4_const_str::iterator c=st->begin();

  w32 char_on=0;
  w32 x,fw;
  if (last_x<0)
    last_x=0;

  for (x=0; xend(); x++)
  {
    ++c;
    char_on++;
  }
    
  for (x=0; xend(); )
  {
    fw=font->width(c.get());
    if (last_x<=x+fw/2+1)
      return char_on;

    x+=fw;
    char_on++;
    ++c;
  }
  return char_on;
}

void i4_text_input_class::request_blink()
{
  if (!need_cancel_blink)
  {
    blink_timer=i4_time_dev.request_event(this,
                                          new cursor_blink_class(this),
                                          500);
    need_cancel_blink=i4_T;
  }
}

void i4_text_input_class::stop_blink()
{
  if (need_cancel_blink)
  {
    i4_time_dev.cancel_event(blink_timer);
    need_cancel_blink=i4_F;
  }
}

void i4_text_input_class::sustain_cursor()
{
  cursor_on=i4_T;
  request_redraw();
}

void i4_text_input_class::del_selected()
{
        
  if (hi_x1!=hi_x2)
  {
    i4_coord swap;
    if (hi_x1>hi_x2)
    {
      swap=hi_x1;
      hi_x1=hi_x2;
      hi_x2=swap;
    }

    cursor_x=hi_x1;

    i4_str::iterator start=st->begin(),end=st->begin();
    while (hi_x1)
    {
      ++start;
      hi_x2--;
      hi_x1--;
    }
    end=start;
    while (hi_x2)
    {
      ++end;
      hi_x2--;
    }
    st->remove(start,end);
  }
  request_redraw();
}

void i4_text_input_class::move_cursor_right()
//{{{
{
  if (cursor_xlength())
  {
    cursor_x++;

    i4_const_str::iterator c=st->begin();
    w32 cur_left,x;

    for (x=0; xend(); x++)
      ++c;
 

    for (cur_left=cursor_x-left_x; cur_left && c!=st->end(); cur_left--)
    {
      x+=font->width(c.get());
      ++c;      
    }
    if (x>=width())
    {
      left_x+=4;
    }
    sustain_cursor();
  }
}
//}}}


void i4_text_input_class::move_cursor_left()
{
  if (cursor_x)
  {
    cursor_x--;
    if (cursor_x4)
        left_x-=4;
      else left_x=0;
    }
    sustain_cursor();
  }
}


void i4_text_input_class::move_cursor_end()
//{{{
{
  w32 left_pos, pos;
  i4_const_str::iterator c;

  cursor_x = st->length();
  left_x = 0;
  pos = 0;
  for (c=st->begin(); c!=st->end(); ++c)
    pos += font->width(c.get());
  left_pos = pos - width();

  pos = 0;
  for (c=st->begin(); c!=st->end() && poswidth(c.get());
    left_x++;
  }

  sustain_cursor();
}
//}}}


void i4_text_input_class::become_active()
{
  i4_window_request_key_grab_class kgrab(this);
  send_to_parent(&kgrab);
      
  selected=i4_T;
  request_redraw();
  if (!cursor_on)      // make sure cursor is not draw when we are not in focus
    cursor_on=i4_T;
  request_blink();
}

void i4_text_input_class::become_unactive()
{
  selected=i4_F;
  request_redraw();

  if (cursor_on)      // make sure cursor is not draw when we are not in focus
    cursor_on=i4_F;

  if (hi_x1!=hi_x2)
  {
    hi_x1=hi_x2;
    request_redraw();
  }

  stop_blink();

  if (selecting)
  {
    i4_window_request_mouse_ungrab_class ungrab(this);
    send_to_parent(&ungrab);
    selecting=i4_F;
  }

}
  
void i4_text_input_class::receive_event(i4_event *ev)
{
  switch (ev->type())
  {
    case i4_event::WINDOW_MESSAGE :        
    {
      CAST_PTR(wev,i4_window_message_class,ev);
      switch (wev->sub_type)
      {
        case i4_window_message_class::GOT_DROP :
        {
          CAST_PTR(dev, i4_window_got_drop_class, ev);

          if (dev->drag_info.drag_object_type==i4_drag_info_struct::FILENAMES)
          {
            if (dev->drag_info.t_filenames==1)
            {
              change_text(*dev->drag_info.filenames[0]);
              if (change_notify)
              {
                i4_text_change_notify_event o(this, st);
                i4_kernel.send_event(change_notify, &o);                
                sent_change=i4_T;
              }
            }
          }

        } break;

        case i4_window_message_class::GOT_MOUSE_FOCUS :
        {
          set_cursor(style->cursor_hint->text_cursor()->copy());
        } break;

        case i4_window_message_class::LOST_MOUSE_FOCUS :
        {
          set_cursor(0);
        } break;


        case i4_window_message_class::LOST_KEYBOARD_FOCUS :
        {
          become_unactive(); 

          if (change_notify && changed && !sent_change)
          {
            i4_text_change_notify_event o(this, st);
            i4_kernel.send_event(change_notify, &o);
            sent_change=i4_T;
          }
        } break;
          
        default :          
          i4_window_class::receive_event(ev);
      }
    } break;

    case i4_event::OBJECT_MESSAGE :
    {
      CAST_PTR(oev,i4_object_message_event_class,ev);
      if (oev->object==this)                              // was this an event we created?
      {
        need_cancel_blink=i4_F;
        cursor_on=(i4_bool)(!cursor_on);
        request_redraw();
        request_blink();
      }
    } break;

    case i4_event::MOUSE_BUTTON_DOWN :
    {
      CAST_PTR(bev,i4_mouse_button_down_event_class,ev);
      if (bev->but==i4_mouse_button_down_event_class::LEFT)
      {
        become_active();

        selecting=i4_T;
          
        hi_x1=mouse_to_cursor();
        hi_x2=hi_x1;
        cursor_x=hi_x1;

        i4_window_request_mouse_grab_class grab(this);
        send_to_parent(&grab);


      }

    } break;

    case i4_event::MOUSE_BUTTON_UP :
    {       
      CAST_PTR(bev,i4_mouse_button_up_event_class,ev);
      if (bev->but==i4_mouse_button_up_event_class::LEFT && selecting)
      {
        selecting=i4_F;
        i4_window_request_mouse_ungrab_class ungrab(this);
        send_to_parent(&ungrab);

      }
    } break;


    case i4_event::MOUSE_MOVE : 
    {
      CAST_PTR(mev,i4_mouse_move_event_class,ev);
      last_x=mev->x;
      last_y=mev->y;
      if (selecting)
      {
        w32 old_hi_x2=hi_x2;
        hi_x2=mouse_to_cursor();
        if (old_hi_x2!=hi_x2)
        {
          request_redraw();
          cursor_x=hi_x2;
        }
      }
    } break;

    case i4_event::KEY_PRESS :
    {
      CAST_PTR(kev,i4_key_press_event_class,ev);

      switch (kev->key)
      {
        case I4_BACKSPACE :
        {
          if (hi_x1!=hi_x2)
            del_selected();
          else if (cursor_x)
          {
            cursor_x--;
            w32 cur=cursor_x;
            i4_str::iterator cursor1=st->begin(),cursor2=st->begin();
            while (cur)
            {
              cur--;
              ++cursor1;
            }
            cursor2=cursor1;
            ++cursor2;
            st->remove(cursor1,cursor2);
            sustain_cursor();
          }
          note_change();
        

        } break;
        
        case I4_LEFT :
        {
          move_cursor_left();
        } break;

        case I4_RIGHT :
        {
          move_cursor_right();
        } break;

        case I4_HOME :
        {
          cursor_x = 0;
          left_x = 0;
          sustain_cursor();
        } break;

        case I4_END :
        {
          cursor_x = 0;
          left_x = 0;
          sustain_cursor();
        } break;

        case I4_ENTER :
        {
          if (change_notify && changed && !sent_change)
          {
            i4_text_change_notify_event o(this, st);
            i4_kernel.send_event(change_notify, &o);
            sent_change=i4_T; 
          }
        } break;

        default :
        {
          if (kev->key>=' ' && kev->key<='~')
          {
            note_change();
        
            if (hi_x1!=hi_x2)
              del_selected();
            w32 cur=cursor_x;
            i4_str::iterator cursor=st->begin();
            while (cur)
            {
              cur--;
              ++cursor;
            }

            st->insert(cursor,kev->key);
            move_cursor_right();
          }

        } break;
      }
    } break;


    case i4_event::QUERY_MESSAGE :
    {
      CAST_PTR(qev,i4_query_text_input_class,ev);
      qev->copy_of_data=new i4_str(*st,st->length()+1);
    } break;

    default :
      i4_window_class::receive_event(ev);
  }
}

i4_text_input_class::~i4_text_input_class()
{
  if (change_notify && changed && !sent_change)
  {
    i4_text_change_notify_event o(this, st);
    i4_kernel.send_event(change_notify, &o);
    sent_change=i4_T; 
  }

  stop_blink();
  delete st;
}


i4_window_class *i4_create_text_input(i4_graphical_style_class *style,
                                      const i4_const_str &default_string,
                                      w32 width,        // width in pixels of window
                                      w32 max_width)
{
  i4_text_input_class *ti=new i4_text_input_class(style,
                                                  default_string,
                                                  width,
                                                  max_width);



  return ti;
}

void i4_text_input_class::change_text(const i4_const_str &new_st)
{
  delete st;
  st=new i4_str(new_st, max_width);
  request_redraw();
  left_x=0;
  cursor_x=0;
}