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

static void skip_line(FILE* file)
{
    int c;

    while ((c = fgetc(file)))
	if (c != '\r' || c != '\n')
	    break;
    ungetc(c, file);
}

static inline char* skip_space(char* buf)
{
    if (!buf)
        return 0;

    while (*buf && isspace(*buf))
	buf++;

    return buf;
}

static inline unsigned int get_subtime(int sh, int sm, int ss, int su)
{
    return ((sh * 60 + sm) * 60 + ss) * 1000 + su;
}

// read line and remove window \r character from it
static char* read_line(char* buf, size_t size, FILE* file)
{
    char *endb = buf + size - 1;
    char *beg = buf;

    //cout << "size " << size << "  " << (void*) beg << "  " << (void*) end << endl;
    while (beg < endb)
    {
	int c = fgetc(file);
	if (c == EOF)
	{
	    if (beg != buf)
		break;

	    return 0;
	}
	if (c == '\n')
	    break;    // we have line
	if (c != '\r')
	    *beg++ = c;
    }
    *beg = 0;
    return buf;
}

static void add_line(subtitle_line_t* l, char *txt)
{
    if (l->lines < SUBTITLE_MAX_LINES)
    {
	int n = 0;
	char* e;
	char* b = skip_space(txt);
        e = b;
	while (*e)
	    e++;

	while (e > b)
	{
	    if (!isspace(e[-1]))
		break;
	    e--;
	}

	n = e - b;
	if (l->lines > 0 || n > 0)
	{
	    e = (char*) malloc(n + 1);
	    if (e)
	    {
		e[n] = 0;
		memcpy(e, b, n);
		l->line[l->lines++] = e;
	    }
	}
    }
    else
	printf("Too many subtitle lines: %d ignoring: %s\n", l->lines, txt);

    if (l->end < l->start)
	// some subtitles end with {XX}{0} - confusing our searcher
	l->end = l->start + 1;
    //cout << "ADDLINE " << n << "  >" << name << "< " << start << " " << end << endl;
}

static void add_line_columned(subtitle_line_t* l, char *text)
{
    for (;;)
    {
	char* s = strchr(text, '|');
	//printf("text %s  %p\n", text, s);
	if (s == NULL)
	{
	    add_line(l, text);
            break;
	}
	*s = 0;
	add_line(l, text);
	text = s + 1;
    }
}

static void add_sub_line(subtitles_t* l, subtitle_line_t* sl)
{

    if (l->allocated <= (l->count + 1))
    {
	l->allocated += 200;
	//printf("Realloc %p   %d\n", l->subtitle, sizeof(*sl) * l->allocated);
	l->subtitle = realloc(l->subtitle, sizeof(*sl) * l->allocated);
    }

    if (l->subtitle)
    {
        memcpy(&l->subtitle[l->count], sl, sizeof(*sl));
        l->count++;
    }
}

static int parse_SAMI(subtitle_line_t* l, char* s, int state, char* temp)
{
    char *p, *q;

    p = temp + strlen(temp);

    for (;;)
    {
	//printf("SAMI %d  %s\n", state, s);
	switch (state) {

	case 0: /* find "START=" */
	    s = strstr(s, "Start=");
	    if (s)
	    {
		l->start = strtol(s + 6, &s, 0);
		state = 1;
		continue;
	    }
	    break;

	case 1: /* find "<P" */
            s = strstr (s, "<P");
	    if (s)
	    {
		s += 2;
		state = 2;
		continue;
	    }
	    break;

	case 2: /* find ">" */
            s = strchr (s, '>');
	    if (s)
	    {
		s++;
		p = temp;
		state = 3;
		continue;
	    }
	    break;

	case 3: /* get all text until '<' appears */
	    if (*s == '<')
		state = 4;
	    else if (!strncasecmp(s, "&nbsp;", 6))
	    {
		*p++ = ' ';
		s += 6;
	    }
	    else if (!strncasecmp(s, "<br>", 4) || *s == 0)
	    {
		*p = 0;
		add_line(l, temp);
		p = temp;
		if (*s == 0)
                    break;
		s += 4; // <br>
	    }
	    else
		*p++ = *s++; // copy
	    continue;

	case 4: /* get end or skip <TAG> */
	    q = strstr(s, "Start=");
	    if (q) {
		l->end = strtol(q + 6, &q, 0) - 1;
		*p = 0;
		state = 100; // finished one subtitle
		break;
	    }
	    s = strchr(s, '>');
	    if (s)
	    {
		s++;
		state = 3;
		continue;
	    }
	    break;
	}
        // when we get here we need next line -> break main loop
	break;
    }
    *p = 0;
    return state;
}


#define IS_SUB(type) (!stype || (stype == type))

static void subtitle_reread(subtitles_t* l)
{
    int dummy, sh, sm, ss, su, eh, em, es, eu;
    int c = 0;
    subtitle_type stype = SUBTITLE_UNSELECTED;
    char bfr[1024];
    fpos_t fpos;

    FILE* file = fopen(l->filename, "rt");
    if (!file)
    {
	perror("subtitle open:");
        return;
    }

    // ORDER OF 'if's IS IMPORTANT!!!!!
    // this piece of code seem to be looking a bit complicated
    // but it is quite efficient and allows to read even subtitles
    // with different types in the same file :) (not very useful thought :))

    while (read_line(bfr, sizeof(bfr), file))
    {
        subtitle_line_t sl;
	int n = 0;

        memset(&sl, 0, sizeof(sl));

	// MicroDVD (.sub)
	if (IS_SUB(SUBTITLE_MICRODVD)
	    && sscanf(bfr, "{%d}{%d}%n", &sl.start, &sl.end, &n) == 2)
	{
	    stype = SUBTITLE_MICRODVD;

	    add_line_columned(&sl, &bfr[n]);
	    skip_line(file);
	}

	// SubRip
	if (IS_SUB(SUBTITLE_SUBRIP)
	    && sscanf(bfr, "%d:%d:%d.%d,%d:%d:%d.%d",
		      &sh, &sm, &ss, &su, &eh, &em, &es, &eu) == 8)
	{
	    stype = SUBTITLE_SUBRIP;

	    sl.start = get_subtime(sh, sm, ss, su);
	    sl.end   = get_subtime(eh, em, es, eu);
	    // now there can be 0, 1 or 2 lines of subtitles... ended by a newline
	    while (read_line(bfr, sizeof(bfr), file))
	    {
		// we skip whitespace and check whether the line's empty
		char* p = skip_space(bfr);
                char* out;
		if (*p == '\0')
		    break;	// this was a blank line -> end of titles here

		// replace [br] [BR] -> |
		out = p = bfr;
		while (*p != 0)
		{
		    if (!strncasecmp(p, "[br]", 4))
		    {
                        *p = 0;
			p += 4;
			add_line(&sl, out);
                        out = p;
		    }
                    else
			p++;
		}
                if (out < p)
		    add_line(&sl, out);
	    }
	}

	// vplayer format
	if (IS_SUB(SUBTITLE_VPLAYER)
            && sscanf(bfr, "%d:%d:%d:%n", &sh, &sm, &ss, &n) == 3)
	{
            stype = SUBTITLE_VPLAYER;

	    sl.start = get_subtime(sh, sm, ss, 0);
#define SHOWSUBTIME     4                               /* in seconds */
	    sl.end = sl.start + SHOWSUBTIME * 1000;

	    add_line_columned(&sl, &bfr[n]);
	    skip_line(file);
	}

        // Aqt (not sure who this is supposed to work)
	if (IS_SUB(SUBTITLE_AQT)
	    && sscanf(bfr, "-->> %d", &sl.start) == 1)
	{
	    int lineadded = 0;
	    unsigned int chr = 0;
            stype = SUBTITLE_AQT;

	    while (read_line(bfr, sizeof(bfr), file))
	    {
		// we skip whitespace and check whether the line's empty
		if (lineadded)
		{
		    char *p = skip_space(bfr);
		    if (*p == '\0')
			break;	// this was a blank line -> end of titles here
		}
		// line's not empty, we add as much as we can to name
		add_line(&sl, bfr);
                chr += strlen(bfr);
		lineadded =  1;
	    }
	    sl.end = sl.start + chr * 5;
	}

        // SAMI (.smi)
	if (IS_SUB(SUBTITLE_SAMI)
	    && (stype || strstr(bfr, "SAMI")))
	{
            char temp[sizeof(bfr)];
	    int state = 0;
	    stype = SUBTITLE_SAMI;
	    for (;;)
	    {
		state = parse_SAMI(&sl, bfr, state, temp);
		if (state == 100)
		{
		    fsetpos(file, &fpos); // back to the begining of the current line
		    break;
		}
		fgetpos(file, &fpos);
		if (!read_line(bfr, sizeof(bfr), file))
                    break;
	    }
	    if (feof(file))
		break;
	    fgetpos(file, &fpos);
	}
	// SubViewer (.srt)
	if (IS_SUB(SUBTITLE_SUBVIEVER)
	    && sscanf(bfr, "%d", &dummy) == 1)
	{
	    // Skip this buffer with only one number
	    // this should be tested as last case
	    // as it destructs the line in buffer !!!
	    if (read_line(bfr, sizeof(bfr), file)
		&& sscanf(bfr, "%d:%d:%d,%d --> %d:%d:%d,%d",
			  &sh, &sm, &ss, &su, &eh, &em, &es, &eu) == 8)
	    {
		int lineadded = 0;

                stype = SUBTITLE_SUBVIEVER;
		sl.start = get_subtime(sh, sm, ss, su);
		sl.end   = get_subtime(eh, em, es, eu);
		// now there can be 0, 1 or 2 lines of subtitles... ended by a newline
		while (read_line(bfr, sizeof(bfr), file))
		{
		    // we skip whitespace and check whether the line's empty
		    if (lineadded)
		    {
			char *p = skip_space(bfr);
			if (*p == '\0')
			    break;	// this was a blank line -> end of titles here
		    }
		    // line's not empty, we add as much as we can to name
		    add_line(&sl, bfr);
		    lineadded = 1;
		}
	    }
	}

	add_sub_line(l, &sl);
    }

    fclose(file);

    l->type = stype;
    switch (stype)
    {
    case SUBTITLE_MICRODVD:
	/* formats which are using frames */
	l->frame_based = 1;
	break;
    default:
        l->frame_based = 0;
    }
}

// SubRip example
//
// [INFORMATION]
// [TITLE]Me, myself and Irene
// [AUTHOR]Mysak
// [SOURCE]Subtitles captured by SubRip 0.93b
// [PRG]
// [FILEPATH]
// [DELAY]0
// [CD TRACK]0
// [COMMENT]
// [END INFORMATION]
// [SUBTITLE]
// [COLF]&HFFFFFF,[STYLE]bd,[SIZE]18,[FONT]Arial
// 00:00:01.02,00:00:07.00
// Titulky beta verze by My..
//
// 00:00:07.03,00:00:10.59
// text[br]text

static char* test_filename_suffix(const char* filename)
{
    static char* exts[] =
    {
        "",
	".sub", ".SUB",
	".srt", ".SRT",
	".txt", ".TXT",
	".aqt", ".AQT",
	".smi", ".SMI",
	/*".rt", ".RT",
	".ssa", ".SSA",*/
	NULL
    };
    char** s;
    int n = strlen(filename);
    char* fn = malloc(n + 8);

    if (!fn)
	return NULL;

    for (s = exts; *s; s++)
    {
        FILE* f;
	strcpy(fn, filename);
	strcpy(fn + n, *s);

	//printf("test %s\n", fn);
        f = fopen(fn, "rt");
	if (f)
	{
	    fclose(f);
	    printf( "Subtitles: using file: %s\n", fn);
	    return fn;
	}
    }

    free(fn);
    return NULL;
}


/*
 * create name for subtitle file
 */
static char* resolve_filename(const char* filename)
{
    char* fn, *fdup;
    int n;

    if (!filename)
	return NULL;

    fdup = strrchr(filename, '.');
    if (fdup == NULL || strchr(fdup, '/'))
	return test_filename_suffix(filename);

    n = fdup - filename;
    fdup = malloc(n + 1);
    if (fdup == NULL)
        return NULL;

    memcpy(fdup, filename, n);
    fdup[n] = 0;

    fn = test_filename_suffix(fdup);
    free(fdup);

    return fn;
}

static void subtitle_print(const subtitles_t* l)
{
    if (l != NULL)
    {
	int i;

	printf("Subtitles: %d line(s)\n", l->count);
	for (i = 0; i < l->count; i++)
	{
	    subtitle_line_t* sl = &l->subtitle[i];
	    int j;
	    //break;
	    printf("Start: %d     End: %d   (%d)\n", sl->start, sl->end, sl->lines);
	    for (j = 0; j < sl->lines; j++)
		printf("  %d: %s\n", j, sl->line[j]);
	}
    }
}

subtitles_t* subtitle_open(subtitles_t* st, const char* filename)
{
    char* subname;
    if (st == 0)
    {
	char* subname;
	st = (subtitles_t*) malloc(sizeof(*st));
	if (!st)
	    return NULL;
	memset(st, 0, sizeof(*st));
	pthread_mutex_init(&st->mutex, NULL);
    }

    pthread_mutex_lock(&st->mutex);
    if (st->filename)
	free(st->filename);
    if (st->subtitle)
    {
	int i;
	for (i = 0; i < st->count; i++)
	{
	    int j;
	    for (j = 0; j < SUBTITLE_MAX_LINES; j++)
	    {
		if (st->subtitle[i].line[j])
		    free(st->subtitle[i].line[j]);
	    }
	}
	free(st->subtitle);
	st->allocated = st->count = 0;
        st->subtitle = 0;
    }

    subname = resolve_filename(filename);
    if (subname != NULL)
    {
	st->filename = subname;
	subtitle_reread(st);
    }
    pthread_mutex_unlock(&st->mutex);
    return st;
}

#if 1
int main(int argc, char **argv)
{  // for testing
    int i,j;
    subtitles_t *subs;
    subtitle_line_t sl;
    //unsigned int ar[] = { 1165, 1493, 0 };
    //double ar[] = { 42.874, 47.1, 0 };
    double ar[] = { 22.14, 53.1, 0 };

    if (argc<2)
    {
        printf("\nUsage: subtitle filename.sub\n\n");
        exit(1);
    }

    subs = subtitle_open(0, argv[1]);
    if (!subs)
    {
	printf("Couldn't load file: %s\n", argv[1]);
        exit(1);
    }

    subtitle_print(subs);

    for (i = 0; ar[i]; i++)
    {
	if (subtitle_get(&sl, subs, ar[i]))
	{
            int j;
	    printf("%f:  Start: %d     End: %d\n", ar[i], sl.start, sl.end);
	    for (j = 0; j < sl.lines; j++)
		printf("  %d: %s\n", j, sl.line[j]);
	}
	else
	    printf("Not found: %f\n", ar[i]);
    }

    subtitle_close(subs);

    //printf ("Subtitle format %s time.\n", sub_uses_time?"uses":"doesn't use");
    //printf ("Read %i subtitles, %i errors.\n", sub_num, sub_errs);
    return 0;
}
#endif
