//
// ANSI font modes -- private class
//
class AnsiMode {
  unsigned long fg, fg_curr, fg_base;
  unsigned long bg, bg_curr, bg_base;
  int fg_bright, bg_bright;		// 0=norm, 1=bright, -1=dim
  int underscore;
  int fontface_base, fontsize_base;
  int fontface_curr, fontsize_curr;

  // Change 'brightness' of specified color 'col' by offset amount 'ofs'
  Fl_Color Brightness(unsigned long col, int ofs) {
    int c;
    unsigned long out = 0;
    if ( col >= 0 && col <= 8 )
        { out = (( ofs < 0 ) ? col : (col + 8)); }
    else
    {
	c = (col & 0xff000000) >> 24;
	    c += ofs;
	    c = ( c < 0 ) ? 0 : (( c > 0xff ) ? 0xff : c);
	    out |= ( c << 24 );
	c = (col & 0x00ff0000) >> 16;
	    c += ofs;
	    c = ( c < 0 ) ? 0 : (( c > 0xff ) ? 0xff : c);
	    out |= ( c << 16 );
	c = (col & 0x0000ff00) >> 8;
	    c += ofs;
	    c = ( c < 0 ) ? 0 : (( c > 0xff ) ? 0xff : c);
	    out |= ( c << 8 );
    }
    return(Fl_Color(out));
  }
public:
  AnsiMode(Fl_Color nfg, Fl_Color nbg, int nfontface, int nfontsize) {
    fg_bright = 0;
    bg_bright = 0;
    underscore = 0;
    fg_base = fg_curr = fg = nfg;
    bg_base = bg_curr = bg = nbg;
    fontface_base = fontface_curr = nfontface;
    fontsize_base = fontsize_curr = nfontsize;
  }
  void Fg(unsigned long v) {
    fg = v;
    fg_curr = fg;
    switch ( fg_bright ) {
      case  1: fg_curr = Brightness(fg, 0x40); break;
      case  0: fg_curr = fg; break;
      case -1: fg_curr = Brightness(fg, -0x40); break;
    }
  }
  void Bg(unsigned long v) {
    bg = v;
    bg_curr = bg;
    switch ( bg_bright ) {
      case  1: bg_curr = Brightness(bg, 0x40); break;
      case  0: bg_curr = bg; break;
      case -1: bg_curr = Brightness(bg, -0x40); break;
    }
  }
  void     Fg(Fl_Color v) { Fg((unsigned long)v); }
  void     Bg(Fl_Color v) { Bg((unsigned long)v); }
  Fl_Color Fg() const     { return(Fl_Color(fg_curr)); }
  Fl_Color Bg() const     { return(Fl_Color(bg_curr)); }
  void FgBrightMode(int v) { 
    fg_bright = v;
    Fg(fg);		// apply brightness to current color
  }
  void BgBrightMode(int v) {
    bg_bright = v;
    Bg(bg);		// apply brightness to current color
  }
  void Underscore(int mode) { underscore = mode; }
  int  Underscore() const { return(underscore); }
  void Bold(int mode) { if ( mode ) fontface_curr |= FL_BOLD; else fontface_curr &= ~FL_BOLD; }
  int  Bold() const   { return((fontface_curr & FL_BOLD) ? 1 : 0); }
  void FontFace(int val) { fontface_curr = val; }
  int  FontFace() const { return(fontface_curr); }
  void FontSize(int val) { fontsize_curr = val; }
  int  FontSize() const { return(fontsize_curr); }
  void ReverseVideo(int mode) {
    Fg(mode ? bg_base : fg_base);
    Bg(mode ? fg_base : bg_base);
  }
  void Reset() {
    // Reset all modes to default
    FgBrightMode(0);
    BgBrightMode(0);
    Underscore(0);
    Bold(0);
    Fg(fg_base);
    Bg(bg_base);
    FontFace(fontface_base);
    FontSize(fontsize_base);
  }
  void AnsiColor(int col);
  char* ParseAnsi(char *ss);
  void DecodeEscapeCodes(int escapeCode);
};

// Apply a new ansi color value to the class
void AnsiMode::AnsiColor(int col) {
  switch ( col ) {
    case  0: Reset(); break;			// reset all attributes to defaults
    case  1: Bold(1); FgBrightMode(1); break;	// bold
    case  2: FgBrightMode(-1); break;		// dim
    case  3: break;				// ? (not currently supported)
    case  4: Underscore(1); break;		// underscore on
    case 24: Underscore(0); break;		// underscore off
    case  5: break;				// blink (not currently supported)
    case  6: break;				// ?
    case  7: ReverseVideo(1);  break;		// reverse video on
    case 27: ReverseVideo(0);  break; 		// reverse video off
                                      // RGB EQUIVS (unaffected by colormap changes)
    case 30: Fg(Fl_Color(0));  break; // Fg(Fl_Color(0x00000000)); break;	// blk
    case 31: Fg(Fl_Color(1));  break; // Fg(Fl_Color(0xdd000000)); break;	// red
    case 32: Fg(Fl_Color(2));  break; // Fg(Fl_Color(0x00dd0000)); break;	// grn
    case 33: Fg(Fl_Color(90)); break; // Fg(Fl_Color(0xdd660000)); break;	// orn (docs say brown, some use orn. We'll use orn)
    case 34: Fg(Fl_Color(4));  break; // Fg(Fl_Color(0x0000dd00)); break;	// blu
    case 35: Fg(Fl_Color(5));  break; // Fg(Fl_Color(0xdd00dd00)); break;	// mag
    case 36: Fg(Fl_Color(6));  break; // Fg(Fl_Color(0x00dddd00)); break;	// cyn
    case 37: Fg(Fl_Color(7));  break; // Fg(Fl_Color(0xdddddd00)); break;	// wht
    case 38:					// underscore on, default fg color 
      Underscore(1);
      Fg(Fl_Color(fg_base));
      break;
    case 39:					// underscore on, default fg color 
      Underscore(0);
      Fg(Fl_Color(fg_base));
      break;
                                      // RGB EQUIVS (unaffected by colormap changes)
    case 40: Bg(Fl_Color(0));  break; // Bg(Fl_Color(0x00000000)); break;	// blk
    case 41: Bg(Fl_Color(1));  break; // Bg(Fl_Color(0xdd000000)); break;	// red
    case 42: Bg(Fl_Color(2));  break; // Bg(Fl_Color(0x00dd0000)); break;	// grn
    case 43: Bg(Fl_Color(90)); break; // Bg(Fl_Color(0xdd660000)); break;	// orn (docs say brown, some use orn. We'll use orn)
    case 44: Bg(Fl_Color(4));  break; // Bg(Fl_Color(0x0000dd00)); break;	// blu
    case 45: Bg(Fl_Color(5));  break; // Bg(Fl_Color(0xdd00dd00)); break;	// mag
    case 46: Bg(Fl_Color(6));  break; // Bg(Fl_Color(0x00dddd00)); break;	// cyn
    case 47: Bg(Fl_Color(7));  break; // Bg(Fl_Color(0xdddddd00)); break;	// wht
    case 49: Bg(Fl_Color(bg_base)); break;	// default bg color
  }
}

const unsigned int TerminalColors[]=
{
	// Normal colors.
	0x00000000,	// Black
	0xcd000000,	// Red
	0x00cd0000,	// Green
	0xcdcd0000,	// Yellow
	0x0000cd00,	// Blue
	0xcd00cd00,	// Magenta
	0x00cdcd00,	// Cyan
	0xfaebd700,	// White
	// Bright colors.
	0x40404000,	// Black
	0xff000000,	// Red
	0x00ff0000,	// Green
	0xffff0000,	// Yellow
	0x1e90ff00,	// Blue
	0xff00ff00,	// Magenta
	0x00ffff00,	// Cyan
	0xffffff00	// White
};

enum
{
	NumOfColors=sizeof(TerminalColors) / sizeof(TerminalColors[0])
};

void AnsiMode::DecodeEscapeCodes(int escapeCode)
{

	int boldCode=0;				// Gets set if bold mode gets changed.
	int fgCode=-1;				// Becomes >= 0 for a foreground color change request.
	int bgCode=-1;				// Becomes >= 0 for a background color change request.
	int count;

	switch(escapeCode)
	{
	case  0: Reset();					break;	// Reset all settings to default values
	case  1: Bold(1); boldCode=1;		break;	// Enable bold text
	case 22: Bold(0); boldCode=1;		break;	// Disable bold text
	case  4: Underscore(1);				break;	// Enable underlined text
	case 24: Underscore(0);				break;	// Disable underlined text
	//case  7: ReverseVideo(1);			break;	// Enable reverse video text
	//case 27: ReverseVideo(0);			break;	// Disable reverse video text

	case 30: fgCode=0;					break;	// Black
	case 31: fgCode=1;					break;	// Red
	case 32: fgCode=2;					break;	// Green
	case 33: fgCode=3;					break;	// Yellow
	case 34: fgCode=4;					break;	// Blue
	case 35: fgCode=5;					break;	// Magenta
	case 36: fgCode=6;					break;	// Cyan
	case 37: fgCode=7;					break;	// White
	case 39: Fg(fg_base);				break;	// Set foreground color to default

	case 40: bgCode=0;					break;	// Black
	case 41: bgCode=1;					break;	// Red
	case 42: bgCode=2;					break;	// Green
	case 43: bgCode=3;					break;	// Yellow
	case 44: bgCode=4;					break;	// Blue
	case 45: bgCode=5;					break;	// Magenta
	case 46: bgCode=6;					break;	// Cyan
	case 47: bgCode=7;					break;	// White
	case 49: Bg(bg_base);				break;	// Set background color to default

	default: return;					break;	// Unrecognized codes get ignored
	}

	if(fgCode >= 0)
	{
		// If bold mode is enabled, force bright colors by adding 8.
		fgCode+=(8 * Bold());
		Fg(Fl_Color(TerminalColors[fgCode]));
	}
	else
	if(bgCode >= 0)
	{
		Bg(Fl_Color(TerminalColors[bgCode]));
	}
	else
	if(boldCode)
	{
		// Get the current color.
		Fl_Color fgColor=Fg();
		for(count=0; count < NumOfColors; count++)
		{
			// See if it matches a color in our array.
			if(fgColor == TerminalColors[count])
			{
				// Bold mode forces bright colors.
				if(Bold())
					Fg(Fl_Color(TerminalColors[(count | 8)]));
				// Non-bold mode forces normal colors.
				else
					Fg(Fl_Color(TerminalColors[(count & 7)]));
			}
		}
	}
}


// Parse ansi sequence, leaves ss pointing to first char after ANSI sequence
char* AnsiMode::ParseAnsi(char *ss) {
  long cols[8];		// ansi code buffer
  int tc = 0;
  while ( isdigit(*ss) ) {	// parse all ';' separated ansi values
    int a = strtol(ss, &ss, 10);
    if ( tc < 8 ) { cols[tc++] = a; }
    switch ( *ss ) {
      case '\0': return(ss);	// end of line
      case ';': ++ss; continue;	// ansi value separator
      case 'm': 		// set ansi color (only mode we support)
        ++ss;
	for ( int i=0; i<tc; i++ )
//	  { AnsiColor((int)cols[i]); }
	  { DecodeEscapeCodes((int)cols[i]); }
	return(ss);		// parsed 'm' mode
      case 'K':
        ++ss;
	return(ss);
    }
    return(ss);			// unknown mode
  }
  return(ss);			// incomplete mode string
}
