/*
 *  BBCode 2 HTML converter by //YorHel
 *  Copyright 2006 Y.Heling,
 *  License: MIT
 *
 *  Created just to learn C, probably very ugly piece of code
 *  and probably with a _LOT_ of bugs... But the only way to
 *  learn a programming language is to code something yourself :)
 */

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

#define MAX_ARGH_SIZE 500
#define MAX_TAG_SIZE 10
#define MAX_NESTED_TAGS 100

#define TAGCHARS 28
#define NUMBER_OF_TAGS 16

#define TRUE 1
#define FALSE 0


/* typedefs  */
typedef enum {
  B,
  I,
  U,
  SIZE,
  COLOR,
  URL,
  QUOTE,
  QUOTE2,
  IMG,
  IMG2,
  EMAIL,
  LIST,
  LIST2,
  CODE,
  HTML,
  UNDEF, /* hack */
} TAGNAME;
typedef struct {
  TAGNAME intags[MAX_NESTED_TAGS];
  int curtag;
  char tag[MAX_TAG_SIZE];
  char argh[MAX_ARGH_SIZE];
  FILE *dest;
  int inlist;
  int liststart;
} PARSEINFO; /* just a hack to prevent the use of global variables */
typedef struct {
  char *bb;
  char *html_s;
  char *html_e;
  char arg;
} TAGINFO; /* store global information about the tags */


/* functions */
void parsebbcode(FILE *, FILE *);
void converttag(PARSEINFO *);
void formattag(PARSEINFO *);
char endtags(PARSEINFO *, TAGNAME);
void convertchars(PARSEINFO *, const char *);
void convertchar(PARSEINFO *, const int);
void convertchararg(char *, int);
char istagchar(const int);
void err(const char *);

/* global vars (only consts!) */
const char tagchars[TAGCHARS] = "abcdefghijklmnopqrstuvwxyz/*";
const TAGINFO taglist[NUMBER_OF_TAGS] = { /* same order as TAGNAME! */
  { "b",      "<b>",  "</b>", FALSE },
  { "i",      "<i>",  "</i>", FALSE },
  { "u",      "<span style=\"text-decoration: underline\">", "</span>", FALSE },
  { "size",   "<span style=\"font-size: %spx\">", "</span>", TRUE },
  { "color",  "<span style=\"color: %s\">", "</span>", TRUE },
  { "url",    "<a href=\"%s\">", "</a>", TRUE },
  { "quote",  "<span class=\"bbcode_quote_header\">Quote: <span class=\"bbcode_quote_body\">", "</span></span>", FALSE },
  { "quote",  "<span class=\"bbcode_quote_header\">%s wrote: <span class=\"bbcode_quote_body\">", "</span></span>", TRUE },
  { "img",    "<img src=\"", "\" alt=\"\" />", FALSE },
  { "img",    "<img src=\"%s\" alt=\"", "\" />", TRUE },
  { "email",  "<a href=\"mailto:%s\">", "</a>", TRUE },
  { "list",   "<ul>", "</li></ul>", FALSE },
  { "list",   "<ul style=\"list-style-type: %s\">", "</li></ul>", TRUE },
  { "code",   "<span class=\"bbcode_code_header\">Code: <span class=\"bbcode_code_body\">", "</span></span>", FALSE },
  { "html",   "", "", FALSE },
  { "", "", "", FALSE },
}; /* NOTE: not all characteristics of the BBCodes are defined above, there is also a lot of hard-coded stuff below for a few tags */



int main() {
/*  printf("BBCode to HTML converter by //YorHel\n");
  printf("Copyright 2006 Y. Heling\n\n");
 
  printf("* Reading & parsing bbcode...\n");
  while(1) {
  FILE *file;
  if((file = fopen(BBFILE, "r")) == NULL) err("Couldn't open BBFILE");
  parsebbcode(file, stdout);
  fclose(file);
  }
  printf("\n");*/

  parsebbcode(stdin, stdout);
  
  return 0;
}


void parsebbcode(FILE *file, FILE *dest) {
  PARSEINFO pitemp; /* make sure we allocate the memory */
  PARSEINFO *pi = &pitemp; /* but we are still going to use a pointer */
  char intag = pi->inlist = pi->liststart = 0;
  pi->curtag = 1;
  pi->intags[0] = UNDEF;
  sprintf(pi->tag, "");
  sprintf(pi->argh, "");
  pi->dest = dest;
  int c;
  char tmp[MAX_TAG_SIZE+MAX_ARGH_SIZE+4]; /* temp string, should be able to hold anything necessary */
  while((c = fgetc(file)) && c != EOF) {
    if(intag == 0 && c == '[') {
      intag = 1;
    }
    else if(intag == 1 && c == '=')
      intag = -1;
    else if(intag != 0 && c == ']') {
      intag = 0;
      converttag(pi);
      sprintf(pi->tag, "");
      sprintf(pi->argh, "");
    }
    else if(intag == 1 && strlen(pi->tag) < sizeof(pi->tag)) {
      if(istagchar(c)) {
        sprintf(tmp, "%c", tolower(c));
        strcat(pi->tag, tmp);
      } else {
        sprintf(tmp, "[%s", pi->tag);
        convertchars(pi, tmp);
        sprintf(pi->tag, "");
      }
    }
    else if(intag == 1) {
      intag = 0;
      sprintf(tmp, "[%s", pi->tag);
      convertchars(pi, tmp);
      sprintf(pi->tag, "");
   }
    else if(intag == -1 && (strlen(pi->argh)+10) < sizeof(pi->argh)) {
      convertchararg(tmp, c);
      strcat(pi->argh, tmp);
    }
    else if(intag == -1) {
      sprintf(tmp, "[%s=%s", pi->tag, pi->argh);
      convertchars(pi, tmp);
      sprintf(pi->tag, "");
      sprintf(pi->argh, "");
      intag = 0;
    }
    else
      convertchar(pi, c);
  }
 /* some stuff left in the buffer */
  if(strlen(pi->tag) > 0 && strlen(pi->argh) == 0)
    fprintf(dest, "[%s", pi->tag);
  else if(strlen(pi->tag) > 0 && strlen(pi->argh) > 0)
    fprintf(dest, "[%s=%s", pi->tag, pi->argh);
 /* automatically close opened tags */
  endtags(pi, UNDEF);
}

void converttag(PARSEINFO *pi) {
 /* ignore tag if we're not allowed to nest */
  if((pi->intags[pi->curtag] == CODE && strcmp(pi->tag, "/code") != 0)
       || (pi->intags[pi->curtag] == HTML && strcmp(pi->tag, "/html") != 0)
       || ((pi->intags[pi->curtag] == IMG || pi->intags[pi->curtag] == IMG2) && strcmp(pi->tag, "/img"))) {
    formattag(pi);
    return;
  }

 /* parse list items */
  if(!strcmp(pi->tag, "*") && pi->inlist) {
    endtags(pi, LIST);
    if(pi->inlist == pi->liststart)
      fprintf(pi->dest, "</li>");
    else
      pi->liststart = pi->inlist;
    fprintf(pi->dest, "<li>");
    
    return; /* no need to parse more */
  }
  
 /* begin a tag */
  if(*pi->tag != '/') {
    int i; int got = -1;
    for(i=0 ; i<NUMBER_OF_TAGS && got == -1 ; i++)
      if(!strcmp(pi->tag, taglist[i].bb)) {
        got = i;
        if(strlen(pi->argh) == 0 && !taglist[i].arg)
          fprintf(pi->dest, "%s", taglist[i].html_s);
        else if(strlen(pi->argh) > 0 && taglist[i].arg) {
          if(i != LIST2)
            fprintf(pi->dest, taglist[i].html_s, pi->argh);
          else
            fprintf(pi->dest, taglist[i].html_s, strcmp(pi->argh, "1") ? "lower-roman" : "decimal");
        }
        else
          got = -1;
      };
    if(got != -1) {
      pi->intags[++pi->curtag] = got;
      if(got == LIST || got == LIST2)
        pi->inlist++;
    }
    else 
      formattag(pi);
    return;
  }
  
 /* end a tag */
  else {
    char *tag = pi->tag+1; /* strip the '/' */
    int i;
    char got = FALSE;
    for(i=0 ; i<NUMBER_OF_TAGS && got == FALSE ; i++)
      if(!strcmp(tag, taglist[i].bb) && endtags(pi, i)) {
        fprintf(pi->dest, "%s", taglist[i].html_e);
        if(pi->intags[pi->curtag] == LIST || pi->intags[pi->curtag] == LIST2)
          pi->liststart = --pi->inlist;
        pi->curtag--;
        got = TRUE;
      };
    if(!got)
      formattag(pi);
  }
}

char endtags(PARSEINFO *pi, TAGNAME to) {
  char s = FALSE;
  if(to == QUOTE || to == LIST || to == IMG)
    s = TRUE;
  else if(to == QUOTE2 || to == LIST2 || to == IMG2) {
    s = TRUE;
    to--;
  }
  int i = pi->curtag;
  if(to != UNDEF)
    while(pi->intags[i] != to && (!s || pi->intags[i] != to+1) && i > 0)
      i--;
  if(i) {
    while(pi->intags[pi->curtag] != to && (!s || pi->intags[pi->curtag] != (to+1)) && pi->curtag > 0) {
      if(pi->intags[pi->curtag] == LIST || pi->intags[pi->curtag] == LIST2)
        pi->liststart = --pi->inlist;
      fprintf(pi->dest, "%s", taglist[pi->intags[pi->curtag--]].html_e);
    }
    return TRUE;
  }
  else
    return FALSE;
}

void formattag(PARSEINFO *pi) {
  char tmp[MAX_TAG_SIZE+MAX_ARGH_SIZE+4];
  if(strlen(pi->argh) == 0)
    sprintf(tmp, "[%s]", pi->tag);
  else
    sprintf(tmp, "[%s=%s]", pi->tag, pi->argh);
  convertchars(pi, tmp);
}

void convertchars(PARSEINFO *pi, const char *chars) {
  int i;
  for(i=0;i<strlen(chars);i++)
    convertchar(pi, *(chars+i));
}

void convertchar(PARSEINFO *pi, const int c) {
  if(pi->inlist && pi->inlist != pi->liststart)
    return;
  else if(pi->intags[pi->curtag] == HTML)
    fprintf(pi->dest, "%c", c);
  else if(pi->intags[pi->curtag] == IMG || pi->intags[pi->curtag] == IMG2) {
    char tmp[10];
    convertchararg(tmp, c);
    fprintf(pi->dest, tmp);
  }
  else if(c == '\n')
    fprintf(pi->dest, "<br />\n");
  else if(c == '&')
    fprintf(pi->dest, "&amp;");
  else if(c == '<')
    fprintf(pi->dest, "&lt;");
  else if(c == '>')
    fprintf(pi->dest, "&gt;");
  else
    fprintf(pi->dest, "%c", c);
}

void convertchararg(char *to, const int c) {
  switch(c) {
    case '\n' :
      sprintf(to, ""); break;
    case '&' :
      sprintf(to, "&amp;"); break;
    case '"' :
      sprintf(to, "&quot;"); break;
    default :
      sprintf(to, "%c", c);
  }
}

char istagchar(const int c) {
  int i;
  int c2 = tolower(c);
  for(i=0;i<TAGCHARS;i++)
    if(c2 == *(tagchars+i))
      return TRUE;
  return FALSE;
}

void err(const char *msg) {
  fprintf(stderr, "ERROR: %s\n", msg);
  exit(1);
}
