/***************************************************************************
            SIMPLY POWERFUL TOOLKIT (SPTK)
            CDialog.cpp  -  description
                                                                                       -------------------
            begin                : Fri Mar 31 2000
            copyright            : (C) 2000-2005 by Alexey S.Parshin
            email                : alexeyp@gmail.com
***************************************************************************/

/***************************************************************************
         This library is free software; you can redistribute it and/or modify it 
         under the terms of the GNU Library General Public License as published by
         the Free Software Foundation; either version 2 of the License, or (at 
         your option) any later version.

         This library 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 Library
         General Public License for more details.

         You should have received a copy of the GNU Library General Public License
         along with this library; if not, write to the Free Software Foundation, 
         Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

         Please report all bugs and problems to "alexeyp@gmail.com"
   ***************************************************************************/

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

#include <string.h>
#include <stdio.h>
#include <sptk3/CDatabase.h>
#include <sptk3/CMessageDialog.h>
#include <sptk3/CException.h>
#include <sptk3/CInput.h>
#include <sptk3/CQuery.h>
#include <sptk3/CRegistry.h>
#include <sptk3/CTabs.h>
#include <sptk3/CCheckButtons.h>
#include <sptk3/CRadioButtons.h>
#include <sptk3/string_ext.h>

using namespace std;

static const unsigned headerHeight = 20;

//==============================================================================
Fl_Group * CDialogTabs::adjustPage(Fl_Group *page) {
   if (children() == 1) {
      box(FL_THIN_DOWN_FRAME);
      showTabs(false);
      page->box(FL_NO_BOX);
   } else {
      box(FL_THIN_UP_BOX);
      showTabs(true);
      page->box(FL_FLAT_BOX);
   }
   return page;
}

Fl_Group * CDialogTabs::newPage(const char *label,bool autoColor) {
   return adjustPage(CTabs::newPage(label,autoColor));
}

Fl_Group * CDialogTabs::newPage(const char *label,Fl_Color color) {
   Fl_Group * grp = adjustPage(CTabs::newPage(label,false));
   grp->color(color);
   return grp;
}

Fl_Group * CDialogTabs::newScroll(const char *label,bool autoColor) {
   if (!children()) {
      box(FL_THIN_DOWN_BOX);
      showTabs(false);
   } else {
      box(FL_THIN_UP_BOX);
      showTabs(true);
   }
   Fl_Group *grp = CTabs::newScroll(label,autoColor);
   return grp;
}

Fl_Group * CDialogTabs::newScroll(const char *label,Fl_Color color) {
   if (!children()) {
      box(FL_THIN_DOWN_BOX);
      showTabs(false);
   } else {
      box(FL_THIN_UP_BOX);
      showTabs(true);
   }
   Fl_Group *grp = CTabs::newScroll(label,false);
   grp->color(color);
   return grp;
}
//==============================================================================
CDialog::CDialog(int w, int h, const char *label)
: CWindow(w,h,label) {
   m_queriesBuilt = false;
   m_controlsScanned = false;
   m_keyValue = -1;
   m_defaultButton = NULL;

   fl_cursor(FL_CURSOR_WAIT);
   Fl::check();

   m_selectQuery = new CQuery;
   m_updateQuery = new CQuery;
   m_insertQuery = new CQuery;

   m_buttonGroup = new CGroup("",20,SP_ALIGN_BOTTOM);
   m_okButton = new CButton(SP_OK_BUTTON);
   m_cancelButton = new CButton(SP_CANCEL_BUTTON);
   m_buttonGroup->end();

   m_pages = new CDialogTabs;

   defaultButton(m_okButton);

   newPage("");

   m_modalResult = DMR_NONE;
}

CDialog::~CDialog() {
   delete m_selectQuery;
   delete m_updateQuery;
   delete m_insertQuery;
}

int CDialog::handle(int event) {
   int rc = Fl_Group::handle(event);
   if (rc) return rc;
   switch (event) {
      case FL_KEYBOARD:
         if (Fl::event_key() == FL_Enter) {
            Fl_Button *btn = dynamic_cast<Fl_Button *>(Fl::focus());
            if (btn)
               btn->do_callback();
            else if (m_defaultButton)
               m_defaultButton->do_callback();
            return 1;
         }
         break;
   }
   return 0;
}

void CDialog::defaultButton(CButton *newDefaultButton) {
   unsigned cnt = m_buttonGroup->children();
   for (unsigned bi = 0; bi < cnt; bi++) {
      CButton *button = (CButton *)m_buttonGroup->child(bi);
      button->defaultButton(button == newDefaultButton);
   }
   m_defaultButton = newDefaultButton;
}

bool CDialog::okPressed() {
   try {
      save();
   }
   catch (exception& e) {
      spError(e.what());
      return false;
   }
   return true;
}

bool CDialog::cancelPressed() {
   return true;
}

bool CDialog::showModal() {
   resize(x(),y(),w(),h());
   Fl_Widget *pressed;
   int x = (Fl::w() - w()) / 2;
   int y = (Fl::h() - h()) / 2;
   position(x,y);
   set_modal();
   fl_cursor(FL_CURSOR_DEFAULT);
   show();
   bool rc = false;

   m_modalResult = DMR_NONE;

   while (m_modalResult == DMR_NONE) {
      pressed = Fl::readqueue();

      if (!pressed) Fl::wait(0.1);
      else {
         fl_cursor(FL_CURSOR_WAIT);
         Fl::check();
         if (pressed == this || pressed == m_cancelButton) {
            if (cancelPressed()) {
               m_modalResult = DMR_CANCEL;
               break;
            }
         }
         if (pressed == m_okButton) {
            if (okPressed()) {
               rc = true;
               m_modalResult = DMR_OK;
               break;
            }
         }
         fl_cursor(FL_CURSOR_DEFAULT);
      }
   }

   if (m_modalResult == DMR_USER)
      rc = true;

   fl_cursor(FL_CURSOR_DEFAULT);
   hide();
   Fl::check();
   return rc;
}

void CDialog::database(CDatabase *db) {
   m_selectQuery->database(db);
   m_updateQuery->database(db);
   m_insertQuery->database(db);
}

CDatabase * CDialog::database() const {
   return m_selectQuery->database();
}

void CDialog::table(const string tableName) {
   if (m_tableName != tableName) {
      m_tableName = tableName;
      m_queriesBuilt = false;
   }
}

void CDialog::table(CDatabase *db,const string tb,const string key) {
   database(db);
   table(tb);
   keyField(key);
}

void CDialog::keyField(const string fldName) {
   if (m_keyField != fldName) {
      m_keyValue = -1;
      m_keyField = fldName;
      m_queriesBuilt = false;
   }
}

void CDialog::keyValue(int val) {
   m_keyValue = val;
   try {
      load();
   }
   catch (exception& e) {
      m_keyValue = -1;
      spError(e.what());
   }
}

bool CDialog::buildQueries() {
   string columnNames;
   string paramNames;
   string updateNames;

   if (!m_controlsScanned) scanControls();

   if (m_queriesBuilt) return true;

   CControlList::const_iterator itor = m_defaultFields.begin();
   bool first = true;
   for (; itor != m_defaultFields.end(); itor++) {
      const CControl* control = itor->second;
      const string& fieldName = control->fieldName();
      if (!first) {
         columnNames += "," + fieldName;
         paramNames  += ",:" + fieldName;
         updateNames += "," + fieldName + "=:" + fieldName;
      } else {
         columnNames = fieldName;
         paramNames  = ":" + fieldName;
         updateNames = fieldName + "=:" + fieldName;
      }
      first = false;
   }

   m_selectQuery->sql("SELECT "+columnNames+" FROM "+m_tableName+" WHERE "+m_keyField+"=:key");
   m_insertQuery->sql("INSERT INTO "+m_tableName+"("+columnNames+") VALUES ("+paramNames+")");
   m_updateQuery->sql("UPDATE "+m_tableName+" SET "+updateNames+" WHERE "+m_keyField+"=:key");

   m_queriesBuilt = true;

   return true;
}

bool CDialog::load() {
   buildQueries();
   reset();
   if (m_keyValue > 0) {
      m_selectQuery->param("key").setInteger(m_keyValue);
      m_selectQuery->open();
      CControlList::iterator itor = m_defaultFields.begin();
      for (; itor != m_defaultFields.end(); itor++) {
         try {
            CControl* control = itor->second;
            control->load(m_selectQuery);
         }
         catch (exception& e) {
            spError(e.what());
         }
      }
      m_selectQuery->close();
   }
   return true;
}

bool CDialog::save() {

   if (!m_controlsScanned) scanControls();

   if (!m_defaultFields.size()) return true;

   // Validating data in controls
   CControlList::iterator itor = m_defaultFields.begin();
   for (; itor != m_defaultFields.end(); itor++) {
      CControl* control = itor->second;
      if (!control->valid()) {
         Fl::focus(control->control());
         throw CException(control->label() + " entry has the incorrect value!");
      }
      if (control->flags() & FGE_MANDATORY) {
         string test = control->data();
         if (!trim(test).length()) {
            Fl::focus(control->control());
            throw CException(control->label() + " entry can't be empty!");
         }
      }
   }

   if (!database()) return true;
   buildQueries();
   CQuery *query = m_insertQuery;

   if (m_keyValue > 0) {
      query = m_updateQuery;
      query->param("key").setInteger(m_keyValue);
   }

   itor = m_defaultFields.begin();
   for (; itor != m_defaultFields.end(); itor++) {
      CControl* control = itor->second;
      control->save(query);
   }
   query->exec();

   return true;
}

bool CDialog::loadFromRegistry(CRegistry *registry,const char *section) {
   registry->topic(section);
   CControlList::iterator itor = m_allFields.begin();
   for (; itor != m_allFields.end(); itor++) {
      CControl* control = itor->second;
      string   stringData;
      string   fieldName = control->fieldName();
      try {
         stringData = (*registry)[fieldName.c_str()].c_str();
         control->data(stringData.c_str());
      }
      catch (...) {
         continue;
      }
   }
   return true;
}

bool CDialog::saveToRegistry(CRegistry *registry,const char *section) {
   registry->topic(section);
   registry->topic().clear();
   CControlList::iterator itor = m_allFields.begin();
   for (; itor != m_allFields.end(); itor++) {
      CControl* control = itor->second;
      string   fieldName = control->fieldName();
      string   stringData = trim(control->data());
      if (stringData.length())
         (*registry)[fieldName.c_str()] = string(stringData.c_str());
   }
   return true;
}

CControl& CDialog::operator [] (string fieldName) {
   if (!m_controlsScanned) scanControls();
   CControlList::iterator itor = m_allFields.find(fieldName);
   if (itor != m_allFields.end())
      return *itor->second;
   throw CException("The dialog window doesn't have a field '"+fieldName+"'");
}

CButton *CDialog::addExtraButton(CButtonKind buttonKind,const char *label,Fl_Callback_p callbackFunction) {
   m_buttonGroup->begin();
   CButton *extraButton = new CButton(buttonKind,SP_ALIGN_RIGHT,label);
   m_buttonGroup->end();
   if (callbackFunction)
      extraButton->callback(callbackFunction);
   return extraButton;
}

int CDialog::keyValue() const {
   return m_keyValue;
}

void CDialog::alert(const string s) const {
   spWarning(s.c_str());
}

void CDialog::scanControls() {
   m_allFields.clear();
   m_defaultFields.clear();
   m_allFields << *m_pages;
   m_defaultFields = m_allFields;
   m_defaultFields.remove(m_specialProcessingControls);
   m_controlsScanned = true;
   m_queriesBuilt = false;
}

Fl_Group* CDialog::newPage(const char *label,bool autoColor) {
   return m_pages->newPage(label,autoColor);
}

Fl_Group* CDialog::newPage(const char *label,Fl_Color color) {
   return m_pages->newPage(label,color);
}

Fl_Group* CDialog::newScroll(const char *label,bool autoColor) {
   return m_pages->newScroll(label,autoColor);
}

Fl_Group* CDialog::newScroll(const char *label,Fl_Color color) {
   return m_pages->newScroll(label,color);
}

bool CDialog::reset() {
   if (!m_controlsScanned) scanControls();
   m_allFields.reset();
   return true;
}
