/*
 * This software is to enable displaying Axiom output on the Firefox 
 * web browser.  I got started on it by looking at tm_axiom.c by Andrey Grozin.
 * As such it falls under the GNU general public license and comes WITHOUT ANY
 * WARRANTY WHATSOEVER.
 * COPYRIGHT : (C) 2006 Arthur C. Ralfs
*/
/*
 * The purpose of this program is to start up axiom and connect to it
 * with a pair of pipes, axcom.axin and axcom.axout, and then setup a 
 * socket to listen for commands.
*/

#include <netdb.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/un.h>
#include <stdlib.h>
#include <regex.h>
#include <string.h>

#define PORT 9876
#define ANSIZE 65536
#define QSIZE 512
#define TYPESIZE 512

regex_t endprompt;
regex_t beginprompt;
regex_t ansax;

struct axcom
{
/* Contains pointers to input and output pipes connecting to axiom. */
    FILE * axin;
    FILE * axout;
};
struct axcom start_ax();
int ax_stop(FILE * );
int ch(FILE *);
char * iline(FILE *);
void get_ans(char *, FILE *);
void axiom(char *, char *, struct axcom *);
int get_clifd();
void fatal(char *);
int get_prompt(FILE * axout);
int stepnum = 1;

int make_regs()
{
    if( regcomp(&endprompt, "[(]([0-9]+)[)] ->$", REG_EXTENDED) < 0 )
    { 
	printf("regex compilation error: endprompt\n");
	return -1;
    }
    if( regcomp(&beginprompt, "^[(][0-9]+[)] ->", REG_EXTENDED) < 0 )
    { 
	printf("regex compilation error: beginprompt\n");
	return -1;
    }
    return 0;
}

int get_clifd(int acc, struct sockaddr_in * sockp, int * socklenp)
{
/*  */
    int cli;
    cli = accept(acc, (struct sockaddr *) sockp, socklenp);
    return cli;
}

int make_serv(struct sockaddr_in * sockp, int socklen)
{
/*  */
    int acc;
    int opt=1;
    acc = socket(AF_INET, SOCK_STREAM, 0);
    (*sockp).sin_family = AF_INET;
    (*sockp).sin_port = htons(PORT);
    (*sockp).sin_addr.s_addr = htonl(INADDR_ANY);
    bind(acc, (struct sockaddr *) sockp, socklen);
    setsockopt(acc, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt));
    listen(acc, 5);
    return acc;
}



struct axcom  start_ax()
{
/*  Starts axiom and connects with input and output pipes. */ 
    int p1[2], p2[2];
    struct axcom  axcom;


    if (pipe(p1)<0) fatal("failed to create pipe p1");
    if (pipe(p2)<0) fatal("failed to create pipe p2");
    switch (fork())
    { 
	case -1: fatal("failure of fork in start_ax");

	case 0: // Axiom 
	    dup2(p1[1],1); close(p1[1]); close(p1[0]);
	    dup2(p2[0],0); close(p2[0]); close(p2[1]);
	    execlp("AXIOMsys","AXIOMsys","-noclef",(char *) NULL);
	    fatal("failure to exec AXIOMsys in start_ax");

	default: // parent 
//	    printf("return from fork in start_ax\n");
	    close(p1[1]); close(p2[0]);
	    axcom.axin=fdopen(p2[1],"w"); axcom.axout=fdopen(p1[0],"r");
    }
    setvbuf(axcom.axin,NULL,_IOLBF,QSIZE);
//    setvbuf(axcom.axout,NULL,_IOFBF,ANSIZE);
/* Need to check initial startup messages here and make sure there is a
 * prompt.
 */
    get_prompt(axcom.axout);
/* Send a few commands to get the right output from axiom, i.e. mathml format. */
    fputs(")set output mathml on\n", axcom.axin); fflush(axcom.axin);
    get_prompt(axcom.axout);
    fputs(")set output algebra off\n", axcom.axin); fflush(axcom.axin);
    get_prompt(axcom.axout);
    fputs(")set messages autoload off\n", axcom.axin); fflush(axcom.axin);
    get_prompt(axcom.axout);
    fputs(")set quit unprotected\n", axcom.axin); fflush(axcom.axin);
    get_prompt(axcom.axout);

    return axcom;
}

int get_prompt(FILE * axout)
{
/* Make sure we've got a prompt by calling iline repeatedly. 
 * Is there redundancy here?  Seems that iline only returns with a prompt.
*/
    while (1)
    {
	if (iline(axout) == "PROMPT") return 0;
	printf("noprompt\n");
    }

}

void axiom(char * ques, char * ans, struct axcom * axcomp)
{
/*  Send axiom command and get axiom's answer. */
//    printf("sending question\n");
    fputs(ques, axcomp->axin);
    fputs("\n", axcomp->axin);
    fflush(axcomp->axin);
//    printf("axiom: question sent\n");
    get_ans(ans, axcomp->axout);
}



void get_ans(char * ans, FILE * axout)
{
/*  Retrieve axiom's answer. */
    char c;
    int i=0;

/*  Start getting characters adding them to a char buffer until a
 *  prompt is encountered. Return the pointer ans.
 *  Appears to be bug with axiom returning unnecessary prompts, for
 *  instance the command S:=[3*x**3+y+1=0,Y**2=4] at the prompt (n)->
 *  will reproduce the prompt (n)-> before the correct output and then
 *  produce the prompt (n+1)->.
 */

    while(1)
    {

	c=ch(axout);
	*(ans+i) = c; i++;
	printf("get_ans: while: %c\n",c);
	if (regexec( &endprompt, ans, 0, NULL, 0) == 0) 
	{
	printf("get_ans: while: 1\n");
	    if (regexec( &beginprompt, ans, 0, NULL, 0) != 0)
	    {
		return;
	    }
	}
    }
}



char * iline(FILE * axout)
{
/*  */
    int j=0, c;
    char prompt[]="-> ";
    while (1)
    {
//	printf("getting character\n");
	c=ch(axout);
//	printf("iline: character is: %c\n",c);
	if (c==prompt[j])
	    if (j==2) { return "PROMPT"; } else j++;
	else
	{
	    j=0;
	}
    }
}



int ch(FILE * axout)
{
/*  Gets one character from axiom output pipe and returns it. */
    char c;
    while (1)
    {
//	printf("ch getting\n");
	c=getc(axout);
//	printf("ch gotten: %c\n",c);
	if (c!='\r') return c;
    }
}



int ax_stop(FILE * axin)
{
/* Quits axiom. */
//    fputs(")set quit unprotected\n", axin);
    fputs(")quit\n", axin);
    return 0;
}



void fatal(char *mess)
{
/*  Take action upon fatal error, e.g. print out message and stop axiom. */

}

int lineno(char * texbuf, char * linenum)
{
/*  Pull the axiom line number out of axiom's answer and save 
 *  it in linenum.
 *  In some cases, e.g. p(0) == 1, it's a valid command that doesn't
 *  produce tex output, yet the step number increments.  Need to rewrite
 *  this to get the step numbers by examining the prompts.
 */  
    while(*texbuf != '\0') {
    if(*texbuf++ == '\\') {
	if(*texbuf++ == 'l') {
	    if(*texbuf++ == 'e') {
		if(*texbuf++ == 'q') {
		    if(*texbuf++ == 'n') {
			if(*texbuf++ =='o') {
			    if(*texbuf++ == '(') {
				while(*texbuf != ')') { *linenum++ = *texbuf++; }
				break;
			    }}}}}}}
    }
    *linenum = '\0';
    return 0;
}



int premath(char * ans)
{
/* ans[] contains the string received from axiom, I pull the
 * information out and then write it back in mml and html.
 */
    regmatch_t matchptr[5];
    regex_t mathbegin;
    regex_t mathend;
    regex_t rmprompt;
    char mmlans[ANSIZE];
    char type[TYPESIZE];
    char axmessage[ANSIZE];
    char * cpto, * cpfrom;

/*  Matching something like:  <math... </math> stuff  OR  stuff */
    if( regcomp(&mathbegin, "(<math)", REG_EXTENDED) < 0 )
    { 
	printf("regex compilation error: ansax\n");
	return -1;
    }
    if( regcomp(&mathend, "(</math>)", REG_EXTENDED) < 0 )
    { 
	printf("regex compilation error: ansax\n");
	return -1;
    }
    if( regcomp(&type, "(Type)", REG_EXTENDED) < 0 )
    { 
	printf("regex compilation error: ansax\n");
	return -1;
    }


    /* Test for mathml content.  The mathml string should be in
     * the 1st element of matchptr
     */
    if (  regexec( &mathbegin, ans, 2, matchptr, 0) == 0 )
    {
	printf("premath: mathml content ok\n");
	/* copy mathml output to mmlans */
	cpto = mmlans;
	cpfrom = ans+matchptr[1].rm_so;
	regexec(&mathend, ans, 2, matchptr, 0);
	while (cpfrom < ans+matchptr[1].rm_eo)
	{
	    if ( *cpfrom != '\n' )// omit newlines, mathml renderer doesn't seem to like them
	    {
		*cpto++ = *cpfrom++;
	    }
	    else {cpfrom++;}
	}
	*cpto++ = '\0';

	regexec(&type, ans, 2, matchptr, 0);
	cpto = type;
	cpfrom = ans+matchptr[1].rm_so;
	while( *cpfrom != '\n' ){ *cpto++ = *cpfrom++; }
	*cpto = '\0';

	/* now reassemble back into ans */
	memset(ans, 0, ANSIZE);
	cpto = ans;
	cpfrom = mmlans;
	while ( *cpfrom != '\0' ) {*cpto++ = *cpfrom++;}
	cpfrom = type;
	cpto = mempcpy(cpto, "<div class=\"type\">", 18);
	while ( *cpfrom != '\0' ) {*cpto++ = *cpfrom++;}
	cpto = mempcpy(cpto, "</div>", 7);
	cpto = mempcpy(cpto, "<br/>", 5);
	*cpto = '\0';
	
    }
    else
    {
//	printf("premath: no mathml content?\n");
	printf("premath: raw answer:\n%s\n",ans);
	/* Remove prompt from error message */
	if( regcomp(&rmprompt, "(.*)[(][0-9]+[)] ->", REG_EXTENDED) < 0 )
	{ 
	    printf("regex compilation error: rmprompt\n");
	    return -1;
	}

	regexec( &rmprompt, ans, 2, matchptr, 0);

	cpto = axmessage;
	cpto = mempcpy(cpto, "<div class=\"axerror\"><div>", 26);
	cpfrom = ans+matchptr[1].rm_so;
	while ( cpfrom < ans+matchptr[1].rm_eo )
	{ 
	    if ( *cpfrom =='\n' )
	    {
		cpto = mempcpy(cpto, "</div><div>", 11 );
		cpfrom++;
	    }
	    else
	    {
		*cpto++ = *cpfrom++;
	    }
	}
	cpto = mempcpy(cpto, "</div></div>", 12);
	*cpto = '\0';
	printf("premath: axmessage: \n%s\n", axmessage);
	cpto = ans;
	cpfrom = axmessage;
	while ( *cpfrom != '\0' ) { *cpto++ = *cpfrom++; }
	*cpto = '\0';
	printf("premath: answer before returning: \n%s\n", ans);
    }
    return 0;
}


int main(void)
{
    int cli;
    int acc;
    char ques[QSIZE];
    char ans[ANSIZE];
    struct axcom axcom;
    struct sockaddr_in sock;
    int socklen=sizeof(sock);
    /* Compile regular expressions */
    make_regs();
    /* set up server listening socket */
    acc = make_serv(&sock, socklen);

    /* set up axiom with pipes */
    printf("starting axiom\n");
    axcom = start_ax();
    printf("axiom started\n");
    while(1)
    {
	/* get client socket connection */
	cli = get_clifd(acc, &sock, &socklen);

	/* get command from client */
	memset(ques, 0, QSIZE);
	ques[0] = '?';
//	printf("questest: %c:\n", ques[0]);
	while(ques[0] == '?')
	{
	    recv(cli, ques, sizeof(ques), 0);
	}
	printf("main: axiom command: %s\n", ques);
	if (ques[0] == 'q')
	    if (ques[1] == 'u')
		if (ques[2] == 'i')
		    if (ques[3] == 't') break;
	/* get answer from axiom */
	memset(ans, 0, ANSIZE);
	axiom(ques, ans, &axcom);
	// premath does all transformations on the answer, when
	// ans comes back it's ready to send off
	printf("main: answer: \n%s\n",ans);
	premath(ans);

	printf("main: answer1: %s\n", ans);
//	printf("main check1\n");
	send(cli, ans, strlen(ans)+1, 0);

	close(cli);
    }


    //need to send quit command to axiom
    ax_stop(axcom.axin);
    return 0;
}
