/** file cwrap.c - line wrapping demo with color escapes
   escape codes: "###..." sequences of (n) hash display as (n-1) hashes
                 "#n" forced linebreak

                 "#=" reset ansi color
                 "#g" ansi brightly black (gray)
                 "#R" ansi bold red
                 "#G" ansi bold green
                 etc...

                 unrecognized char prints a single hash also,
                 hash at end of line won't crash or infinite-loop
                 (anymore, thank goodness!)

   todo: multiple columns like a newspaper or magazine,
         each wrapped individually.
**/

#include <stdio.h>
#include <stdlib.h>

int COLUMNS = 75; /* terminal character columns, not paragraphs */

#define DO_FILES

#define pass(x) do { printf("%s\n", #x); } while(0)
#define LEVEL 100
#define if_debug(x) if((x) && (x) < LEVEL)

#define dummy (-1)

#define RESET   "\x1b[0m"
#define GRAY    "\x1b[1;30m"
#define RED     "\x1b[1;31m"
#define GREEN   "\x1b[1;32m"
#define YELLOW  "\x1b[1;33m"
#define BLUE    "\x1b[1;34m"
#define MAGENTA "\x1b[1;35m"
#define CYAN    "\x1b[1;36m"
#define WHITE   "\x1b[1;37m"

typedef enum {
   reset=0, gray, red, green, yellow, blue, magenta, cyan, white,
   ansimax=white
} ansi_t;

const char *ANSI[] = {
   RESET, GRAY, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE
};

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

int runlen(char *msg, int width, int *run) {
   int i, bump, stop;
   int pos, notrun;
   char prev, c;

   if(!run) run = &notrun;
   pos = *run = 0;

   prev = c = '?';
   bump = stop = 0;

   /* !! looping until the PREVIOUS char in string prevents "...#" bug !! */
   /* delayed char kinda sucks reading from a terminal but this is string */

   for(i = 0; prev != '\0'; i++) {
       prev = c; c = msg[i];

       if(c == '#' && prev != c)    continue;

       if(prev != '#' || prev == c) pos++;

       if(!c) bump = i;
       if(c == ' ' && prev != c) bump = i;

       /* forced linebreak, i+1 to include the 'n' too, our "previous" */
       /* trick wouldn't work without an extra char of context.        */

       if(c == 'n' && prev == '#') bump = i+1;

#ifdef DO_FILES
       if(c == '\n') bump = i+1;
#endif

       /* OR EQUAL!, seldom seen fencepost error otherwise */

       if(pos <= width) { stop = bump; *run = pos; }
       else break;

       /* this hasn't proved neccessary but should've been disasterous... */
       prev = c;
   }
   return stop;
}

//-------------------------------------------------------------------------
int printchar(char *msg, int i) {
   char prev, c, *t;

   c = msg[i];
   if(0 < i) prev = msg[i-1];
   else prev = ' ';

   if(!c) return 0;

#ifdef DO_FILES
   if(c == '\n') return 1;
#endif

   if(c == '#' && prev != c) return 0;

   if(prev != '#') putc(c, stdout);
   else switch(c) {

       case 'n': return 1; break;

       case '=': printf( ANSI[reset] );   break;
       case 'g': printf( ANSI[gray] );    break;
       case 'R': printf( ANSI[red] );     break;
       case 'G': printf( ANSI[green] );   break;
       case 'Y': printf( ANSI[yellow] );  break;
       case 'B': printf( ANSI[blue] );    break;
       case 'M': printf( ANSI[magenta] ); break;
       case 'C': printf( ANSI[cyan] );    break;
       case 'W': printf( ANSI[white] );   break;

       default: putc('#', stdout); break;
   };
   return 0;
}
//-------------------------------------------------------------------------

int wrap(char *msg, int width, int pos) {
   int i, w, len, run, lb;

   if(!msg || !*msg) { printf("\r\n"); return 0; }

   while(*msg) {

       w = width-pos;
       len = runlen(msg, w, &run);

       for(i = 0; i < len; i++) lb = printchar(msg, i);

       if(lb) { printf("\r\n"); pos = 0; }
       else pos += run;

       msg += len;
       if(*msg) {
           while(*msg == ' ') msg++;

           /* this is wrapping linebreak only */
           if(!lb) printf("\r\n");
           pos = 0;
       }
   }
   return pos;
}

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

int main(int argc, char *argv[]) {
   int i, len, pos;
   char *line, buffer[4096];
   char *lyrics[] = {
       "",
       "Bye bye Miss #RAmer#Wican#B Pie#=, drove my Chevy to the levy 'cause the levy was dry.",
       " And good ol' boys were drinking wisky and rye, singing: 'This will be the day that I die'...#n",
       "",
       "Did you write the Book of Love, and do you have faith in #YGod#= above, if the Bible tells you so.",
       " Do you believe in #Crock 'n roll#=, can music save, #gyour mortal soul#=, and can you teach me to dance real slow...#n",
       "",
       "Well I know that you're in love with him, 'cause I saw you dancing, in the gym.",
       " You both kicked off your shoes, man I love those rythmly #Bblues!#=",
       " Ooh! I was a lonely teenage broncho buck, with a #Mpink carnation#= and a pickup truck,",
       " But I knew I was out of luck, the day, the music, died.#n",
       "",
       "I was singing: Bye bye Miss American Pie...#n",
       "#.#n#",
       "#.#n#asdf",
       "abc#nxyz",
       NULL
   };

   argc--;
   if(argc) { sscanf(argv[1], "%d", &COLUMNS); argv++; argc--; }
   if(12 > COLUMNS || COLUMNS > 120) COLUMNS = 75;

   for(i = 0, pos = 0; lyrics[i] != NULL; i++) {
       line = lyrics[i];
       pos = wrap(line, COLUMNS, pos);
   }

#ifdef DO_FILES
   while(argc) {
       freopen(argv[1], "r", stdin);

       pos = 0;
       while( fgets(buffer, sizeof(buffer), stdin) ) {
           pos = wrap(buffer, COLUMNS, pos);
       }
       argv++; argc--;
   }
#endif

   return 0;
}