// ----------------------------------------------------------------------------
// Copyright (C) 2014
//              David Freese, W1HKJ
//
// This file is part of fldigi
//
// fldigi 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 3 of the License, or
// (at your option) any later version.
//
// fldigi 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, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------

#include <config.h>

#include <cstring>
#include <cstdio>
#include <cstdlib>

#include <FL/Fl.H>
#include <FL/Fl_Pixmap.H>

#include "pixmaps.h"
#include "calendar.h"

void popcal_cb (Fl_Widget *v, long d);

static void fl_calendar_button_cb (Fl_Button *a, void *b)
{
  long j=0;
  Fl_Calendar *c = (Fl_Calendar *)b;
  Fl_Button   *sb;
  int numdays = c->daysinmonth () + 1;
  for (int i=1; i < numdays; i++) {
    sb = c->day_button(i);
    sb->color (52);
    if (a == sb) {
	    c->Day (i);
	    j = i;
      sb->color (sb->selection_color());
      if (c->target) {
        ((Fl_Input2 *)(c->target))->value(c->szDate(c->calfmt));
        (c->target)->redraw();
      }
    }
  }
  c->redraw();
  c->do_callback(c, j);
}

void
Fl_Calendar_Base::setTarget (Fl_Widget *tgt)
{
  target = tgt;
}

Fl_Calendar_Base::Fl_Calendar_Base (int x, int y, int w, int h, 
				    const char *l) : Fl_Group (x, y, w, h, l), Date () 
{
  int i;  
  
  for (i = 0; i<(7*6); i++)
  {
    days[i] = new Fl_Button ((w/7)*(i%7) + x,
			     (h/6)*(i/7) + y,
			     (w/7),
			     (h/6));
    days[i]->down_box (FL_THIN_DOWN_BOX);
    days[i]->labelsize (10);
    days[i]->box (FL_THIN_UP_BOX);
    days[i]->color (52);
    days[i]->callback ((Fl_Callback*)&fl_calendar_button_cb, (void *)this);
  }
  calfmt = 0;
}

void Fl_Calendar_Base::csize (int cx, int cy, int cw, int ch)
{
  int i;
  for (i = 0; i<(7*6); i++)
  {
    days[i]->resize ((cw/7)*(i%7) + cx,
		   (ch/6)*(i/7) + cy,
		   (cw/7),
	    (ch/6));
  }
}


void
Fl_Calendar_Base::update ()
{
  int dow = dayofweek (Year(), Month(), 1);
  int dim = daysinmonth (Month(), isleapyear (Year()));
  int i;

  for (i=0; i<dow; i++)
    {
      days[i]->hide ();
    }

  for (i=(dim+dow); i<(6*7); i++)
    {
      days[i]->hide ();
    }
  
  for (i=dow; i<(dim+dow); i++)
    {
      char t[8];
      snprintf (t, sizeof(t), "%d", (i-dow+1));
      days[i]->label (strdup(t));
      days[i]->color (52);
      if ((i-dow+1) == Day())
        days[i]->color (selection_color());
      days[i]->show ();
    }
}

Fl_Button *
Fl_Calendar_Base::day_button (int i)
{
  if ((i > 0) && (i <= daysinmonth ()))
    return days[i + dayofweek (Year(), Month(), 1) - 1];
  return 0;
}



static void 
fl_calendar_prv_month_cb (Fl_Button *, void *b)
{
  Fl_Calendar *c = (Fl_Calendar *)b;
  c->previous_month ();
  c->do_callback(c, (long)0);
}

static void 
fl_calendar_nxt_month_cb (Fl_Button *, void *b)
{
  Fl_Calendar *c = (Fl_Calendar *)b;
  c->next_month ();
  c->do_callback(c, (long)0);
}

static void 
fl_calendar_prv_year_cb (Fl_Button *, void *b)
{
  Fl_Calendar *c = (Fl_Calendar *)b;
  c->previous_year ();
  c->do_callback(c, (long)0);
}

static void 
fl_calendar_nxt_year_cb (Fl_Button *, void *b)
{
  Fl_Calendar *c = (Fl_Calendar *)b;
  c->next_year ();
  c->do_callback(c, (long)0);
}

Fl_Calendar::Fl_Calendar (int x, int y, int w, int h, 
			  const char *l) : Fl_Calendar_Base (x, y, w, h, l) 
{
  int i, bw;
  for (i = 0; i<7; i++) {
//    weekdays[i] = new Fl_Button ((w/7)*(i%7) + x,
    weekdays[i] = new Fl_Box ((w/7)*(i%7) + x,
                              (h/8)*((i/7)+1) + y,
                              (w/7),
                              (h/8));
    weekdays[i]->box (FL_THIN_UP_BOX);  
    weekdays[i]->labelsize (10);
    weekdays[i]->color (52);
  }
  
  weekdays[SUNDAY]->label ("S");
  weekdays[MONDAY]->label ("M");
  weekdays[TUESDAY]->label ("T");
  weekdays[WEDNESDAY]->label ("W");
  weekdays[THURSDAY]->label ("T");
  weekdays[FRIDAY]->label ("F");
  weekdays[SATURDAY]->label ("S");

  bw = w/10 < 16 ? 16 : w/10;
  prv_year = new Fl_Button (x, y, bw, (h/8), "@<<");
  prv_year->box (FL_THIN_UP_BOX);
  prv_year->labeltype (FL_SYMBOL_LABEL);
  prv_year->labelsize (10);
  prv_year->down_box (FL_THIN_DOWN_BOX);
  prv_year->callback ((Fl_Callback*)&fl_calendar_prv_year_cb, (void *)this);  

  prv_month = new Fl_Button (x + bw, y, bw, (h/8), "@<");
  prv_month->box (FL_THIN_UP_BOX);
  prv_month->labeltype (FL_SYMBOL_LABEL);
  prv_month->labelsize (10);
  prv_month->down_box (FL_THIN_DOWN_BOX);
  prv_month->callback ((Fl_Callback*)&fl_calendar_prv_month_cb, (void *)this);  

  nxt_month = new Fl_Button (x + w - 2*bw, y, bw, (h/8), "@>");
  nxt_month->box (FL_THIN_UP_BOX);
  nxt_month->labeltype (FL_SYMBOL_LABEL);
  nxt_month->labelsize (10);
  nxt_month->down_box (FL_THIN_DOWN_BOX);
  nxt_month->callback ((Fl_Callback*)&fl_calendar_nxt_month_cb, (void *)this);
  
  nxt_year = new Fl_Button (x + w - bw, y, bw, (h/8), "@>>");
  nxt_year->box (FL_THIN_UP_BOX);
  nxt_year->labeltype (FL_SYMBOL_LABEL);
  nxt_year->labelsize (10);
  nxt_year->down_box (FL_THIN_DOWN_BOX);
  nxt_year->callback ((Fl_Callback*)&fl_calendar_nxt_year_cb, (void *)this);

//  caption = new Fl_Button (x + (w/10)*2, y, (6*w/10), (h/8));
  caption = new Fl_Box (x + 2*bw, y, w - 4*bw, (h/8));
  caption->box (FL_THIN_UP_BOX);
  caption->labeltype (FL_SYMBOL_LABEL);
  caption->labelfont (1);
  if (bw < 20)
    caption->labelsize (9);
  else
    caption->labelsize (11);
//  caption->down_box (FL_THIN_DOWN_BOX);
      
  Fl_Calendar_Base::csize (x, y + (2*h/8), w, (6*h/8));

  target = 0;

  update ();
}

void
Fl_Calendar::csize (int cx, int cy, int cw, int ch)
{
  int i;
  for (i = 0; i<7; i++)
    {
//      weekdays[i] = new Fl_Button ((cw/7)*(i%7) + cx,
      weekdays[i] = new Fl_Box ((cw/7)*(i%7) + cx,
				   (ch/8)*((i/7)+1) + cy,
				   (cw/7),
				   (ch/8));
    }

  prv_month->resize (cx + (cw/10), cy, (cw/10), (ch/8));
  nxt_month->resize (cx + (cw/10)*8, cy, (cw/10), (ch/8));
  prv_year->resize (cx, cy, (cw/10), (ch/8));
  nxt_year->resize (cx + (cw/10)*9, cy, (cw/10), (ch/8));
  caption->resize (cx + (cw/10)*2, cy, (cw/10)*6, (ch/8));
  
  Fl_Calendar_Base::csize (cx, cy + (2*ch/8), cw, (6*ch/8));  
}

void 
Fl_Calendar::update ()
{
  int dow = dayofweek (Year(), Month(), 1);
  int dim = daysinmonth (Month(), isleapyear (Year()));
  int i;
  
  for (i=dow; i<(dim+dow); i++)
    {
      char t[8];
      snprintf (t, sizeof(t), "%d", (i-dow+1));
      days[i]->label (strdup(t));
    }

  char tmp[32];
  snprintf (tmp, sizeof(tmp), "%s %d", month_name[Month()-1], Year());
  Fl_Calendar_Base::update ();
  if (caption->label ())
    free ((void *) caption->label ());
  caption->label (strdup(tmp));
  redraw ();
}

void Fl_Calendar::today ()
{
  Date::today();
  update ();
}

void Fl_Calendar::previous_month ()
{
  Date::previous_month();
  update ();
}

void
Fl_Calendar::next_month ()
{
  Date::next_month();
  update ();
}

void
Fl_Calendar::previous_year ()
{
  Date::previous_year();
  update ();
}

void Fl_Calendar::next_year ()
{
  Date::next_year();
  update ();
}

void Fl_Calendar::setDate(int m, int d, int y)
{
  Date::setDate(m,d,y);
}

int 
Fl_Calendar::handle (int event) 
{ 
  int m, d, y, o, md; 

  switch (event) 
    { 
    case FL_FOCUS: 
    case FL_UNFOCUS: 
      return 1; 
      
    case FL_KEYBOARD: 
      m = Month (); 
      d = Day (); 
      y = Year (); 
      switch(Fl::event_key ()) 
	{ 
        case FL_Enter:
          do_callback(this, d);
          return 1;
          break;
	case FL_Up: 
	  o = -7; 
	  break; 
	case FL_Down: 
	  o = 7; 
	  break; 
	case FL_Right: 
	  o = 1; 
	  break; 
	case FL_Left: 
	  o = -1; 
	  break; 
	case FL_Page_Up:
	  previous_month (); 
	  return 1; 
	case FL_Page_Down: 
	  next_month (); 
	  return 1; 
	default: 
	  return Fl_Group::handle (event); 
	} 
    if (datevalid (y, m, d + o))
      setDate (m, d + o, y); 
      else 
	{ 
	  if (o < 0) 
	    { 
	      previous_month (); 
	      m = Month (); 
	      y = Year (); 
	      md = daysinmonth (m, isleapyear (y)); 
	      d = d + o + md; 
        setDate (m, d, y);
	    } 
	  else 
	    { 
	      md = daysinmonth (m, isleapyear (y)); 
	      next_month (); 
	      m = Month (); 
	      y = Year (); 
	      d = d + o - md;
        setDate (m, d, y); 
	    } 
	} 
      return 1;
    }
  return Fl_Group::handle (event);
}


// Popup Calendar class

Fl_PopCal::Fl_PopCal (int X, int Y, int W, int H, Fl_Input2 * tgt)
 : Fl_Window (X, Y, W, H, "")
{
  target = tgt;
  clear_border();
  box(FL_UP_BOX);
//  popcal = new Fl_Calendar(2, 2);
  popcal = new Fl_Calendar(2, 2, W-4, H-4);
  popcal->callback ( (Fl_Callback*)popcal_cb);
  end();
}


Fl_PopCal::~Fl_PopCal ()
{
}

void Fl_PopCal::popcalfmt (int i) 
{
  popcalfmt_ = i;
}

int Fl_PopCal::popcalfmt () 
{
  return popcalfmt_;
}

void Fl_PopCal::setDate (int m, int d, int y)
{
  popcal->setDate (m,d,y);
  popcal->update();
}

int Fl_PopCal::handle(int event)
{
  int ex = Fl::event_x_root(),
      ey = Fl::event_y_root();
  if (event == FL_PUSH) {
    if ( ex < x() || ex > (x() + w()) ||
         ey < y() || ey > (y() + h()) ) {
      pophide();
      return 1;
    }
  }
  if (Fl_Group::handle(event)) return 1;
  return 0;
}

void Fl_PopCal::popposition (int x, int y)
{
  position (x, y);
}

void Fl_PopCal::popshow ()
{
  show ();
  Fl::grab(this);
}

void Fl_PopCal::pophide ()
{
  hide ();
  Fl::release();
}   

void Fl_PopCal::popcal_cb_i (Fl_Widget *v, long d)
{
  int ey = Fl::event_y_root();
  Fl_PopCal *me = (Fl_PopCal *)(v->parent());
  Fl_Input2 *tgt = me->target;
  if (ey > me->y() + 40) {
    if (d && tgt) 
      tgt->value (((Fl_Calendar *)v)->szDate (me->popcalfmt_));
    me->pophide();
  }
  return;
}

void popcal_cb (Fl_Widget *v, long d)
{
  ((Fl_PopCal *)(v))->popcal_cb_i (v, d);
  return;
}

void
Fl_DateInput::fl_popcal()
{
  Fl_Widget *who = this, *parent;
  int xpos = who->x(), ypos = who->h() + who->y();
  int w = who->w(), h;
  int m = 0, d = 0, y = 0;

  w = w < 140 ? 140 : w;
  w = w - (w % 7);
  h = 8*(w/7);
  w += 4; h += 4;
  parent = who;
  while (parent) {
    who = parent;
    parent = parent->parent();
    if (parent == 0) {
      xpos += who->x();
      ypos += who->y();
    }
  }
  if (!Cal)
//    Cal = new Fl_PopCal(xpos, ypos, 7*20+4, 8*20+4, Input);
    Cal = new Fl_PopCal(xpos, ypos, w, h, Input);
  else
    Cal->popposition(xpos, ypos);

  if (popcalfmt_ < 3) {
    switch (popcalfmt_) {
      case 0:
      case 1:
        sscanf(Input->value(), "%d/%d/%d", &m, &d, &y);
        break;
      case 2:
      default:
        sscanf(Input->value(),"%4d%2d%2d", &y, &m, &d);
        break;
    }
    if (y < 10) y+=2000;
    if (y < 100) y+=1900;
    Cal->setDate (m,d,y);
  }
  Cal->popcalfmt (popcalfmt_);

  Cal->popshow();
  return;
}

void btnDateInput_cb (Fl_Widget *v, void *d)
{
  ((Fl_DateInput *)(v->parent()))->fl_popcal ();
  return;
}


Fl_DateInput::Fl_DateInput (int X,int Y,int W,int H, const char *L)
 : Fl_Group (X, Y, W, H, 0)
{
  Btn = new Fl_Button (X + W - H, Y, H, H);
  (new Fl_Pixmap (time_icon))->label (Btn);
  Btn->callback ((Fl_Callback *)btnDateInput_cb, 0);
  Input = new Fl_Input2 (X, Y, W-H, H, L);

  popcalfmt_ = 0;
  Cal = 0;
  end();
}

void Fl_DateInput::align (Fl_Align how)
{
  Input->align(how);
}

// DateInput value is contained in the Input widget

void Fl_DateInput::value( const char *s )
{
  Input->value (s);
}

const char *Fl_DateInput::value()
{
  return (Input->value ());
}


void Fl_DateInput::textfont(int tf)
{
  Input->textfont (tf);
}
 
void Fl_DateInput::textsize(int sz)
{
  Input->textsize (sz);
}

void Fl_DateInput::textcolor(Fl_Color c)
{
  Input->textcolor(c);
}

void Fl_DateInput::color(Fl_Color c)
{
  Input->color(c);
}

void Fl_DateInput::labelfont(int fnt)
{
  Input->labelfont(fnt);
}

void Fl_DateInput::labelsize(int size)
{
  Input->labelsize(size);
}

void Fl_DateInput::labelcolor(int clr)
{
  Input->labelcolor(clr);
}

void Fl_DateInput::format (int fmt)
{
  switch (fmt) {
    case 0: 
    case 1: 
    case 2:
    case 3:
    case 4: 
      popcalfmt_ = fmt; 
      break;
    default : 
      popcalfmt_ = 0;
  }
}

void Fl_DateInput::take_focus() {
  Input->take_focus();
}
