#include <string.h>
#include <sys/unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xbae/Matrix.h>
#include <Vk/VkApp.h>
#include <Vk/VkErrorDialog.h>
#include "DmStatus.h"
#include "DataManager.h"
#include "DmLog.h"

/* Have to undef OK (which is #define'd by VxWorks stuff), since
VkGenericDialog.h has an enum symbol called "OK" */
#undef OK

#include <Vk/VkErrorDialog.h>
#include <Vk/VkFileSelectionDialog.h>


#define dprintf if (debug) fprintf

#define AudioPlayer "maplay"
#define AudioFile "dmAlertSound.mpg"
#define XBellLevel 50

#define NColumns 3
#define NameColumn 0
#define StateColumn 1
#define MsgColumn 2
#define PeerFileName "peers.txt"
#define WarningSoundFileName "warningSound.mp2"
#define CriticalSoundFileName "criticalSound.mp2"
#define NoLogFileMnem "LogFile?"

const char *DmPeer::name(InAddr peerAddr)
{
  struct in_addr inetAddr;
  inetAddr.s_addr = peerAddr;

  struct hostent *hp;
  char *name;

  if ((hp = gethostbyaddr((char *)&inetAddr, sizeof(inetAddr), AF_INET))
      == 0) {
    if (inet_ntoa(inetAddr))
      name = strdup(inet_ntoa(inetAddr));
    else
      name = strdup("inet_ntoa() failed!");
  }
  else {
    if (hp->h_name)
      name = strdup(hp->h_name);
    else
      name = strdup("Null hostent->h_name!");
  }

  return (const char *)name;
}


DmPeer::DmPeer(InAddr peerAddr, const char *logFile, Boolean isHost)
{
  _peerAddr = peerAddr;
  _name = strdup(DmPeer::name(_peerAddr));

  _logFile = strdup(logFile);

  _state = Unknown;

  if (isHost)
    setState(IsHost);

  logWindow = new DmLogWindow("dmLogWindow", name(), logFile);

  int dummy = 0;

  return;

}


DmPeer::~DmPeer()
{
  free((void *)_name);
  free((void *)_logFile);
}


const char *DmPeer::stateMnem() 
{
  const char *mnem;

  switch (_state) {
  case IsHost:
    mnem = "Host";
    break;

  case Unknown:
    mnem = "Unknown";
    break;

  case Error:
    mnem = "Down";
    break;

  case NotConnected:
    mnem = "Not Connected";
    break;

  case Booting:
    mnem  = "Booting";
    break;

  case Init:
    mnem = "Init Phase 1";
    break;

  case Init2:
    mnem = "Initializing";
    break;
		
  case InitSent:
    mnem = "Init Sent";
    break;

  case InitRcvd:
    mnem = "Init Rcvd";
    break;

  case Connected:
    mnem = "Connected";
    break;

  default:
    mnem = "????";
  }

  return mnem;
}



XtResource DmStatus::_resources[] = 
{
  {  
    "criticalBackground",
    "CriticalBackground",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(DmStatus *, _criticalBackground),
    XmRString,
    "Red"
  },

  {  
    "criticalForeground",
    "CriticalForeground",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(DmStatus *, _criticalForeground),
    XmRString,
    "White"
  },

  {  
    "warningBackground",
    "WarningBackground",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(DmStatus *, _warningBackground),
    XmRString,
    "Yellow"
  },

  {  
    "warningForeground",
    "WarningForeground",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(DmStatus *, _warningForeground),
    XmRString,
    "Black"
  },

  {  
    "okayBackground",
    "OkayBackground",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(DmStatus *, _okayBackground),
    XmRString,
    "Green"
  },

  {  
    "okayForeground",
    "OkayForeground",
    XtRPixel,
    sizeof(Pixel),
    XtOffset(DmStatus *, _okayForeground),
    XmRString,
    "Black"
  }
};


DmStatus::DmStatus(const char *objName, Widget parent, 
		   const char *configDir,
		   const char *logFileDir)
{
  _baseWidget = 
    XtVaCreateWidget(objName, xmFormWidgetClass, parent, NULL);

  installDestroyHandler();

  getResources(_resources, XtNumber(_resources));

  _logFileDir = strdup(logFileDir);

  // const int initialRows = 1;
  const int initialRows = 0;

  _peerListWidget = 
    XtVaCreateWidget("peerList", xbaeMatrixWidgetClass,
		     _baseWidget,
		     XmNrows, initialRows,
		     XmNcolumns, NColumns,
		     XmNtopAttachment, XmATTACH_FORM,
		     XmNleftAttachment, XmATTACH_FORM,
		     XmNrightAttachment, XmATTACH_FORM,
		     //		     XmNbottomAttachment, XmATTACH_POSITION,
		     // XmNbottomPosition, 50,
		     NULL);

  XtAddCallback(_peerListWidget, XmNdefaultActionCallback, selectCallback,
		(XtPointer )this);

  XtAddCallback(_peerListWidget, XmNenterCellCallback, enterCellCallback,
		(XtPointer )this);


  _rowsFilled = 0;

  XtManageChild(_peerListWidget);

  _ackButton = 
    XtVaCreateManagedWidget("ackButton", xmPushButtonWidgetClass, _baseWidget,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, _peerListWidget,
			    XmNleftAttachment, XmATTACH_FORM,
			    NULL);

  XtAddCallback(_ackButton, XmNactivateCallback, 
		&DmStatus::alarmAckCallback, (XtPointer )this);
		

  _peerSeqNo = -1;

  _configDir = strdup(configDir);

  char peerFileName[256];
  sprintf(peerFileName, "%s/%s", _configDir, PeerFileName);

  if (loadPeers(peerFileName) == -1) {
  }

  _alarmMgr = new AlarmMgr(_peerListWidget, 1000,
			   _configDir,
			   _okayForeground, _okayBackground,
			   _warningForeground, _warningBackground,
			   _criticalForeground, _criticalBackground);

  _alarmRaised = -111;
  periodicUpdate();

  setStatusUpdatePeriod(1000);
}


DmStatus::~DmStatus() 
{
  free((void *)_configDir);
  free((void *)_logFileDir);
}


void DmStatus::setStatusUpdatePeriod(int millisec)
{
  _updatePeriod = millisec;
  _updateTimer = XtAppAddTimeOut(theApplication->appContext(), _updatePeriod,
				 &DmStatus::updateTimerCallback, 
				 (XtPointer )this);
}


void DmStatus::periodicUpdate()
{
  Boolean debug = False;

  if (_alarmRaised != _alarmMgr->alarmRaised()) {

    _alarmRaised = _alarmMgr->alarmRaised();

    if (_alarmRaised) {
      XtSetSensitive(_ackButton, True);
    }
    else {
      XtSetSensitive(_ackButton, False);
    }
  }

  const int MaxPeers = 32;
  DmPeerSts visiblePeer[MaxPeers];
  Nat32 nPeers = MaxPeers;
  Int32 peerSeqNo;

  dprintf(stderr, "dm_peer_status()...\n");
  if ((peerSeqNo = dm_peer_status(visiblePeer, &nPeers)) != _peerSeqNo) {
    _stateChanged = True;
    _peerSeqNo = peerSeqNo;
  }
  else
    _stateChanged = False;

  _peerSeqNo = peerSeqNo;
  dprintf(stderr, "updatePeerList()...\n");
  updatePeerList(visiblePeer, nPeers);
  dprintf(stderr, "updatePeerListDisplay()...\n");
  updatePeerListDisplay();
  dprintf(stderr, "done\n");
}


void DmStatus::updatePeerList(DmPeerSts *visiblePeer, int nVisiblePeers)
{
  int j;

  DmPeer *peer;

  /* Initialize connect status to "Unknown", then fill in statuses of
     visible peers in subsequent loop. */
  for (j = 0; j < _peers.size(); j++) {
    _peers.get(j, &peer);
    peer->setState(DmPeer::Unknown);
  }

  /* Look for peers in current "visible" list. If peer is not yet in
     list, add it. Update connect status of list entries. */
  for (int i = 0; i < nVisiblePeers; i++) {

    Boolean inList = False;

    // Try to find visible peer in list
    for (j = 0; j < _peers.size(); j++) {
      _peers.get(j, &peer);

      if (peer->address() == visiblePeer[i].peer_addr) {
	inList = True;
	break;
      }
    }
    if (!inList) {
      // Not yet in list; add it
      const char *logFile = 
	defaultLogFile(DmPeer::name(visiblePeer[i].peer_addr));

      peer = new DmPeer(visiblePeer[i].peer_addr, logFile);

      _peers.add(&peer);
    }

    // Update connect status
    peer->setState((DmPeer::State )visiblePeer[i].peer_state);
  }
}


void DmStatus::updatePeerListDisplay()
{
  if (_peers.size() < _rowsFilled) {
    // Programming error!
    fprintf(stderr, "DmStatus::updateListDisplay() - bad row count!\n");
    return;
  }

  displayNewPeers();
  updateState();
  updateMsgCounts();
}


void DmStatus::displayNewPeers()
{
  if (_peers.size() > _rowsFilled) {
    int nNewRows = _peers.size() - _rowsFilled;
    // Fill in list widget data array
    String *cells = (String *)XtMalloc(nNewRows * sizeof(String) * NColumns);
    int i;

    for (i = 0; i < nNewRows; i++) {

      DmPeer *peer;
      _peers.get(i + _rowsFilled, &peer);

      cells[i*NColumns + NameColumn] = strdup(peer->name());
      cells[i*NColumns + StateColumn] = strdup(peer->stateMnem());
      cells[i*NColumns + MsgColumn] = strdup(" ");
    }

    XbaeMatrixAddRows(_peerListWidget, _rowsFilled, cells, NULL, NULL, 
		      nNewRows);

    XtFree((char *)cells);

    _rowsFilled += nNewRows;
  }
}


void DmStatus::updateState() 
{
  if (!_stateChanged)
    return;

  for (int i = 0; i < _peers.size(); i++) {
    DmPeer *peer;
    _peers.get(i, &peer);

    char stateMnem[32];
    strcpy(stateMnem, peer->stateMnem());

    XbaeMatrixSetCell(_peerListWidget, i, StateColumn, 
		      (const String )stateMnem);

    switch (peer->state()) {
    case DmPeer::Connected:
    case DmPeer::IsHost:
      _alarmMgr->removeCell(i, StateColumn);
      break;

    case DmPeer::Booting:
    case DmPeer::Init:
    case DmPeer::Init2:
    case DmPeer::InitSent:
    case DmPeer::InitRcvd:
      _alarmMgr->addCell(i, StateColumn, AlarmMgr::Warning);
      break;

    default:
      _alarmMgr->addCell(i, StateColumn, AlarmMgr::Critical);
    }
  }
}


void DmStatus::updateMsgCounts() {

  for (int row = 0; row < _rowsFilled; row++) {

    DmPeer *peer;
    _peers.get(row, &peer);

    char countStr[16];
    
    if (!peer->logWindow->logFileFound()) {
      // Can't find log file for this peer
      char *currText = XbaeMatrixGetCell(_peerListWidget, row, MsgColumn);

      if (!strcmp(currText, NoLogFileMnem))
	// Already showing "no logfile" message; no need to update text
	continue;

      strcpy(countStr, NoLogFileMnem);

      // Highlight this peer's MsgColumn
      XbaeMatrixSetCell(_peerListWidget, row, MsgColumn, 
			(const String )countStr);

      _alarmMgr->addCell(row, MsgColumn, AlarmMgr::Warning);
      continue;
    }


    int msgCount = 
      peer->logWindow->nWarningMsgs() + 
      peer->logWindow->nCriticalMsgs();

    if (msgCount != peer->msgCount()) {

      // Update count display
      peer->setMsgCount(msgCount);

      sprintf(countStr, "%d", msgCount);

      XbaeMatrixSetCell(_peerListWidget, row, MsgColumn, 
			(const String )countStr);

      if (msgCount) {
	_alarmMgr->addCell(row, MsgColumn, AlarmMgr::Critical);
      }
      else {
	_alarmMgr->removeCell(row, MsgColumn);
      }
    }
  }
}



int DmStatus::loadPeers(const char *peerFileName) 
{
  struct hostent *hostEntry;
  DmPeer *peer;
  char *logFile;

  // Load self (i.e., this host) into peer list
  char hostName[256];
  gethostname(hostName, sizeof(hostName));

  hostEntry = gethostbyname(hostName);

  if (!hostEntry) {
    fprintf(stderr, "Host \"%s\" not found!\n", *hostName);
    return -1;
  }

  logFile = (char *)defaultLogFile(hostName);

  peer = 
    new DmPeer(((struct in_addr *)(hostEntry->h_addr_list[0]))->s_addr, 
	       logFile, True);

  _peers.add(&peer);
  
  // Read peer and logfile names from file
  char errorBuf[512];

  FILE *fp;
  if ((fp = fopen(peerFileName, "r")) == 0) {

    sprintf(errorBuf, 
	    "Couldn't open peer file \"%s\"; getting peer list from DM, "
	    "using default logfile names", 
	    peerFileName);

    theErrorDialog->postAndWait(errorBuf);
    return -1;
  }

  char buf[256];
  char line[256];
  int lineNo = 0;

  Boolean error = False;

  while (fgets(line, sizeof(line), fp)) {
    lineNo++;
    strcpy(buf, line);

    // Strip off newline
    if (buf[strlen(buf) - 1] == '\n') {
      buf[strlen(buf) - 1] = '\0';
    }

    char *peerName;
    char *token;

    // Check for comment character
    if ((token = strchr(buf, '#')) != 0) {
      *token = '\0';
    }

    int nTokens = 0;
    char *ptr = buf;
    // Parse host and logfile names
    while ((token = strtok(ptr, " \t")) != 0) {
      ptr = 0;

      switch (nTokens) {
      case 0:
	peerName = token;
	break;
      case 1:
	logFile = token;
	break;
      }
      nTokens++;
    }

    switch (nTokens) {

    case 0:
      // Blank line
      continue;
      break;

    case 1:
      logFile = defaultLogFile(peerName);
      break;

    case 2:
      // Okay
      break;

    default:
      sprintf(errorBuf, "Bad syntax in peer file %s, line %d:\n    %s\n"
	      "Proper syntax for each line is \"hostname  logfileName\"", 
	      peerFileName, lineNo, line);

      theErrorDialog->postAndWait(errorBuf);
      error = True;
      break;

    }

    hostEntry = gethostbyname(peerName);

    if (!hostEntry) {
      fprintf(stderr, "Host \"%s\" not found\n", *peerName);
      continue;
    }

    peer = 
      new DmPeer(((struct in_addr *)(hostEntry->h_addr_list[0]))->s_addr, 
		 logFile);

    _peers.add(&peer);

  }

  fclose(fp);

  if (error)
    return -1;
  else
    return 0;
}


void DmStatus::showPeerDetail(int index) 
{
  Boolean debug = False;

  DmPeer *peer;
  if (_peers.get(index, &peer) == -1) {
    fprintf(stderr, "DmStatus::showPeerDetail() - bad index!\n");
    return;
  }
  peer->logWindow->show();
  peer->logWindow->raise();

}


const char *DmStatus::defaultLogFile(const char *peerName)
{
  static char fileName[256];
  sprintf(fileName, "%s/dm.log.", _logFileDir);
  
  char *ptr = strchr(peerName, '.');

  if (ptr) {
    strncat(fileName, peerName, ptr - peerName);
  }
  else
    strcat(fileName, peerName);

  return fileName;
}


void DmStatus::enterCellCallback(Widget w,
				 XtPointer clientData,
				 XtPointer callData)
{
  Boolean debug = False;

  XbaeMatrixEnterCellCallbackStruct *cbs = 
    (XbaeMatrixEnterCellCallbackStruct *)callData;

  cbs->doit = False;

  dprintf(stderr, "DmStatus::enterCellCallback() - row=%d, col=%d\n",
	  cbs->row, cbs->column);

  if (cbs->column == MsgColumn) {
    DmStatus *obj = (DmStatus *)clientData;
    obj->showPeerDetail(cbs->row);
  }
}


void DmStatus::selectCallback(Widget w,
			      XtPointer clientData,
			      XtPointer callData)
{
  Boolean debug = False;

  XbaeMatrixDefaultActionCallbackStruct *cbs = 
    (XbaeMatrixDefaultActionCallbackStruct *)callData;


  dprintf(stderr, "DmStatus::selectCallback() - row=%d, col=%d\n",
	  cbs->row, cbs->column);

  DmStatus *obj = (DmStatus *)clientData;
  obj->showPeerDetail(cbs->row);
}



void DmStatus::alarmAckCallback(Widget w,
				XtPointer clientData,
				XtPointer callData)
{
  Boolean debug = False;

  DmStatus *obj = (DmStatus *)clientData;
  obj->_alarmMgr->acknowledge();
}



void DmStatus::updateTimerCallback(XtPointer clientData, XtIntervalId *id)
{
  MBool debug = False;
  dprintf(stderr, "DmStatus::timerCallback()\n");
  
  DmStatus *obj = (DmStatus *)clientData; 
  obj->_updateTimer = XtAppAddTimeOut(theApplication->appContext(), 
				      obj->_updatePeriod, 
				      &DmStatus::updateTimerCallback, 
				      (XtPointer )obj);

  obj->periodicUpdate();
}


AlarmMgr::AlarmMgr(Widget matrixWidget, int updateMillisec,
		   const char *configDir,
		   Pixel okayForeground,
		   Pixel okayBackground,
		   Pixel warningForeground, 
		   Pixel warningBackground,
		   Pixel criticalForeground, 
		   Pixel criticalBackground)
{
  _matrixWidget = matrixWidget;
  _updatePeriod = updateMillisec;
  _actionPhase = 0;
  _timer = 0;

  _okayForeground = okayForeground;
  _okayBackground = okayBackground;
  _warningForeground = warningForeground;
  _warningBackground = warningBackground;
  _criticalForeground = criticalForeground;
  _criticalBackground = criticalBackground;

  _audio = False;

#ifdef USE_SOUNDS
  // Determine if audio player and files are present
  _audio = True;
  struct stat statBuf;

  sprintf(_audioPlayer, "%s/%s", configDir, AudioPlayer);
  sprintf(_audioFile, "%s/%s", configDir, AudioFile);

  char errorBuf[512];
  strcpy(errorBuf, "");

  if (stat(_audioPlayer, &statBuf) == -1) {
    sprintf(errorBuf, "Couldn't find audio player \"%s\"; will use beep", 
	    _audioPlayer);

    theErrorDialog->postAndWait(errorBuf);
    _audio = False;
  }

  if (stat(_audioFile, &statBuf) == -1) {
    sprintf(errorBuf, "Couldn't find audio file \"%s\"; will use beep", 
	    _audioFile);

    theErrorDialog->postAndWait(errorBuf);
    _audio = False;
  }
#endif

}


AlarmMgr::~AlarmMgr()
{
}


void AlarmMgr::addCell(int row, int column, AlarmLevel level)
{
  AlarmedCell *cell = new AlarmedCell(row, column, level);
  _alarmedCellList.append(cell);

  XbaeMatrixSetCellColor(_matrixWidget, row, column, foreground(level));
  XbaeMatrixSetCellBackground(_matrixWidget, row, column, background(level));

  raise();
}


int AlarmMgr::removeCell(int row, int column)
{
  SListIter<AlarmedCell *> cells(_alarmedCellList);
  cells.reset();
  AlarmedCell **cell;
  while (cell = cells()) {
    if ((*cell)->row == row && (*cell)->column == column) {

      XbaeMatrixSetCellColor(_matrixWidget, row, column,
			     _okayForeground);

      XbaeMatrixSetCellBackground(_matrixWidget, row, column,
				  _okayBackground);

      cells.remove();
      return 0;
    }
  }

  // Specified cell not found
  return -1;
}
 

int AlarmMgr::setCellLevel(int row, int column, AlarmLevel level)
{
  SListIter<AlarmedCell *> cells(_alarmedCellList);
  cells.reset();
  AlarmedCell **cell;
  while (cell = cells()) {
    if ((*cell)->row == row && (*cell)->column == column) {

      (*cell)->level = level;
      return 0;

      cells.remove();
      return 0;
    }
  }

}


void AlarmMgr::raise()
{
  _alarmRaised = True;

  if (!_timer)
    _timer = XtAppAddTimeOut(theApplication->appContext(), _updatePeriod,
			     &AlarmMgr::timerCallback, 
			     (XtPointer )this);
}
 

void AlarmMgr::acknowledge()
{
  _alarmRaised = False;

  // Set alarmed cells to appropriate color
  SListIter<AlarmedCell *> cells(_alarmedCellList);
  cells.reset();
  AlarmedCell **cell;
  while(cell = cells()) {

    XbaeMatrixSetCellColor(_matrixWidget, (*cell)->row, (*cell)->column,
			   foreground((*cell)->level));

    XbaeMatrixSetCellBackground(_matrixWidget, (*cell)->row, (*cell)->column,
				background((*cell)->level));

    // Mark cell as "old", so won't flash anymore
    (*cell)->newAlarm = False;
  }

  // Remove timer
  if (_timer) {
    XtRemoveTimeOut(_timer);
    _timer = 0;
  }
}


void AlarmMgr::doActions()
{
  if (!_alarmRaised)
    return;

  Pixel foreground, background;

  if (!_actionPhase) {

    // Do audio actions on every other cycle
    doAudio();

    foreground = _criticalForeground;
    background = _criticalBackground;

  }
  else {
    foreground = _okayForeground;
    background = _okayBackground;
  }

  // Go through list of alarmed cells; toggle color of critical new ones
  SListIter<AlarmedCell *> cells(_alarmedCellList);
  cells.reset();
  AlarmedCell **cell;
  while(cell = cells()) {
    if ((*cell)->newAlarm && (*cell)->level == Critical) {

      XbaeMatrixSetCellColor(_matrixWidget, (*cell)->row, (*cell)->column,
				foreground);

      XbaeMatrixSetCellBackground(_matrixWidget, (*cell)->row, (*cell)->column,
				background);
    }
  }

  // Toggle phase
  _actionPhase = !_actionPhase;
}


void AlarmMgr::doAudio()
{

  // Sound is disabled for now!
  return;

  if (highestNewLevel() == Okay)
    return;

  if (_audio) {
    pid_t childPid = fork();
    switch (childPid) {
    case 0:
      // Child process
      execlp(_audioPlayer, _audioPlayer, _audioFile, 0);

      // If we get here, execlp() failed
      perror("execlp() failed");
      exit(1);
      break;

    case -1:
      perror("fork() failed");
      exit(1);

    default:
      // Parent
      wait(0);
      break;
    }
  }
  else {
    // Missing audio player and/or sound file; just beep
    XBell(theApplication->display(), XBellLevel);
    return;
  }
}


AlarmMgr::AlarmLevel AlarmMgr::highestNewLevel()
{
  AlarmLevel level = Okay;

  // Go through list of alarmed cells; find highest alarm level
  SListIter<AlarmedCell *> cells(_alarmedCellList);
  cells.reset();
  AlarmedCell **cell;
  while(cell = cells()) {
    if ((*cell)->level > level)
      level = (*cell)->level;
  }

  return level;
}


Boolean AlarmMgr::alarmRaised()
{
  return _alarmRaised;
}


void AlarmMgr::timerCallback(XtPointer clientData, XtIntervalId *id)
{
  MBool debug = False;
  dprintf(stderr, "DmStatus::timerCallback()\n");
  
  AlarmMgr *obj = (AlarmMgr *)clientData; 
  obj->_timer = XtAppAddTimeOut(theApplication->appContext(), 
				obj->_updatePeriod, 
				&AlarmMgr::timerCallback, 
				(XtPointer )obj);

  obj->doActions();
}

Pixel AlarmMgr::foreground(AlarmLevel level)
{
  Pixel color;

  switch (level) {
  case Okay:
    color = _okayForeground;
    break;

  case Warning:
    color = _warningForeground;
    break;

  case Critical:
    color = _criticalForeground;
    break;
  }

  return color;
}


Pixel AlarmMgr::background(AlarmLevel level)
{
  Pixel color;

  switch (level) {
  case Okay:
    color = _okayBackground;
    break;

  case Warning:
    color = _warningBackground;
    break;

  case Critical:
    color = _criticalBackground;
    break;
  }

  return color;
}

