// =========================================================================
// TimeLine generator program - Version #104.
// Kapitany Csaba - 2007
// 
// Compile: cc TimeLine104.c
// =========================================================================

/* Example Parameter file :

#
# Kapitány Csaba 'TimeLine' paraméter fájl
#
# Felépítése:
# 
# # = megjegyzés, ha az első oszlopban van a # jel.
# ; = mezőelválasztó.
#
# 1. oszlop = Születési év (Eleje)
# 2. oszlop = Halálozás éve (Vége), ha van ilyen; egyébként kimarad a 2. oszlop.
# 3. oszlop = Név (Szöveg)

1685; 1750; J.S.Bach;
1699; Kantáta 51.;
1770; 1850; Beethoven;

*/

// =========================================================================
// Include section:
// =========================================================================

#include <stdio.h>
#include <string.h>
#include <time.h>

// =========================================================================
// Define section:
// =========================================================================

#define LEN_MAX     100   // Filename's max. length
#define L_RECORD    600   // Input Record buffer size
#define L_FIELD     300   // Input Field buffer size
#define TEXT_SIZE   30000 // Number of Entries in Input file
#define LINE_SIZE   50000 // Number of Entries in "sTimeLine" table
#define LEVEL_SIZE  500   // Max. Number of levels

#define NULL_FLAG   0
#define BORN_FLAG   1
#define DEATH_FLAG  2
#define EVENT_FLAG  4

#define SEPARATOR   ";"   // Field separator
#define COMMENT     '#'   // Comment sign in the 1st column

#define TAB_PIX     250   // Table size (Depends on the maximum word length of the text)
#define PAD_PIX      40   // Horizontal drawing line length
#define VER_PIX      30   // Vertical size (Depends on the maximum text line length)

// =========================================================================
// Global variable section:
// =========================================================================

static  FILE    *pFileI ;
static  char    *pcToken;
static  char     acInpFile[ LEN_MAX+1 ] = { "time_line.txt" };

static  unsigned char acBuff[ L_RECORD+1 ];
static  unsigned char acEventText[ L_FIELD+1 ];

static  int     iBorn;
static  int     iDeath;
static  int     iSerialTextTable;
static  int     iSerialTimeLine;

typedef struct tTimeLine
{
  char  cFlag;        // Begin/Born, End/Death, Event, Null
  int   iYear;        // Year of the event-line
  int   iPointer;     // Pointer to acTextTable (line number)
  int   iLevel;       // Drawing level
} TimeLine;

static  char acTextTable[ TEXT_SIZE ][ L_FIELD+1 ];
static  TimeLine sTimeLine[ LINE_SIZE+1 ];
static  int iLevel[ LEVEL_SIZE+1 ]; // Auxiliary counter for levels

// =========================================================================
// Function declaration section:
// =========================================================================

static int fCountLevelNumber();
static int fAlpha( TimeLine *s1, TimeLine *s2 );

// =========================================================================
// HTML template section:
// =========================================================================

#define HTML_FILE_HEADER "\
<html>\n\
<head>\n\
\n\
\n\
  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-2\">\n\
  <meta http-equiv=\"description\" content=\"Csaba Kapitány, Kapitany, Homepage, Website\">\n\
  <meta http-equiv=\"keywords\" content=\"Csaba Kapitány, Csaba, Kapitány, Kapitány Csaba, Csaba Kapitany, Kapitany, Early music, Baroque\">\n\
\n\
  <meta name=\"author\" content=\"Csaba Kapitány\">\n\
  <meta name=\"description\" content=\"Csaba Kapitány, Kapitany, Homepage, Website\">\n\
  <meta name=\"keywords\" content=\"Csaba Kapitány, Csaba, Kapitány, Kapitány Csaba, Csaba Kapitany, Kapitany, Early music, Baroque\">\n\
  <meta name=\"generator\" content=\"Manually on Linux for Netscape\">\n\
\n\
  <title> Csaba Kapitány's Music History Time Line </title>\n\
\n\
</head>\n\
\n\
<body lang=EN-US BGCOLOR=\"#F8DDBA\" LINK=\"#009900\" VLINK=\"#FF0000\" ALINK=\"#000088\">\n\
<BR>\n\
<h1><center><FONT COLOR=\"#800000\">\n\
Csaba Kapitány's Music History Time Line\n\
</FONT></center></h1>\n\
"

// ------------------------------------------------------------------------

#define HTML_FILE_FOOTER "\
<BR><HR>\n\
\n\
<!-- Author's logo: -->\n\
\n\
<h3>\n\
 <i><font SIZE=-1> Last updated: %s\n\
 , EmaiI: <img title=\"go_robot_here@gmail.com\" src=\"logo_100.gif\"\n\
 alt=\"go_robot_here@gmail.com\" style=\"height: 24px; width: 226px;\"\n\
 align=\"middle\"></font></i>\n\
</h3>\n\
\n\
</body>\n\
</html>\n\
"

// ------------------------------------------------------------------------

#define HTML_TABLE_BEGIN "\
<table border=0 cellspacing=0 cellpadding=0><tr align=left>\n\
"

// ------------------------------------------------------------------------

#define HTML_TABLE_END "\
</tr></table>\n\
"

// ------------------------------------------------------------------------

#define HTML_ITEM_TEXT "\
  <td><table border=1 cellpadding=0 cellspacing=0 style='border-collapse:collapse'>\n\
      <tr><td width=%d bgcolor=\"#%s\"> %d: %c%s </td></tr></table></td>\n\
"

// ------------------------------------------------------------------------

#define HTML_DRAW_HORIZONTAL_VISIBLE "\
  <td><table width=%d border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=\"#%s\" height=1></td></tr></table></td>\n\
"

// ------------------------------------------------------------------------

#define HTML_DRAW_HORIZONTAL_INVISIBLE "\
  <td><table width=%d border=0 cellpadding=0 cellspacing=0><tr><td></td></tr></table></td>\n\
"

// ------------------------------------------------------------------------

#define HTML_DRAW_VERTICAL_BORN "\
  <td height=%d valign=bottom><table border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=\"#%s\" height=%d width=1><br></td></tr></table></td>\n\
"

// ------------------------------------------------------------------------

#define HTML_DRAW_VERTICAL_DEATH "\
  <td height=%d valign=top><table border=0 cellpadding=0 cellspacing=0><tr><td bgcolor=\"#%s\" height=%d width=1><br></td></tr></table></td>\n\
"

// ------------------------------------------------------------------------

#define HTML_DRAW_VERTICAL_INVISIBLE "\
  <td height=%d valign=bottom><table border=0 cellpadding=0 cellspacing=0><tr><td height=1 width=1><br></td></tr></table></td>\n\
"

// =========================================================================
// Function declarations:
// =========================================================================

static int fDisplayAllField( int iNumberOfFields );
static int fClearAllField();
static int fNewTable( char *pacDisplay );
static char *fColor( int iLevel );
static char *fBgColor( char cFlag );

// =========================================================================
// Start of Main Program
// =========================================================================

int main( argc, argv )
int argc;
char **argv;
{
  time_t unix_time;

  if( argc > 1 )
  {
      strncpy( acInpFile, argv[1], LEN_MAX ); /* non default name */
  }

  if(( pFileI = fopen( acInpFile , "r" )) == NULL )
  {
      printf( "\nTimeLine parameter file '%s' is failed to open.\n\n", acInpFile );

      exit( 1 );
  }

  // Read the parameter file into TextTable:

  fClearAllField();

  while( fgets( acBuff, L_RECORD, pFileI ) != NULL ) 
  {   /* `fgets` reads until CR,LF but max. L_RECORD */

    if(( acBuff[0] == COMMENT )
    || ( acBuff[0] == '\0' )
    || ( acBuff[0] == '\r' )
    || ( acBuff[0] == '\n' ))
    {
        continue; // Comment in the 1st column, or Empty line
    }

    if(( pcToken = strtok( acBuff, SEPARATOR )) == NULL ) // Read 1st field in
    {
        printf( "Missing 1st field in '%s'!<BR>\n", acBuff );
        continue;
    }

    iBorn = atoi( pcToken ); // Field #1 -> Born (beginning)

    if(( pcToken = strtok( NULL, SEPARATOR )) == NULL ) // Read 2nd field in
    {
        printf( "Missing 2nd field in '%s'!<BR>\n", acBuff );
        continue;
    }

    iDeath = atoi( pcToken ); // Field #2 -> Death (end)

    if( iDeath != 0 ) // This is a "from-to" entry, otherwise the 2nd field was the text itself
    {
      if(( pcToken = strtok( NULL, SEPARATOR )) == NULL ) // Read 3rd field in
      {
          printf( "Missing 3rd field in '%s'!<BR>\n", acBuff );
          continue;
      }
    }

    strncpy( acEventText, pcToken, L_FIELD ); // Field #3 -> Text to display
    acEventText[L_FIELD] = '\0'; // close text anyways

    if(( acEventText[0] == '\0' )
    || ( acEventText[0] == '\r' )
    || ( acEventText[0] == '\n' )) /* Empty text field */
    {
        printf( "Empty text field in '%s'!<BR>\n", acBuff );
        continue;
    }

    // Fill acTextTable and sTimeLine:

    if(( iDeath != 0 )&&( iDeath < iBorn )) // Validity check
    {
        printf( "'Death=%d' is less than 'Born=%d' at line#%d!<BR>\n",iDeath, iBorn, iSerialTextTable );
        iDeath = 0; // clear wrong data
    }

    strncpy( acTextTable[iSerialTextTable], acEventText, L_FIELD );

    if( iDeath == 0 ) // Just an event
    {
      sTimeLine[iSerialTimeLine].cFlag = EVENT_FLAG;
    }
    else // End of something
    {
      sTimeLine[iSerialTimeLine].cFlag = BORN_FLAG;
    }
    sTimeLine[iSerialTimeLine].iYear = iBorn;
    sTimeLine[iSerialTimeLine].iPointer = iSerialTextTable;

    iSerialTimeLine++;

    if( iDeath > 0 )
    {
      sTimeLine[iSerialTimeLine].cFlag = DEATH_FLAG;
      sTimeLine[iSerialTimeLine].iYear = iDeath;
      sTimeLine[iSerialTimeLine].iPointer = iSerialTextTable;

      iSerialTimeLine++; // second line in sTimeLine
    }

    iSerialTextTable++;

  } // while

  // Input Data are stored. Quick sort must be performed on the 'sTimeLine'

	qsort( (char*)sTimeLine, iSerialTimeLine, sizeof(TimeLine), fAlpha );

  // Set the drawing level along the 'sTimeLine':

  fCountLevelNumber();

  printf( "%s", HTML_FILE_HEADER );

  fDisplayAllField( 3 );

  printf( HTML_FILE_FOOTER, ctime(&unix_time) );

  fclose( pFileI );

  printf( "\n" );

  exit( 0 );
}

// =========================================================================
// Set the drawing level along the 'sTimeLine':
// =========================================================================

static int fCountLevelNumber()
{
  int i, j, iLev;

  for( i=0; i < LINE_SIZE; i++ )
  {
    if( sTimeLine[i].cFlag == NULL_FLAG ) break; // No more entry

    for( iLev=1; iLev < LEVEL_SIZE; iLev++ ) // Get the 1st free level
    {
      if( iLevel[iLev] == 0 ) break;
    }

    if( sTimeLine[i].cFlag == BORN_FLAG )
    {
      sTimeLine[i].iLevel = iLev;  // actual level
      iLevel[iLev]        = iLev;  // Level is occupied
    }
    else if( sTimeLine[i].cFlag == DEATH_FLAG )
    {
      for( j=0; j < i; j++ ) // Search for the 'Born' entry for this 'Death'
      {
        if( sTimeLine[i].iPointer == sTimeLine[j].iPointer )
        {
          sTimeLine[i].iLevel = sTimeLine[j].iLevel; // same level
          iLevel[sTimeLine[j].iLevel] = 0;           // Level becomes free
          break;
        }
      }
    }
    else // EVENT_FLAG
    {
      sTimeLine[i].iLevel  = 0;  // no level for single events
    }
  }

  return( 0 );
}

// =========================================================================
// fClearAllField:
// =========================================================================

static int fClearAllField()
{
  int i;

  iSerialTextTable = 0;
  iSerialTimeLine  = 0;
  
  for( i=0; i < TEXT_SIZE; i++ )
  {
    acTextTable[ i ][ 0 ] = '\0'; // "" = Empty text
  }

  for( i=0; i < LINE_SIZE; i++ )
  {
    sTimeLine[i].cFlag = NULL_FLAG;
    sTimeLine[i].iYear = 0;
    sTimeLine[i].iPointer = 0;
    sTimeLine[i].iLevel = 0;
  }

  for( i=0; i < LEVEL_SIZE; i++ )
  {
    iLevel[i] = 0; // Level Zero
  }

  return( 0 );
}

// =========================================================================
// fDisplayAllField:
// =========================================================================

static int fDisplayAllField( int iNumberOfFields )
{
  int  i, iWith, iHeight, iPadding, iLev, iMaxLev, iVertSize;
  char cLifeStatus;

  /* Test Begin:

  printf( "<BR><BR>Test sTimeLine:<BR>\n" );

  for( i=0; i < LINE_SIZE; i++ )
  {
    if ( sTimeLine[i].cFlag == NULL_FLAG ) break; // No more entry

    printf( "<BR>\n" );
    printf( "cFlag='%d'\n", sTimeLine[i].cFlag );
    printf( "iYear='%d'\n", sTimeLine[i].iYear );
    printf( "iPointer='%d'\n", sTimeLine[i].iPointer );
    printf( "iLevel='%d'\n", sTimeLine[i].iLevel );

    printf( "acTextTable='%s'\n", acTextTable[ sTimeLine[i].iPointer ] );
  }

  printf( "<BR><BR>\n" );

  Test End */

  iWith    = TAB_PIX; // Table size (Depends on the maximum word length of the text)
  iPadding = PAD_PIX; // Horizontal drawing line length

  for( iLev=0; iLev < LEVEL_SIZE; iLev++ )
  {
    iLevel[iLev] = 0; // Reset Level Working Counter
  }

  for( i=0; i < LINE_SIZE; i++ )
  {
    if( sTimeLine[i].cFlag == NULL_FLAG ) break; // No more entry

    // Count the box's vertical size (Depends on the maximum text line length):

    iHeight = VER_PIX * (((strlen(acTextTable[ sTimeLine[i].iPointer ]))/90)+1);

    if( sTimeLine[i].cFlag == DEATH_FLAG ) cLifeStatus = '+'; else cLifeStatus = ' ';

    printf( "%s", HTML_TABLE_BEGIN ); // Print the text 1st

    printf( HTML_ITEM_TEXT
          , iWith // td width
          , fBgColor( sTimeLine[i].cFlag )
          , sTimeLine[i].iYear
          , cLifeStatus
          , acTextTable[ sTimeLine[i].iPointer ]
          );

    // Set the levels:

    if( sTimeLine[i].cFlag == BORN_FLAG )
    {
      iLevel[sTimeLine[i].iLevel] = sTimeLine[i].iLevel; // Level is set the actual record
    }

    for( iMaxLev=iLev=1; iLev < LEVEL_SIZE; iLev++ ) // Get the last level index
    {
      if( iLevel[iLev] != 0 ) iMaxLev = iLev;
    }

    // Draw the lines:

    for( iLev=1; iLev <= iMaxLev; iLev++ ) // Until the last level number
    {
      if(( sTimeLine[i].cFlag != EVENT_FLAG ) // Born or Death
      && ( sTimeLine[i].iLevel >= iLev ))
      {
        printf( HTML_DRAW_HORIZONTAL_VISIBLE, iPadding, fColor( sTimeLine[i].iLevel ));
      }
      else // Simple event or actual level is reached
      {
        printf( HTML_DRAW_HORIZONTAL_INVISIBLE, iPadding );
      }

      if( sTimeLine[i].iLevel == iLev ) iVertSize = iHeight; else iVertSize = iHeight*2;

      if( iLevel[iLev] == 0 )
      {
        printf( HTML_DRAW_VERTICAL_INVISIBLE, iHeight*2 );
      }
      else
      {
        if( sTimeLine[i].cFlag == BORN_FLAG )
        {
          printf( HTML_DRAW_VERTICAL_BORN, iHeight*2, fColor( iLev ), iVertSize );
        }
        else // Death
        {
          printf( HTML_DRAW_VERTICAL_DEATH, iHeight*2, fColor( iLev ), iVertSize );
        }
      }
    }

    if( sTimeLine[i].cFlag == DEATH_FLAG )
    {
      iLevel[sTimeLine[i].iLevel] = 0; // Level is cleared
    }

    printf( "%s", HTML_TABLE_END );
  }

  return( 0 );
}

// =========================================================================
// fAlpha(): Sort "sTimeLine" table by iYear field
// =========================================================================

static int fAlpha( TimeLine *s1, TimeLine *s2 )
{
  if( s1->iYear > s2->iYear ) return( 1 );

	return( 0 );
}

// =========================================================================
// fColor(): Set the line color depending on the level
// =========================================================================

static char *fColor( int iLevel )
{
  static char acColor[10];

  switch( iLevel%10 )
  {
  case  1:  sprintf( acColor, "990000" ); break;
  case  2:  sprintf( acColor, "009900" ); break;
  case  3:  sprintf( acColor, "FF9900" ); break;
  case  4:  sprintf( acColor, "0099CC" ); break;
  case  5:  sprintf( acColor, "FF66CC" ); break;
  case  6:  sprintf( acColor, "FFCC00" ); break;
  case  7:  sprintf( acColor, "9955FF" ); break;
  case  8:  sprintf( acColor, "552200" ); break;
  case  9:  sprintf( acColor, "220066" ); break;
  case 10:  sprintf( acColor, "3322FF" ); break;
  case 11:  sprintf( acColor, "6666FF" ); break;
  default:  sprintf( acColor, "CC00FF" ); break;
  }

	return( acColor );
}

// =========================================================================
// fColor(): Set the background color depending on the entry type
// =========================================================================

static char *fBgColor( char cFlag )
{
  static char acColor[10];

  switch( cFlag )
  {
  case  BORN_FLAG :  sprintf( acColor, "FFCC00" ); break;
  case  DEATH_FLAG:  sprintf( acColor, "E0E0E0" ); break;
  case  EVENT_FLAG:  sprintf( acColor, "CCFF99" ); break;
  case  NULL_FLAG :  sprintf( acColor, "FF0000" ); break;
  default:           sprintf( acColor, "FF0000" ); break;
  }

	return( acColor );
}


/* ========================== end of source =============================== */
