/*
* Descent 3 
* Copyright (C) 2024 Parallax Software
*
* 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 3 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, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/ui/UIGadget.cpp $
 * $Revision: 28 $
 * $Date: 10/21/99 3:31p $
 * $Author: Kevin $
 *
 *	Gadget code
 *
 * $Log: /DescentIII/Main/ui/UIGadget.cpp $
 *
 * 28    10/21/99 3:31p Kevin
 * mac merge & fix for slider
 *
 * 27    4/14/99 1:52a Jeff
 * fixed case mismatched #includes
 *
 * 26    3/23/99 9:02p Samir
 * always null hotkey when creating gadget!
 *
 * 25    3/01/99 7:29p Samir
 * fixed an issue with slaves getting freed after their master was
 * destroyed for sliders.
 *
 * 24    2/22/99 8:13p Samir
 * added slave gadget system that only uses mouse input from window, but
 * defers keys to its master.
 *
 * 23    2/21/99 6:36p Samir
 * focusing changes and key input changes to ui.,
 *
 * 22    2/17/99 8:32p Samir
 * fixed bugs in remove gadget, added callbacks for attaching and
 * detaching gadgets.
 *
 * 21    2/16/99 11:58a Samir
 * small fixes and added gadget notification.
 *
 * 20    10/19/98 12:18p Samir
 * made tabbing between edit controls possible.
 *
 * 19    10/13/98 8:23p Samir
 * communication between gadget and window and edit system to work
 * together.
 *
 * 18    8/26/98 12:25p Samir
 * fixed the whole editbox, keyboard access mess.
 *
 * 17    8/25/98 7:08p Samir
 * select how keys are sent to a gadget.
 *
 * 16    6/22/98 7:30p Samir
 * set focus on gadget when calling LockFocus.
 *
 * 15    6/05/98 5:35p Samir
 * massive improvement in UI keyboard interface.
 *
 * 14    5/25/98 8:18p Samir
 * Enter keys are passed to gadgets too.
 *
 * 13    4/24/98 2:43a Samir
 * added IsLocked function.
 *
 * 12    3/24/98 4:26p Samir
 * fixed minor bug in TrapKey.
 *
 * 11    3/10/98 7:24p Samir
 * Save original width and height.
 *
 * 10    3/09/98 2:57p Samir
 * Remove gadget from window called from gadget destroy.
 *
 * 9     2/15/98 7:07p Samir
 * Revamped way keys like up and down are handled in window system.
 *
 * 8     2/13/98 6:35p Samir
 * Fixed some gadget creation/destruction inconsistancies
 *
 * 7     1/30/98 7:05p Samir
 * Each gadget now has their own coordinate system.
 *
 * 6     1/23/98 5:34p Samir
 * Allow for nonpersistant UIItems.
 *
 * 5     1/18/98 4:22p Samir
 * Implemented new UIItem system.
 *
 * 4     1/13/98 4:27p Samir
 * Keyboard timing delay stored UIGadget.  Moved from UIEdit.
 *
 * 3     1/05/98 4:36p Samir
 * Moved centering and fittext control flags to UIGadget class.
 *
 * 2     1/02/98 12:43p Samir
 * Gadget processing functional.
 *
 * 1     12/30/97 4:36p Samir
 * Initial revision
 *
 * $NoKeywords: $
 */

#include <cstring>

#include "UIlib.h"
#include "Macros.h"

int UIGadget::m_LastKey = 0;
int UIGadget::m_LastKeyCount = 0;
float UIGadget::m_LastKeyTime = 0.0f;

UIGadget::UIGadget() : UIObject() {
  m_ID = 0;
  m_Wnd = NULL;
  m_Prev = m_PrevSlave = NULL;
  m_Next = m_NextSlave = NULL;
  m_MasterGadget = NULL;
  m_SlaveGadgets = NULL;
  m_CurrentSlave = NULL;
  m_infocus = false;
}

UIGadget::~UIGadget() {}

//	ALL Gadgets must have a parent.
void UIGadget::Create(UIWindow *wnd, int id, int x, int y, int w, int h, int flags) {
  ASSERT(!IsCreated());
  ASSERT(wnd);

  m_ID = id;
  m_Wnd = wnd;

  m_Disabled = false;
  m_Flags = flags;
  m_infocus = false;

  //	add to interface's list.
  UIObject::Create(x, y, w, h);

  //	save original width and height
  m_SavedW = m_W;
  m_SavedH = m_H;
  m_Hotkey = 0;

  //	handle formatting issues.
  OnFormat();
  m_MasterGadget = NULL;
  m_SlaveGadgets = NULL;
  m_CurrentSlave = NULL;
  m_NextSlave = m_PrevSlave = NULL;
  m_Datum = 0;

  m_Wnd->AddGadget(this);
}

void UIGadget::Destroy() {
  // slaves are freed or destroyed by now.
  m_SlaveGadgets = NULL;

  // remove gadget from windw list.
  if (m_Wnd) {
    m_Wnd->RemoveGadget(this);
  }
  OnDestroy();
  UIObject::Destroy();
}

//	sets hotkey for gadget
void UIGadget::SetHotkey(int key) { m_Hotkey = key; }

void UIGadget::SetFlag(int flag) { m_Flags |= flag; }

void UIGadget::ClearFlag(int flag) { m_Flags &= (~flag); }

//	locks input on this gadget
void UIGadget::LockFocus() {
  //	m_Wnd->SetFocusOnGadget(this);
  m_Wnd->LockFocusOnGadget(this);
}

void UIGadget::UnlockFocus() { m_Wnd->UnlockFocusOnGadget(); }

//	called before drawing usually.  sets x,y,w,h optionally.
void UIGadget::OnFormat() {
  if (m_Flags & UIF_CENTER)
    m_X = m_Wnd->W() / 2 - m_W / 2;
}

//	interprets input on this gadget.  this gadget is in focus.
void UIGadget::Process(bool do_keys, bool do_mouse, bool do_user) {
  // do mouse processing on this gadget if and only if no slave processing was done.
  if (UI_input.b1_status == UIMSEBTN_PRESSED && do_mouse) {
    OnMouseBtnDown(UILMSEBTN);
    UI_input.b1_last_status = UIMSEBTN_PRESSED; // DAJ use to latch the UI mouse button
  } else if (UI_input.b1_status == UIMSEBTN_RELEASED && do_mouse) {
    OnMouseBtnUp(UILMSEBTN);
    UI_input.b1_last_status = UIMSEBTN_RELEASED; // DAJ use to latch the UI mouse button
  } else if (UI_input.b1_status == UIMSEBTN_CLICKED && do_mouse) {
    OnMouseBtnDown(UILMSEBTN);
    OnMouseBtnUp(UILMSEBTN);
  } else if (do_keys) {
    // keyboard processing.
    switch (UI_input.key) {
    case KEY_ENTER:
      if ((UI_input.key_status == UIKEY_PRESSED) || (UI_input.key_status == UIKEY_CLICKED)) {
        OnSelect();
      }
      break;
    }

    if (UI_input.key_status == UIKEY_PRESSED) {
      if (TrapKey(UI_input.key))
        OnKeyDown(UI_input.key);
    } else if (UI_input.key_status == UIKEY_RELEASED) {
      UntrapKey(UI_input.key);
      OnKeyUp(UI_input.key);
    } else if (UI_input.key_status == UIKEY_CLICKED) {
      if (TrapKey(UI_input.key))
        OnKeyDown(UI_input.key);
      UntrapKey(UI_input.key);
      OnKeyUp(UI_input.key);
    }
  }

  // run gadget's user process
  if (do_user) {
    OnUserProcess();
  }
}

//	when gadget is selected, this function will AT LEAST be called.
//	typically this function is called by a child class when we want
//	to tell the caller that this gadget was selected for something.
void UIGadget::OnSelect() {
  if (m_MasterGadget && CHECK_FLAG(m_Flags, UIF_NOTIFYMASTERSEL)) {
    m_MasterGadget->OnNotifySelect(this);
  } else {
    UI_output.id = m_ID;
    UI_output.gadget = this;
  }
}

void UIGadget::AttachSlaveGadget(UIGadget *gadget) {
  // attach to list.
  ASSERT(gadget);
  ASSERT(!m_MasterGadget); // slaves can't own each other (sorry, been reading Uncle Tom's Cabin in class)

  gadget->m_Flags |= UIF_SLAVE;
  gadget->m_MasterGadget = this;
  if (m_SlaveGadgets) {
    m_SlaveGadgets->m_NextSlave = gadget;
  }
  gadget->m_PrevSlave = m_SlaveGadgets;
  gadget->m_NextSlave = NULL;
  m_SlaveGadgets = gadget;
}

void UIGadget::DetachSlaveGadget(UIGadget *gadget) {
  if (!gadget) {
    return;
  }

  ASSERT(gadget->m_MasterGadget == this);
  ASSERT(!m_MasterGadget); // slaves can't own each other (sorry, been reading Uncle Tom's Cabin in class)

  if (gadget == m_SlaveGadgets) {
    m_SlaveGadgets = gadget->m_PrevSlave;
  }
  if (gadget == m_CurrentSlave) {
    m_CurrentSlave->LostFocus();
    m_CurrentSlave = gadget->m_PrevSlave ? gadget->m_PrevSlave : gadget->m_NextSlave;
  }
  if (gadget->m_PrevSlave) {
    gadget->m_PrevSlave->m_NextSlave = gadget->m_NextSlave;
  }
  if (gadget->m_NextSlave) {
    gadget->m_NextSlave->m_PrevSlave = gadget->m_PrevSlave;
  }

  gadget->m_MasterGadget = NULL;
  gadget->m_Flags &= (~UIF_SLAVE);
}

///////////////////////////////////////////////////////////////////////////////
//	PRIVATE FUNCTIONS

// called by window system
void UIGadget::LostFocus() {
  this->OnLostFocus(); // should invoke gadget's OnLostFocus function
  if (m_CurrentSlave) {
    ASSERT(m_SlaveGadgets);
    m_CurrentSlave->LostFocus();
    m_CurrentSlave = NULL;
  }
  m_infocus = false;
}

// called by window system
void UIGadget::GainFocus() {
  //	m_LastKey = 0;
  //	m_LastKeyTime = 0.0f;

  m_infocus = true;
  m_CurrentSlave = NULL;
  this->OnGainFocus(); // should invoke gadget's OnGainFocus function
}

void UIGadget::CheckFocusOnSlaves(int mx, int my) {
  UIGadget *slave = m_SlaveGadgets;

  if (CHECK_FLAG(m_Flags, UIF_SLAVE))
    return;

  while (slave) {
    if (PT_IN_GADGET(m_Wnd, slave, mx, my)) {
      if (m_CurrentSlave) {
        m_CurrentSlave->LostFocus();
      }
      m_CurrentSlave = slave;
      m_CurrentSlave->GainFocus();
      break;
    }
    slave = slave->m_PrevSlave;
  }

  if (!slave) {
    if (m_CurrentSlave) {
      m_CurrentSlave->LostFocus();
      m_CurrentSlave = NULL;
    }
  }
}

// used to manage keypresses so repeating isn't quick.
bool UIGadget::TrapKey(int key) {
  float time;
  time = timer_GetTime();

  //	take care of multiple keypresses and key down repeats. handle timing
  // so it feels 'natural'
  if (m_LastKey && m_LastKey == key) {
    if (m_LastKeyCount == 1) {
      if ((time - m_LastKeyTime) < KEYDOWN_FIRST_DELAY)
        return false;
    } else if ((time - m_LastKeyTime) < KEYDOWN_REPEAT_DELAY) {
      return false;
    }
    m_LastKeyCount++;
  } else {
    m_LastKeyCount = 1;
  }

  m_LastKey = key;
  m_LastKeyTime = time;

  return true;
}

void UIGadget::UntrapKey(int key) {
  //	if the key previously pressed went up, then
  //	turn off key repeat management.
  if (key == m_LastKey) {
    m_LastKeyCount = 0;
    m_LastKey = 0;
  }
}

// is this gadget's focus locked?
bool UIGadget::IsLocked() const {
  if (m_Wnd->m_LockedGadget == this)
    return true;
  else
    return false;
}

UIGadget *UIGadget::GetPrevUsableGadget() {
  UIGadget *gadget = this;
  UIGadget *next_gadget = NULL;

  while (gadget->m_Prev) {
    gadget = gadget->m_Prev;
    if (!gadget->IsDisabled()) {
      next_gadget = gadget;
      break;
    }
  }

  return next_gadget;
}

UIGadget *UIGadget::GetNextUsableGadget() {
  UIGadget *gadget = this;
  UIGadget *next_gadget = NULL;

  while (gadget->m_Next) {
    gadget = gadget->m_Next;
    if (!gadget->IsDisabled()) {
      next_gadget = gadget;
      break;
    }
  }

  return next_gadget;
}
