#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define condicao (strcmp(string,"+") && strcmp(string,"-") && strcmp(string,"*") && strcmp(string,"d") && strcmp(string,"=") && (terminar!=EOF))
//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);
    }
}
void multiplicar(no cabeca,int coeficiente,int *vetor, int num_var){
    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];
    }
}
//Funcao que soma 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){
    int igual=0;
    int i;
    while (cabeca->prox!=NULL){
        cabeca=avancar(cabeca);
        for (i=0;i<num_var;i++){
            if (vetor[i]==cabeca->grau[i])
                igual++;
        }
        if (igual==num_var){
            switch (opcao){
                case '+':
                    cabeca->coeficiente+=coeficiente;
                    break;
                case '-':
                    cabeca->coeficiente-=coeficiente;
            }
            return 1;
        }
    }
    switch (opcao){
	case '+':
       		inserir_apos(cabeca,coeficiente,vetor,num_var);
                break;
        case '-':
        	inserir_apos(cabeca,-coeficiente,vetor,num_var);
        }
    
    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);
        }
	if (base->coeficiente==0){
		base=aux;
		remover_apos(aux);
	}
	else
		aux=avancar(aux);	
    }
}

void imprimir_lista(no cabeca,int num_var){
	int i;
	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]);
		}
		printf(" + ");
	}
	printf("\n");
}
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");
}
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;;
    
    	entrada=fopen(argv[1],"r");
    	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);
	
	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);
	}
	
	printf("\nO polinomio inicial eh: ");
	imprimir_lista(cabeca,num_var);
	
	while (terminar!=EOF){
		if (!strcmp(string,"+")){
			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);
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"d")){
			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);
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"-")){
			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);
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"*")){
			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);
				terminar=fscanf(entrada,"%s",string);
			}
		}
		if (!strcmp(string,"=")){
			simplificar(cabeca,num_var);
			printf("\nSimplificacao :");
			imprimir_lista(cabeca,num_var);
			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;
}
