/* Laboratorio de Estruturas de Dados
   Professor: Vicente
   Alunos: Ricardo José e Leonardo César
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define condicao (strcmp(string,"+") && strcmp(string,"-") && strcmp(string,"*") && strcmp(string,"d") && strcmp(string,"=") && (terminar!=EOF))
#define tela printf("________________________________________________________________________\n")
//Estrutura que representa os elementos da lista
typedef struct _no{
    //Inteiro que guarda o coeficiente
    int coeficiente;
    int *grau;
    struct _no *prox;
}*no;
//Funcao para verificar se a lista esta vazia
int vazia(no cabeca){
    if (cabeca->prox==NULL)
        return 1;
    else
        return 0;
}
//Cria uma lista vazia
no lista_vazia(void){
    //Cria um sentinela para facilitar os algoritmos de inserção e remoção
    no sentinela=(no)malloc(sizeof(struct _no));
    //Prox de sentinela aponta para nada
    sentinela->prox=NULL;
    return sentinela;
}
//Funcao para retornar a primeira posicao valida da lista
no inicio(no cabeca){
    return cabeca->prox;
}
//Funcao para retornar o endereco do proximo elemento da lista
no avancar(no lista){
    return lista->prox;
}
//Percorre a lista até uma determinada posicao
no percorrer(no cabeca,int posicao){
    int i;
    //chama avança na lista a cada iteração
    for (i=0;i<posicao;i++){
        cabeca=avancar(cabeca);
    }
    return cabeca;
}
//Funcao para remover apos um determinado no
int remover_apos(no lista){
    no aux=avancar(lista);
    //Se a posicao solicitada for nula a funcao retorna 0
    if (aux==NULL)
        return 0;
    //_no->prox aponta para o proximo de aux    
    lista->prox=avancar(aux);
    //Libera aux
    free(aux);
    return 1;
}

//Funcao para inserir apos um no da lista
void inserir_apos(no lista,int coeficiente,int *vetor,int tamanho_vetor){
    int i;
    //Aloca espaco para um novo no
    no novo=(no)malloc(sizeof(struct _no));
    //Aloca espaco para o vetor de inteiros dentro do no
    novo->grau=(int*)malloc(sizeof(int)*tamanho_vetor);
    //Prox de novo aponta para o proximo da no atual
    novo->prox=avancar(lista);;
    //Novo recebe coeficiente
    novo->coeficiente=coeficiente;
    //Grau vai receber o vetor de inteiros
    for(i=0;i<tamanho_vetor;i++)
        novo->grau[i]=vetor[i];
    //Lista atual aponta para o novo no
    lista->prox=novo;
}
//Funcao para derivar um no da lista
void derivar(no cabeca,int var){
    //Um no auxiliar para guardar a posicao anterior
    no anterior=cabeca;
    //Enquanto o no nao apontar para nulo
    while ((cabeca=avancar(cabeca))!=NULL){
        //Mutiplica o coeficiente pelo grau da variavel escolhida
        cabeca->coeficiente=cabeca->coeficiente*cabeca->grau[var-1];
        //Decrementa o grau da variavel
        cabeca->grau[var-1]--;
        //Se o coeficiente for igual a 0, retira o no da lista
        if(cabeca->coeficiente==0){
            //Vai para a posicao anterior da lista
            cabeca=anterior;
            //Remove a posicao atual
            remover_apos(anterior);
        }
        else
	    //Avanca com o no auxiliar
            anterior=avancar(anterior);
    }
}
//Funcao para multiplicar a lista por um termo do polinomio
void multiplicar(no cabeca,int coeficiente,int *vetor, int num_var){
    no anterior=cabeca;//No anterior auxiliar para remoção
    int i;
    //Enquanto o no nao apontar para nulo
    while ((cabeca=avancar(cabeca))!=NULL){
        //Mutiplica o coeficiente pelo coeficiente lido do arquivo
        cabeca->coeficiente=cabeca->coeficiente*coeficiente;
        for (i=0;i<num_var;i++)
            //Soma o grau das variaveis com  a do arquivo recebido
            cabeca->grau[i]+=vetor[i];
        //Se o coeficiente for igual a 0 remove da lista
        if (cabeca->coeficiente==0){
            cabeca=anterior;
            remover_apos(anterior);
        }
        else
            anterior=avancar(anterior);
    }
}
//Funcao que soma/subtrai com a primeira sequencia de grau de variaveis que encontrar. Senao aloca novo local pra ele na lista
int soma_sub(no cabeca,int coeficiente,int *vetor,int num_var,char opcao){
    no anterior=cabeca;//No auxiliar anterior para possivel remoção
    int igual=0;//Variavel para verificar a igualdade de grau dos termos
    int i;
    while (cabeca->prox!=NULL){
        cabeca=avancar(cabeca);
        for (i=0;i<num_var;i++){
            if (vetor[i]==cabeca->grau[i])
                igual++;
        }
        //Se o grau das variaveis for igual, entao soma
        if (igual==num_var){
            switch (opcao){
                case '+':
                    cabeca->coeficiente+=coeficiente;
                    break;
                case '-':
                    cabeca->coeficiente-=coeficiente;
            }
            //Se o coeficiente for igual a 0 remove da lista
            if (cabeca->coeficiente==0){
                cabeca=anterior;
                remover_apos(anterior);
            }
            else
                anterior=avancar(anterior);
            
            return 1;
        }
    }
    //Se nao encontrar um termo do polinomio com o mesmo grau, entao insere no fim da lista
    switch (opcao){
        case '+':
       		inserir_apos(cabeca,coeficiente,vetor,num_var);
                break;
        case '-':
        	inserir_apos(cabeca,-coeficiente,vetor,num_var);
    }
    //Verifica se é 0 e remove
    if(cabeca->prox->coeficiente==0)
        remover_apos(cabeca);
    else
        anterior=avancar(cabeca);
    
    return 1;
}     
//Funcao para simplificar os termos e eliminar os nos com 0   
void simplificar(no cabeca,int num_var){
    int i,igual=0;;
    no base=cabeca;
    no aux=cabeca;
    //Enquanto o no nao apontar para nulo
    while ((base=avancar(base))!=NULL){
        no anterior=base;
        cabeca=base;
        while ((cabeca=avancar(cabeca))!=NULL){
            for (i=0;i<num_var;i++){
                if (cabeca->grau[i]==base->grau[i])
                    igual++;
            }
            if (igual==num_var){
                base->coeficiente+=cabeca->coeficiente;
                //Vai para a posicao anterior da lista
                cabeca=anterior;
                //Remove a posicao atual
                remover_apos(anterior);
            }	
            else
                anterior=avancar(anterior);
        }
	//Se o coeficiente for igual a 0 remove da lista
	if (base->coeficiente==0){
		base=aux;
		remover_apos(aux);
	}
	else
		aux=avancar(aux);	
    }
}
//Funcao para imprimir os valores da lista na tela
void imprimir_lista(no cabeca,int num_var){
	int i;
	//Se a lista for vazia, imprime 0 na tela
	if (vazia(cabeca)) printf("0");
	while ((cabeca=avancar(cabeca)) != NULL){
		printf("%d",cabeca->coeficiente);
		for (i=0;i<num_var;i++){
			if (cabeca->grau[i]!=0)
				printf("(x%d^%d)",i+1,cabeca->grau[i]);
		}
		(cabeca->prox!=NULL)?printf(" + "):printf(".");
	}
	printf("\n");
}
//Funcao para imprimir na tela um termo do polinomio
void imprimir_termo(int coeficiente,int * vetor,int num_var){
	int i;
	printf(" termo do polinomio:");
	printf("%d",coeficiente);
	for (i=0;i<num_var;i++){
		if (vetor[i]!=0)
			printf("(x%d^%d)",i+1,vetor[i]);
	}
	printf("\n");
}
//Funcao para escrever no arquivo a lista 
void escrever_arquivo(no cabeca,FILE *fp,int num_var){
	int i;
	if (vazia(cabeca)){
		fprintf(fp,"%d\n",0);
	}
	while((cabeca=avancar(cabeca))!=NULL){
		fprintf(fp,"%d\n",cabeca->coeficiente);
		for (i=0;i<num_var;i++){
			fprintf(fp,"%d ",cabeca->grau[i]);
		}
		fprintf(fp,"\n");
	}
}
//Funcao principal
int main(int argc,char **argv){
	if (argc!=2){
		printf("Digite na linha de comando o nome do arquivo a ser lido\n");
        exit(0);
   	}
    
	char string[20];
	no cabeca=lista_vazia();
    no temp=cabeca;
    FILE *entrada,*saida;
    int num_var,*vetor,coeficiente,i,j,ordem,terminar=1;;
    
    if(!(entrada=fopen(argv[1],"r"))){
        printf("\nO arquivo de leitura nao pode ser aberto\n");
        exit(0);
    }
    saida=fopen("pol.out","w");
	
   	//Le do arquivo o numero de variaveis e o coeficiente como uma string
   	fscanf(entrada,"%d\n%s",&num_var,string);
   	//Aloca posicoes para um vetor
   	vetor=(int*)malloc(sizeof(int)*num_var);
	fprintf(saida,"%d\n",num_var);
	
	//Guarda na lista um vetor base
	while condicao{
		coeficiente=atoi(string);
		for (i=0;i<num_var;i++){
			fscanf(entrada,"%d",&vetor[i]);
		}
		inserir_apos(temp,coeficiente,vetor,num_var);
		temp=avancar(temp);
		terminar=fscanf(entrada,"%s",string);
	}
	tela;
	printf("\nO polinomio inicial eh: ");
	imprimir_lista(cabeca,num_var);
	tela;
	
	//A partir daqui realiza as operacoes solicitadas sobre os elementos da lista base
	while (terminar!=EOF){
		if (!strcmp(string,"+")){//Soma
			terminar=fscanf(entrada,"%s",string);
			while condicao{
				coeficiente=atoi(string);
				for (i=0;i<num_var;i++){
					fscanf(entrada,"%d",&vetor[i]);
				}
				soma_sub(cabeca,coeficiente,vetor,num_var,'+');
				printf("\nSoma com o");
				imprimir_termo(coeficiente,vetor,num_var);
				printf("Resultado: ");
				imprimir_lista(cabeca,num_var);
				tela;
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"d")){//Derivada
			terminar=fscanf(entrada,"%s",string);
			while condicao{
				ordem=atoi(string);
				derivar(cabeca,ordem);
				printf("\nDerivada em relacao ao termo X%d: ",ordem);
				imprimir_lista(cabeca,num_var);
				tela;
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"-")){//Subtracao
			terminar=fscanf(entrada,"%s",string);
			while condicao{
				coeficiente=atoi(string);
				for (i=0;i<num_var;i++){
					fscanf(entrada,"%d",&vetor[i]);
				}
				soma_sub(cabeca,coeficiente,vetor,num_var,'-');
				printf("\nSubtracao com o");
				imprimir_termo(coeficiente,vetor,num_var);
				printf("Resultado: ");
				imprimir_lista(cabeca,num_var);
				tela;
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"*")){//Multiplicacao
			terminar=fscanf(entrada,"%s",string);
			while condicao{
				coeficiente=atoi(string);
				for (i=0;i<num_var;i++){
					fscanf(entrada,"%d",&vetor[i]);
				}
				multiplicar(cabeca,coeficiente,vetor,num_var);
				printf("\nMultiplicacao com o");
				imprimir_termo(coeficiente,vetor,num_var);
				printf("Resultado: ");
				imprimir_lista(cabeca,num_var);
				tela;
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"=")){//Simplificacao
			simplificar(cabeca,num_var);
			printf("\nSimplificacao :");
			imprimir_lista(cabeca,num_var);
			tela;
			terminar=fscanf(entrada,"%s",string);
		}
	}		
	printf("\nO resultado final eh:");
	imprimir_lista(cabeca,num_var);
	escrever_arquivo(cabeca,saida,num_var);
   	//Fecha os arquivos
   	fclose(entrada);
   	fclose(saida);
   
   	return 0;
}
