/****************************** Dusan Rajcevic NRT-1/12 ******************************/
#include <stdio.h>	// printf, scanf, fprintf, fopen, fclose
#include <ctype.h>	// tolower
#include <stdlib.h>	// exit, malloc, realloc, free, atoi
#include <string.h>	// strcat, strcmp, strcpy, strlen
#include <math.h>		// fmod

#define MAXCHAR 80+1
#define SAT 3600
#define MIN 60
#define DES 0.1
#define STO 0.01
#define MIL 0.001
enum tipovi { CHAR, INT, DOUBLE, TITL };

typedef struct titl {
	int br_titla;
	double vrem_poc;
	double vreme_kraj;
	char *text;
	struct titl *sled;
} Titl;

void copyright(void);
void greska(int error);
void *dodela_memorije(int brEl, int tip);
short provera_putanje(char *putanja);
void unos_naziva(char **putanja_izvor, char **putanja_cilj, int argc, char *argv[]);
void otvaranje_datoteka(char *izvor, char *cilj, FILE **izvorni, FILE **konvertovan);
int uBroj(char broj);
void konverzija(char *izvor, char *cilj, FILE *titl_izvorni, FILE *titl_konvertovan);
void brisi_listu(Titl *lista);
void srt_u_sub(FILE *izvorni, FILE *konvertovan);
void vreme(double vreme, FILE *konvertovan);
void sub_u_srt(FILE *izvorni, FILE *konvertovan);
void zatvaranje_datoteka(FILE *izvorni, FILE *konvertovan);
void poruka();

int main(int argc, char *argv[]) {
	char *putanja_izvor, *putanja_cilj;
	FILE *titl_izvorni = 0, *titl_konvertovan = 0;
	copyright();
	unos_naziva(&putanja_izvor, &putanja_cilj, argc, argv);
	otvaranje_datoteka(putanja_izvor, putanja_cilj, &titl_izvorni, &titl_konvertovan);
	konverzija(putanja_izvor, putanja_cilj, titl_izvorni, titl_konvertovan);
	zatvaranje_datoteka(titl_izvorni, titl_konvertovan);
	poruka();
	return 0;
}

// Funkcija 'copyright' ispisuje na ekranu ime programa i autora.
// Poziv se obavlja iz:
//   - funkcije 'main' (linija 43).
void copyright(void) {
	printf("Konvertor SubRip(SRT) U MicroDVD(SUB) i obratno\t[Verzija: 1.0]\n");
	printf("(C) Copyright 2013, Dusan Rajcevic NRT-1/12\n\n");
}

// Funkcija 'greska', poziva se kada je potrebno ispisati
// gresku na ekranu i izaci iz programa.
// Poziv se obavlja iz:
//   - funkcije 'dodela_memorije'
//   - funkcije 'otvaranje_datoteka'
//   - funkcije 'uBroj'
//   - funkcije 'srt_u_sub'
void greska(int error) {
	switch(error) {
		case 0: fprintf(stderr, "\nGreska pri rezervisanju memorije!\n\n"); exit(1);
		case 1: fprintf(stderr, "\nDatoteka sa unetim imenom ne postoji na disku!\n\n"); exit(1);
		case 2: fprintf(stderr, "\nGreska pri kreiranju datoteke!\n\n"); exit(1);
		case 3: fprintf(stderr, "\nUneta datoteka nije pravilno formatirana!\n\n"); exit(1);
		default: break;
	}
}

// Funkcija 'dodela_memorije' vraca genericki pokazivac.
// Poziva se pri rezervisanju memorije za razne tipove promenljivih pri
// cemu se vrsi i provera. U slucaju greske, izlazi se iz programa.
// Poziv se obavlja iz:
//   - funkcije 'unos_naziva'
//   - funkcije 'srt_u_sub'
//   - funkcije 'sub_u_srt'
void *dodela_memorije(int brEl, int tip) {
	void *dodela;
	switch(tip) {
		case 0: dodela = malloc(brEl * sizeof(char)); break;
		case 1: dodela = malloc(brEl * sizeof(int)); break;
		case 2: dodela = malloc(brEl * sizeof(double)); break;
		case 3: dodela = malloc(brEl * sizeof(Titl)); break;
		default: break;
	}
	if(dodela == NULL)
		greska(0);
	return dodela;
}

// Funkcija 'provera_putanje' vrsi proveru stringa kojim se zadaje putanja do datoteke.
// Putanja mora biti duza od 4 karaktera, da poslednja 4 karaktera budu ".srt", ".sub",
// (ili velikim slovima), pocetni karakter mora biti broj ili slovo, i ukoliko je zadat
// direktorijum razlicit od podrazumevanog, razdvajanje mora biti obavljeno dvostrukom
// obrnutom kosom crtom. U slucaju greske, funkcija vraca 1, a ako prodje proveru 0.
// Poziv se obavlja iz:
//   - funkcije 'unos_naziva'
short provera_putanje(char *putanja) {
	int i, duzina = (int)strlen(putanja);
	
	// Proverava se ekstenzija - duzina naziva mora biti veca od 4, ime datoteke
	// mora se zavrsiti na srt ili sub i 4. karakter od kraja mora biti '.'.
	if(duzina <= 4 || putanja[duzina-4] != '.' ||
	!(tolower(putanja[duzina-1]) == 't' && tolower(putanja[duzina-2]) == 'r' && tolower(putanja[duzina-3]) == 's' ||
	  tolower(putanja[duzina-1]) == 'b' && tolower(putanja[duzina-2]) == 'u' && tolower(putanja[duzina-3]) == 's')) {
		printf("Ekstenzija mora biti srt ili sub.\n\n");
		return 1;
	}

	// Pocetni karakter naziva mora biti slovo ili broj.
	else if(!(putanja[0] > '0' && putanja[0] < '9'
		|| putanja[0] > 'A' && putanja[0] < 'Z'
		|| putanja[0] > 'a' && putanja[0] < 'z'))
		return 1;
	
	// Putanja mora sadrzati "\\", a ne samo '\' ili '/'.
	for(i = 0; i < duzina; i++) {
		if(putanja[i] == '\\')
			if(putanja[i+1] == '\\' || putanja[i-1] == '\\')
				continue;
			else {
				printf("Direktorijumi moraju biti odvojeni dvostrukom obrnutom kosom crtom (\\\\)!\n\n");
				return 1;
			}
		else if(putanja[i] == '/') {
			printf("Direktorijumi moraju biti odvojeni dvostrukom obrnutom kosom crtom (\\\\)!\n\n");
			return 1;
		}
	}
	return 0;
}

// Funkcija 'unos_naziva' proverava da li je iz komandne linije uneta putanja i ako
// jeste, proverava se njena ispravnost. U slucaju da putanja nije navedena pri pozivu
// programa, ili ako je argument pogresno napisan, od korisnika se trazi unos,
// a zatim ponovo vrsi provera. Takodje se automatski dodeljuje naziv za datoteku u
// koju ce se smestiti konvertovan titl; razlika je samo u ekstenziji.
// Poziv se obavlja iz:
//   - funkcije 'main'
// Funkcija poziva:
//   - funkciju 'provera_putanje'
//   - funkciju 'dodela_memorije'
void unos_naziva(char **putanja_izvor, char **putanja_cilj, int argc, char *argv[]) {
	int i;
	if(argc == 1 || provera_putanje(argv[1]) == 1) {	// provera_putanje -> 0 ako je OK.
		*putanja_izvor = (char *)dodela_memorije(MAXCHAR, CHAR);
		do {
			printf("Unesite putanju datoteke u kojoj se nalazi titl:\n");
			gets(*putanja_izvor);
		} while(provera_putanje(*putanja_izvor));	// provera_putanje -> 0 ako je OK.
		realloc(*putanja_izvor,strlen(*putanja_izvor)+1);	// Optimizacija memorije.
	} else	// Ako je drugi parametar iz komandne linije unet i ako je OK.
		*putanja_izvor = argv[1];	// putanja_izvor dobija adresu stringa argv[1].
	*putanja_cilj = (char *)dodela_memorije((int)strlen(*putanja_izvor)+1, CHAR);
	
	// Upis naziva datoteke izvornog titla u ciljani, npr. ako se u *putanja_izvor nalazi
	// "naziv.srt", *putanja_cilj se puni do "naziv.s".
	for(i = 0; i < (int)strlen(*putanja_izvor)-2; i++) {
		(*putanja_cilj)[i] = (*putanja_izvor)[i];
	}
	// Nastavak upisa; ako je pretposlednje slovo 'r' ili 'R', dopisuje se 'u' i 'b',
	// u suprotnom, dopisuje se 'r' i 't'
	// tj. *putanja_cilj postaje "naziv.sub"
	if((*putanja_izvor)[i] == 'r' || (*putanja_izvor)[i] == 'R')
		(*putanja_cilj)[i] = 'u', (*putanja_cilj)[++i] = 'b';
	else
		(*putanja_cilj)[i] = 'r', (*putanja_cilj)[++i] = 't';
	(*putanja_cilj)[i+1] = '\0'; // Dopisuje se oznaka za kraj stringa.
}

// Funkcija otvaranje_datoteka otvara datoteke za upis i citanje.
// U slucaju greske, oslobadja se memorija i izlazi iz programa.
// Poziv funkcije obavlja se iz:
//   - funkcije 'main'
// Funkcija poziva
//   - funkciju 'greska'
void otvaranje_datoteka(char *izvor, char *cilj, FILE **izvorni, FILE **konvertovan) {
	if((*izvorni = fopen(izvor,"r")) == NULL) {
		free(izvor); free(cilj);
		greska(1);
	}
	if((*konvertovan = fopen(cilj,"w")) == NULL) {
		free(izvor); free(cilj);
		greska(2);
	}
}

// Funkcija konverzija vrsi proveru naziva datoteke za koju treba izvrsiti konverziju.
// U slucaju da je u pitanju SRT, vrsi se konverzija SRT u SUB.
// U suprotnom, obavice se konverzija SUB u SRT.
// Poziv funkcije obavlja se iz:
//   - funkcije 'main'
// Funkcija poziva:
//   - funkciju srt_u_sub
//   - funkciju sub_u_srt
void konverzija(char *izvor, char *cilj, FILE *titl_izvorni, FILE *titl_konvertovan) {
	// Ako je ekstenzija izvorne datoteke koju treba prevesti srt, vrsi se prevodjenje
	// srt_u_sub, a ako je sub - vrsi se prevodjenje sub_u_srt.
	if(izvor[strlen(izvor)-1]=='t') {	// Dovoljno je ispitati samo poslednje slovo.
		// oslobadja se memorija za stringove u kojima se nalaze putanje datoteka.
		free(izvor); free(cilj);
		srt_u_sub(titl_izvorni, titl_konvertovan);	// poziv konverzije
	}
	else {
		free(izvor); free(cilj);	// oslobadjanje memorije za stringove
		sub_u_srt(titl_izvorni, titl_konvertovan); // poziv konverzije
	}
}

// Funkcija uBroj pretvara karakter u rasponu od '0' do '9' u ceo broj od 0 do 9.
// U slucaju da nije prosledjen karakter od '0' do '9', povratna vrednost je -1.
// Poziv funkcije obavlja se iz:
//   - funkcije srt_u_sub
int uBroj(char broj) {
	if(broj >= '0' && broj <= '9')
		return (int)broj - '0';
	else
		return -1;
}

// Funkcija 'brisi_listu' dobija pokazivac na pocetak liste, i oslobadja memoriju
// do njenog kraja.
// Poziv funkcije obavlja se iz:
//   - funkcije 'srt_u_sub'
//   - funkcije 'sub_u_srt'
void brisi_listu(Titl *lista) {
	Titl *tek;
	while(lista) {
		tek = lista;
		lista = lista->sled;
		if(tek->text != NULL)
			free(tek->text);
		free(tek);
	}
}

// Funkcija 'srt_u_sub', formira jednostruko ulancanu listu i u nju ucitava podatke
// iz SRT datoteke. U slucaju loseg formata datoteke, napusta se program. Ako je
// datoteka dobrog formata, nakon formiranja liste, vrsi se upis u SUB datoteku.
// Poziv funkcije obavlja se iz:
//   - funkcije 'konverzija'
// Funkcija poziva:
//   - funkciju 'dodela_memorije'
//   - funkciju 'brisi_listu'
//   - funkciju 'zatvaranje_datoteka'
//   - funkciju 'greska'
//   - funkciju 'uBroj'
void srt_u_sub(FILE *izvorni, FILE *konvertovan) {
	char red[MAXCHAR];
	int br_frejmova, i;
	Titl *nov = NULL, *lista = NULL, *tek = NULL;
	int ik = 0;
	int izlaz = 0;
	fgets(red, MAXCHAR, izvorni);
	while(!feof(izvorni)) {
		nov = (Titl *)dodela_memorije(1,TITL);	// Rezervacija memorije za jedan element ul. liste.
		nov->text = NULL; // da bi se pravilno oslobodila memorija
		
		// Postavljanje ostatka elemenata ulancane liste
		nov->sled = NULL;
		if(lista == NULL)
			lista = nov;
		else
			tek->sled = nov;
		tek = nov;
		
		/*if((int)strlen(red) > 2) {	// Provera formata datoteke.
			brisi_listu(lista);
			zatvaranje_datoteka(izvorni, konvertovan);
			greska(3);
		}*/
		nov->br_titla = atoi(red);	// Ucitavanje rednog broja titla iz datoteke.
		
		fgets(red, MAXCHAR, izvorni);	// Ucitavanje vremena iz datoteke
		if((int)strlen(red) != 30)	{// Provera formata datoteke.
			brisi_listu(lista);
			zatvaranje_datoteka(izvorni, konvertovan);
			greska(3);
		}
		// Racunanje pocetnog vremena -> pretvaranje u sekunde sa zarezom.
		nov->vrem_poc  = uBroj(red[0])*10*SAT + uBroj(red[1])*SAT;
		nov->vrem_poc += uBroj(red[3])*10*MIN + uBroj(red[4])*MIN;
		nov->vrem_poc += uBroj(red[6])*10 + uBroj(red[7]);
		nov->vrem_poc += uBroj(red[9])*DES;
		nov->vrem_poc += uBroj(red[10])*STO;
		nov->vrem_poc += uBroj(red[11])*MIL;
		// Racunanje krajnjeg vremena -> pretvaranje u sekunde sa zarezom.
		nov->vreme_kraj  = uBroj(red[17])*10*SAT + uBroj(red[18])*SAT;
		nov->vreme_kraj += uBroj(red[20])*10*MIN + uBroj(red[21])*MIN;
		nov->vreme_kraj += uBroj(red[23])*10 + uBroj(red[24]);
		nov->vreme_kraj += uBroj(red[26])*DES;
		nov->vreme_kraj += uBroj(red[27])*STO;
		nov->vreme_kraj += uBroj(red[28])*MIL;
		
		fgets(red, MAXCHAR, izvorni);	// Ucitavanje teksta titla iz datoteke.
		nov->text = (char *)dodela_memorije((int)strlen(red)+1, CHAR);
		strcpy(nov->text,red);
		// Ako titl ima drugi red, nastavak ucitavanja teksta titla iz datoteke.
		fgets(red, MAXCHAR, izvorni);
		if(strcmp(red, "\n")) {
			realloc(nov->text, strlen(red)+strlen(nov->text)+1); // Povecanje prostora.
			strcat(nov->text,red); // Nadovezivanje.
		}
		do {	// Citaju se svi prazni redovi do sledeceg titla.
			fgets(red, MAXCHAR, izvorni);
		} while(!strcmp(red, "\n") && !feof(izvorni));
	}

	tek = lista;
	printf("Unesite broj frejmova u sekundi: ");
	scanf("%d", &br_frejmova);
	// upis konverzije u datoteku
	while(tek) {
		fprintf(konvertovan, "{%.0lf}{%.0lf}",tek->vrem_poc*br_frejmova,tek->vreme_kraj*br_frejmova);
		for(i = 0; i < (int)strlen(tek->text); i++) {
			if((tek->text[i] == '\n') && (i < (int)strlen(tek->text)-1))
				fputc('|',konvertovan);
			else
				fputc(tek->text[i],konvertovan);
		}
		tek = tek->sled;
	}
	brisi_listu(lista);
}

// Funkcija 'vreme' vrsi upis vremena pravilnog formatau SRT
// npr. "01:16:22,103"
// Poziv funkcije obavlja se iz:
//   - funkcije 'sub_u_srt'
void vreme(double vreme, FILE *konvertovan) {
	double a;
	fprintf(konvertovan, "%02.0lf:", vreme/3600); // sati
	fprintf(konvertovan, "%02d:", (int)(vreme/60)%60); // minuti
	fprintf(konvertovan, "%02d,", (int)vreme%60); // sekunde
	
	// Funkcija modf -> mnozi se razlomljeni deo vremena sa hiljadu i smesta ceo deo
	// dobijenog broja u promenljivu a.
	modf((vreme-floor(vreme))*1000, &a);
	fprintf(konvertovan, "%03.0lf", a); // razlomljeni deo
}

// Funkcija 'sub_u_srt', kao i funkcija 'srt_u_sub' formira jednostruko ulancanu listu
// sa podacima iz datoteke, ovaj put iz datoteke SUB. Takodje, u slucaju loseg formata
// napusta se program. Ako je format dobar, vrsi se upis u SRT datoteku.
// Poziv funkcije obavlja se iz:
//   - funkcije 'konverzija'
// Funkcija poziva:
//   - funkciju 'dodela_memorije'
//   - funkciju 'brisi_listu'
//   - funkciju 'zatvaranje_datoteka'
//   - funkciju 'greska'
//   - funkciju 'vreme'
void sub_u_srt(FILE *izvorni, FILE *konvertovan) {
	char red[MAXCHAR*2-1],frejm[6+1];
	int i, j, k = 1, br_frejmova, zatvorena, otvorena;
	Titl *nov = NULL, *lista = NULL, *tek = NULL;
	printf("Broj frejmova u sekundi: ");
	scanf("%d",&br_frejmova);
	while(fgets(red,MAXCHAR*2-2,izvorni) != NULL) {	// Ucitavanje se vrsi do kraja datoteke.
		if(strcmp(red,"\n")==0)	// Ucitavanje se prekida i kod praznog reda.
			break;

		nov = (Titl *)dodela_memorije(1,TITL);
		nov->text = NULL;	// da bi se pravilno oslobodila memorija

		// Postavljanje ostatka elemenata ulancane liste
		if(lista == NULL)
			lista = nov;
		else
			tek->sled = nov;
		tek = nov;

		zatvorena = otvorena = 0;
		
		// Provera formata -> red mora imati po dve otvorene i zatvorene 
		// uglaste zagrade {}.
		for(i = 0; i < (int)strlen(red); i++) {
			if(red[i] == '}')	zatvorena++;
			else if(red[i] == '{') otvorena++;
		}
		if(zatvorena != 2 || otvorena != 2) {
			brisi_listu(lista);
			zatvaranje_datoteka(izvorni, konvertovan);
			greska(3);
		}
		
		i = 1, j = 0;
		while(red[i] != '}')
			frejm[j++] = red[i++]; // Izdvajaju se frejmovi
		frejm[j] = '\0';
		nov->vrem_poc = atoi(frejm) / (double)br_frejmova;

		i += 2, j = 0;
		while(red[i] != '}')
			frejm[j++]=red[i++]; // Izdvajaju se frejmovi.
		frejm[j] = '\0';
		nov->vreme_kraj = atoi(frejm) / (double)br_frejmova;
		
		// U polje strukture se smesta tekst jednog titla.
		nov->text = (char *)dodela_memorije((int)strlen(red)-i,CHAR);
		for(j=i+1; j < (int)strlen(red); j++)
			if(red[j] == '|')
				nov->text[j-i-1] = '\n';
			else
				nov->text[j-i-1] = red[j];
		nov->text[j-i-1] = '\0';
		
		nov->br_titla = k++; // broj titla
		nov->sled = NULL;
	}
	tek = lista;
	
	// Upis podataka u datoteku.
	while(tek) {
		fprintf(konvertovan, "%d\n", tek->br_titla);
		vreme(tek->vrem_poc, konvertovan);
		fprintf(konvertovan, " --> ");
		vreme(tek->vreme_kraj, konvertovan);
		fprintf(konvertovan, "\n%s\n", tek->text);
		tek = tek->sled;
	}
	brisi_listu(lista);
}

// Funkcija 'zatvaranje_datoteka' zatvara datoteke
// Poziv funkcije obavlja se iz:
//   - funkcije 'srt_u_sub'
//   - funkcije 'sub_u_srt'
//   - funkcije 'main'
void zatvaranje_datoteka(FILE *izvorni, FILE *konvertovan) {
	fclose(izvorni);
	fclose(konvertovan);
}

// Funkcija 'poruka' daje obavestenje na ekranu da je konverzija uspesno obavljena
// Pozvi funkcije obavlja se iz:
//   - funkcije 'main'
void poruka() {
	printf("Konverzija uspesno obavljena.\n\n");
}