/*
 * mpserver.c
 * 
 * Copyright 2025 osboxes <osboxes@osboxes>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 * Ejercicio 5 del TP III 
 * Programa servidor
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <signal.h>

// datos globales

typedef struct msg {
	long mtype;       /* message type, must be > 0 */
	char mtext[128];  /* message data */
} msg;

typedef struct cmd {
	int pid;
	int nreg;
	char descr[100];
	int ret;
} cmd;

typedef struct reg {
	unsigned char estado;
	char descr[100];
} reg;

FILE *f = NULL;
char archivo[256];
int msgid = -1;
int nlocks = 0;
pid_t *locks = NULL;

//prototipo de funciones

void salir(int);
int parsing(char *,cmd *);
int parsing2(char *,cmd *);
int proceso(cmd *);

int main(int argc, char **argv) {
	if ( argc != 2 ) { 
		printf("Forma de Uso:\n./mpserver <archivo>\n");
		return 1;
	}
	
	// abre la cola de mensajes
	msgid = msgget(0xA,0);
	// si no pudo abrir la cola de mensajes, la crea
	if ( msgid == -1 ) msgid = msgget(0xA,IPC_CREAT|IPC_EXCL|0600);
	printf("msgid = %d\n",msgid);
	
	signal(SIGINT,salir); // Ctrl+C
	
	strcpy(archivo,argv[1]);
	f = fopen(archivo,"r+b");
	if ( f == NULL ) {
		// archivo no existe o no pude abrir, intento crearlo
		reg r;
		memset(&r,0,sizeof(reg));
		f = fopen(archivo,"wb");
		if ( f != NULL ) {
			int i;
			for(i=0;i<1000;i++) fwrite(&r,sizeof(reg),1,f);
			printf("Archivo [%s] creado!\n",archivo);
			fclose(f);
			f = fopen(archivo,"r+b");
		} else {
			printf("Error!, imposible crear archivo [%s]\n",archivo);
			return 2;
		}
	}
	// inicializo vector de locks
	fseek(f,0L,SEEK_END); // EOF
	nlocks = ftell(f) / sizeof(reg);	
	locks = (pid_t *) malloc(sizeof(pid_t)*nlocks);
	memset(locks,0,sizeof(pid_t)*nlocks); // nlocks=NULL
	printf("Hay %d registros!\n",nlocks);
	
	// proceso lector
	msg mi,mo;
	mi.mtype = 1L;
	cmd c;
	do {
		memset(mi.mtext,0,128);
		memset(&c,0,sizeof(cmd));
		msgrcv(msgid,&mi,128,1L,0);
		printf("Recibi [%s]\n",mi.mtext);
		// parsing de los comandos de los clientes
		if ( parsing(mi.mtext,&c) ) {
			// proceso comando
			printf("Parsing: pid: %d reg: %d descr: [%s]\n",c.pid,c.nreg,c.descr);
			proceso(&c);
			printf("Proceso: pid: %d reg: %d descr: [%s] ret: %d\n",c.pid,c.nreg,c.descr,c.ret);
			// envio respuesta del comando al cliente en tipo <pid>
			mo.mtype = (long) c.pid;
			snprintf(mo.mtext,128,"%d,%d,%s",c.ret,c.nreg,c.descr);
			msgsnd(msgid,&mo,128,0);
		} else {
			printf("Error! en parsing cmd [%s]\n",mi.mtext);
			// si tengo pid envio error para evitar que cliente cuelgue!
			if ( c.pid > 1 ) {
				mo.mtype = (long) c.pid;
				snprintf(mo.mtext,128,"0,-1,Error parsing");
				msgsnd(msgid,&mo,128,0);
			}
		}
 	} while(strncmp(mi.mtext,"0,0,fin",7) != 0);
	
	salir(0);
	return 0;
}

// salgo de la aplicacion
void salir(int signo) {
	printf("Saliendo...\n");
	if ( f != NULL ) fclose(f);
	// borra la cola de mensaje!
	printf("Borro cola de mensajes 0xA...\n");
	msgctl(msgid,IPC_RMID,NULL);
	// libero memoria
	if ( locks != NULL ) free(locks);
	exit(0);
} 

//parsing basado en sscanf()
int parsing(char *m,cmd *c) {
	int n = sscanf(m,"%d,%d,%99c",&c->pid,&c->nreg,c->descr);
	if ( n == 3 ) {
		if ( c->pid <= 1 ) return 0;
		if ( c->nreg < -1 ) return 0;
		if ( strlen(c->descr) <= 0 ) return 0;
		return 1;
	} else {
		return 0;
	}
}

//parsing manual, sin hacer uso de libreria string
int parsing2(char *m,cmd *c) {
	char tmp[128];
	int i=0,w=0;
	while(i<128 && m[i] && m[i] != ',' && m[i] >= '0' && m[i] <= '9' ) { tmp[w]=m[i];i++;w++; }
	tmp[w]='\0';
	c->pid = atoi(tmp);
	if ( c->pid <= 0 ) return 0;
	w=0;
	if ( m[i] != ',' ) return 0;
	i++;
	while(i<128 && m[i] && m[i] != ',' && m[i] >= '0' && m[i] <= '9' ) { tmp[w]=m[i];i++;w++; }
	tmp[w]='\0';
	c->nreg = atoi(tmp);
	if ( m[i] != ',' ) return 0;
	w=0;
	while(i<128 && m[i] ) { tmp[w]=m[i];i++;w++; }
	tmp[w]='\0';
	strcpy(c->descr,tmp);
	return 1;
}

// proceso comando recibido e indico retorno en c->ret
int proceso(cmd *c) {
	reg r;
	memset(&r,0,sizeof(reg));
	c->ret = 0;
	fseek(f,0L,SEEK_END);
	int n = ftell(f) / sizeof(reg);
	if ( c->nreg == -1 ) {
		// alta
		fseek(f,0L,SEEK_END);
		c->nreg = ftell(f) / sizeof(reg);
		r.estado = 1;
		strncpy(r.descr,c->descr,100);
		fwrite(&r,sizeof(reg),1,f);
		c->ret = 1;
		locks = (pid_t *) realloc(locks,sizeof(pid_t)*(nlocks+1));
		memset(locks+nlocks,0,sizeof(pid_t));
		nlocks++;
	} else if ( strncmp(c->descr,"borrar",6) == 0 ) {
		// baja
		if ( c->nreg >= 0 && c->nreg < n ) {
			if ( *(locks+c->nreg) && *(locks+c->nreg) != c->pid ) {
				c->ret = 0;
				snprintf(c->descr,100,"Error,reg %d lockeado por PID %d",c->nreg,*(locks+c->nreg));
			} else { 
				fseek(f,c->nreg*sizeof(reg),SEEK_SET);
				fread(&r,sizeof(reg),1,f);
				switch(r.estado) {
					case 0:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta vacio",c->nreg); 
						break;
					case 1:
						fseek(f,c->nreg*sizeof(reg),SEEK_SET);
						r.estado = 2;
						fwrite(&r,sizeof(reg),1,f);
						c->ret = 1;
						break;
					case 2:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta borrado",c->nreg); 
						break;
					default:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d estado %d erroneo",c->nreg,r.estado); 
				}
			}
		} else {
			c->ret = 0;
			snprintf(c->descr,100,"Error,reg %d invalido (%d-%d)",c->nreg,0,n); 
		}
	} else if ( strncmp(c->descr,"leer",4) == 0 ) {
		// leer
		if ( c->nreg >= 0 && c->nreg < n ) {
			if ( *(locks+c->nreg) && *(locks+c->nreg) != c->pid ) {
				c->ret = 0;
				snprintf(c->descr,100,"Error,reg %d lockeado por PID %d",c->nreg,*(locks+c->nreg));
			} else { 
				fseek(f,c->nreg*sizeof(reg),SEEK_SET);
				fread(&r,sizeof(reg),1,f);
				switch(r.estado) {
					case 0:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta vacio",c->nreg); 
						break;
					case 1:
						strncpy(c->descr,r.descr,100);
						c->ret = 1;
						break;
					case 2:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta borrado",c->nreg); 
						break;
					default:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d estado %d erroneo",c->nreg,r.estado); 
				}			
			}
		} else {
			c->ret = 0;
			snprintf(c->descr,100,"Error,reg %d invalido (%d-%d)",c->nreg,0,n); 
		}
	} else if (strncmp(c->descr,"desborrar",9) == 0) {
		// des-baja
		if ( c->nreg >= 0 && c->nreg < n ) {
			if ( *(locks+c->nreg) && *(locks+c->nreg) != c->pid ) {
				c->ret = 0;
				snprintf(c->descr,100,"Error,reg %d lockeado por PID %d",c->nreg,*(locks+c->nreg));
			} else { 
				fseek(f,c->nreg*sizeof(reg),SEEK_SET);
				fread(&r,sizeof(reg),1,f);
				switch(r.estado) {
					case 0:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta vacio",c->nreg); 
						break;
					case 1:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta ocupado",c->nreg); 
						break;
					case 2:
						fseek(f,c->nreg*sizeof(reg),SEEK_SET);
						r.estado = 1;
						fwrite(&r,sizeof(reg),1,f);
						c->ret = 1;
						strncpy(c->descr,r.descr,100);
						break;
					default:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d estado %d erroneo",c->nreg,r.estado); 
				}
			}
		} else {
			c->ret = 0;
			snprintf(c->descr,100,"Error,reg %d invalido (%d-%d)",c->nreg,0,n); 
		}
	} else if (strncmp(c->descr,"lock",4) == 0) {
		// lock
		if ( c->nreg >= 0 && c->nreg < n ) {
			if ( *(locks+c->nreg) ) {
				c->ret = 0;
				snprintf(c->descr,100,"Error,reg %d ya esta locked por %d",c->nreg,*(locks+c->nreg)); 
			} else {
				// leo registro
				fseek(f,c->nreg*sizeof(reg),SEEK_SET);
				fread(&r,sizeof(reg),1,f);
				switch(r.estado) {
					case 0:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta vacio",c->nreg); 
						break;
					case 1:
						strncpy(c->descr,r.descr,100);
						c->ret = 1;
						*(locks+c->nreg) = c->pid;
						break;
					case 2:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d esta borrado",c->nreg); 
						break;
					default:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d estado %d erroneo",c->nreg,r.estado); 
				}
			}
		} else {
			c->ret = 0;
			snprintf(c->descr,100,"Error,reg %d invalido (%d-%d)",c->nreg,0,n-1); 
		}
	} else if (strncmp(c->descr,"unlock",6) == 0) {
		// unlock
		if ( c->nreg >= 0 && c->nreg < n ) {
			if ( *(locks+c->nreg) ) {
				if ( c->pid == *(locks+c->nreg) ) {
					// solo puedo deslockear lo lockeado por mi mismo
					// leo registro
					fseek(f,c->nreg*sizeof(reg),SEEK_SET);
					fread(&r,sizeof(reg),1,f);
					switch(r.estado) {
						case 0:
							c->ret = 0;
							snprintf(c->descr,100,"Error,reg %d esta vacio",c->nreg); 
							break;
						case 1:
							strncpy(c->descr,r.descr,100);
							c->ret = 1;
							memset(locks+c->nreg,0,sizeof(pid_t));
							break;
						case 2:
							c->ret = 0;
							snprintf(c->descr,100,"Error,reg %d esta borrado",c->nreg); 
							break;
						default:
							c->ret = 0;
							snprintf(c->descr,100,"Error,reg %d estado %d erroneo",c->nreg,r.estado); 
					}
				} else {
					c->ret = 0;
					snprintf(c->descr,100,"Error,reg %d esta lockeado por %d",c->nreg,*(locks+c->nreg)); 
				}
			} else {
				c->ret = 0;
				snprintf(c->descr,100,"Error,reg %d no esta lockeado por nadie!",c->nreg); 
			}
		} else {
			c->ret = 0;
			snprintf(c->descr,100,"Error,reg %d invalido (%d-%d)",c->nreg,0,n-1); 
		}
	} else {
		// cambio
		if ( c->nreg >= 0 && c->nreg < n ) {
			if ( *(locks+c->nreg) && *(locks+c->nreg) != c->pid ) {
				c->ret = 0;
				snprintf(c->descr,100,"Error,reg %d lockeado por PID %d",c->nreg,*(locks+c->nreg));
			} else { 
				fseek(f,c->nreg*sizeof(reg),SEEK_SET);
				fread(&r,sizeof(reg),1,f);
				switch(r.estado) {
					case 0:
					case 2:
						r.estado = 1;
					case 1:
						strncpy(r.descr,c->descr,100);
						fseek(f,c->nreg*sizeof(reg),SEEK_SET);
						fwrite(&r,sizeof(reg),1,f);
						c->ret = 1;
						break;
					default:
						c->ret = 0;
						snprintf(c->descr,100,"Error,reg %d estado %d erroneo",c->nreg,r.estado); 
				}			
			}
		} else {
			c->ret = 0;
			snprintf(c->descr,100,"Error,reg %d invalido (%d-%d)",c->nreg,0,n); 
		}
	}
	return c->ret;
}
