/******************************************************************************
 * MODULE     : tm_mathematica.c
 * DESCRIPTION: Interface with Mathematica
 * COPYRIGHT  : (C) 2005  Andrey Grozin
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mathlink.h"

#define CLOSED 11L
#define PRE_NAME  "/plugins/mathematica/ps/pre"
#define POST_NAME "/plugins/mathematica/ps/post"
#define LOG "./log.mma"
// #define LOG_PS "./log.ps"
/* MODIFY THE EXTENSION TO EXPORT DIFFERENT FORMAT*/
#define FIG_NAME "./tmp.eps" 

#ifdef LOG
static FILE *log;
#endif
#ifdef LOG_PS
static FILE *psfile;
#endif

MLENV env =(MLENV)0;
MLINK link=(MLINK)0;
static size_t size=128;
static char *input,*pre_name,*post_name;
static char *mls_buf;
static int protect_hat=0;

static void texput(char *s, FILE* fid) {
  char c; int n,nl; char* pc;
  nl=0; pc=s;
  while( (c=*(s++)) ) {
    if (c=='\\') {
      c=*(s++);
      if ((c>='0')&&(c<='9')) {
	n=c-'0';
	while (1) {
	  c=*(s++);
	  if ((c<'0')||(c>'9')) {
	    if (n==0x0a) { putc(' ', fid); *(pc++) = ' '; nl=1; }
	    else { putc(n, fid); *(pc++) = n; }
	    if (c)
	      if (nl) {
		if (c=='>') nl=0;
		else if (c>' ') { putc(c, fid); *(pc++) = c; nl=1; }
		else if (protect_hat&&(c=='^')) { putc(' ', fid); *(pc++) = ' '; }
		else {putc(c, fid); *(pc++) = c;}
	      }
	    break;
	  } else n=8*n+(c-'0');
	}
	if (c=='\0') {*(pc++) = '\0'; break;}
      } else if (protect_hat&&(c=='^')) {putc(' ', fid); *(pc++) = ' ';}
      else {putc(c, fid); *(pc++) = c;}
    } else if (nl) {
      if (c=='>') nl=0;
      else if (c>' ') {
	if (protect_hat&&(c=='^')) { putc(' ', fid); *(pc++) = ' '; }
	else { putc(c, fid); *(pc++) = c;}
	nl=1;
      }
    } else if (protect_hat&&(c=='^')) { putc(' ', fid); *(pc++) = ' '; }
    else {putc(c, fid); *(pc++) = c;}
  }
}

static void psput(char *s) {
  char c; int n,l; char* pc;
  pc = s;
  while( ( c=*(s++) ) ) {
    if (c=='\\') {
      c=*(s++);
      if ((c>='0')&&(c<='7')) {
	l=0; n=c-'0';
	while (1) {
	  l++; c=*(s++);
	  if ((l>=3)||(c<'0')||(c>'7')) {
	    putchar(n);
	    *(pc++) = n;
#ifdef LOG_PS
	    fputc(n,psfile);
#endif
	    if (c) {
	      putchar(c);
	      *(pc++) = c;
#ifdef LOG_PS
	      fputc(c,psfile);
#endif
	    }
	    break;
	  } else n=8*n+(c-'0');
	}
	if (c=='\0') {*(pc++)='\0'; break;}
      } else {
	putchar(c);
	*(pc++) = c;
#ifdef LOG_PS
	fputc(c,psfile);
#endif
      }
    } else {
      putchar(c);
      *(pc++) = c;
#ifdef LOG_PS
      fputc(c,psfile);
#endif
    }
  }
}

static void prelude(char *name) {
  FILE *ps=fopen(name,"r");
  char(c);
  while (1) {
    c=getc(ps);
    if (c==EOF) break;
    putchar(c);
#ifdef LOG_PS
    fputc(c,psfile);
#endif
  }
  fclose(ps);
}

static void regularize(char* s) 
{
  /* delete '\\\012' and '\012>' */
  char c; char* pc;
  pc=s;
  while( (c=*(s++)) ) {
    if(c=='\\') {
      c=*(s++);
      if(c=='\\') { s+=5; }
      else if((c<='9')&&(c>='0')) {
	while((c=*(s++))) { if(c=='>'||c==' ') break; }
      }
    }
    else *(pc++) = c;
  }
  *pc = '\0';
}

static void eps_to_string()
{
  FILE* f_ps; int c;
  
  fputs("ps:", stdout); fflush(stdout);
  if( ( f_ps = fopen(FIG_NAME, "r") ) ) {
    while((c=fgetc(f_ps)) != EOF) {
      putchar(c);
    }
    putchar( '\0' ); putchar( '\n' );
    fclose(f_ps);
  } else {
    fputs("Cannot open tmp.eps\n", stdout);
  }
}

static void evaluate(int type, char* s)
{
  int pkt,more,non_ps,msg; long err;
  char *result,*symbol;
  /* FILE* f_log; */

  MLPutFunction(link, "EvaluatePacket", 1L);
  switch(type) {
  case -1: /* No way! */
    exit(1);
  case 0: /* TeX */
    MLPutFunction(link, "Print", 1L);
    MLPutFunction(link, "TeXForm", 1L);
    MLPutFunction(link, "ToExpression", 1L);
    MLPutString(link,s);
    break;
  case 1: /* Graphics */
    regularize(s); /* remove control characters */
    /*    
	  f_log = fopen("./fig.log", "w");
	  fputs(s, f_log);
	  fclose(f_log);  
    */
    MLPutFunction(link, "Export", 2L);
    MLPutString(link, FIG_NAME);
    MLPutFunction(link, "ToExpression", 1L);
    MLPutString(link, s);
    break;
  }
  MLEndPacket(link);

  more=1; non_ps=1; msg=0;
  do {
    switch (pkt=MLNextPacket(link)) {
    case RETURNPKT:
#ifdef LOG
      fputs("RETURNPKT@evaluate\n\n",log); fflush(log);
#endif
      more=0; break;
    case RETURNTEXTPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"RETURNTEXTPKT@evaluate: \"%s\"\n\n",result); fflush(log);
#endif
      MLReleaseString(link,result);
      more=0; break;
    case INPUTNAMEPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"INPUTNAMEPKT@evaluate: \"%s\"\n",result); fflush(log);
#endif
      MLReleaseString(link,result);
      break;
    case OUTPUTNAMEPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"OUTPUTNAMEPKT@evaluate: \"%s\"\n",result); fflush(log);
#endif
      MLReleaseString(link,result);
      break;
    case TEXTPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"TEXTPKT@evaluate: \"%s\"\n",result); fflush(log);
#endif
      if (msg) {
	fputs("{\\magenta ",stdout);
	protect_hat=1; texput(result, stdout);
	fputs("}\n\n",stdout);
	protect_hat=0; msg=0;
      } else {
	
	switch(type) {
	case 0: /* TeX */
	  fputs("$\\displaystyle ",stdout);
	  texput(result, stdout);
	  fputs("$",stdout);
	  break;
	case 1: /* graphics */
	  break;
	default:
	  fputs("Oops, nothing happened...\n", stdout);
	  break;
	}
      }
      MLReleaseString(link,result);
      break;
    case MESSAGEPKT:
      MLGetSymbol(link,(const char**)&symbol);
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"MESSAGEPKT@evaluate: \"%s\"  \"%s\"\n",symbol,result); fflush(log);
#endif
      MLReleaseSymbol(link,symbol);
      MLReleaseString(link,result);
      msg=1;
      break;
    case DISPLAYPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"DISPLAYPK@evaluate: \"%s\"\n",result); fflush(log);
#endif
      if (non_ps) {
	fputs("\2ps:",stdout);
#ifdef LOG_PS
	psfile=fopen(LOG_PS,"w");
#endif
	prelude(pre_name); non_ps=0;
      }
      psput(result);
      MLReleaseString(link,result);
      break;
    case DISPLAYENDPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"DISPLAYENDPKT@evaluate: \"%s\"\n",result); fflush(log);
#endif
      psput(result);
      prelude(post_name);
      fputs("\5{}{}\n\n",stdout);
#ifdef LOG_PS
      fclose(psfile);
#endif
      non_ps=1;
      MLReleaseString(link,result);
      break;
    case CALLPKT:
#ifdef LOG
      fputs("CALLPKT@evaluate\n",log); fflush(log);
#endif
      break;
    default:
#ifdef LOG
      fprintf(log,"UNKNOWN PACKET@evaluate: %1d\n",pkt); fflush(log);
#endif
      break;
    }
    MLNewPacket(link);
    err=MLError(link);
    if (err==CLOSED) {
      fputs("\\red The end\5",stdout);
      MLClose(link);
      MLDeinitialize(env);
      exit(0);
    } else if (err) {
      printf("\\red Error %ld: %s\5",err,MLErrorMessage(link));
      exit(1);
    }
  } while (more);

}

static void command(char *s) {
  int pkt,more,non_ps,msg, type, is_buf; long err;
  char *result,*symbol;
  fputs("\2latex:",stdout);
  MLPutFunction(link,"EvaluatePacket",1L);
  MLPutFunction(link,"Print",1L);
  //  MLPutFunction(link,"TeXForm",1L);
  MLPutFunction(link, "ExportString", 2L);
  MLPutFunction(link,"ToExpression",1L);
  MLPutString(link,s);
  MLPutString(link,"Text");
  MLEndPacket(link);
  more=1; non_ps=1; msg=0; type = 0; is_buf = 0;

  do {
    switch (pkt=MLNextPacket(link)) {
    case RETURNPKT:
#ifdef LOG
      fputs("RETURNPKT@command\n\n",log); fflush(log);
#endif
      more=0;
      break;
    case RETURNTEXTPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"RETURNTEXTPKT@command: \"%s\"\n\n",result); fflush(log);
#endif
      MLReleaseString(link,result);
      more=0;
      break;
    case INPUTNAMEPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"INPUTNAMEPKT@command: \"%s\"\n",result); fflush(log);
#endif
      MLReleaseString(link,result);
      break;
    case OUTPUTNAMEPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"OUTPUTNAMEPKT@command: \"%s\"\n",result); fflush(log);
#endif
      MLReleaseString(link,result);
      break;
    case TEXTPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"TEXTPKT@command: \"%s\"\n",result); fflush(log);
#endif
      if (msg) {
	fputs("{\\magenta ",stdout);
	protect_hat=1; texput(result, stdout);
	fputs("}\n\n",stdout);
	protect_hat=0; msg=0;
      } else {
	
	if( is_buf ) {
	  fputs("TEXTPKT more than once?\n", stdout);
	  break;
	}
	if( ! ( mls_buf = (char*)malloc(strlen(result)+1) ) ) {
	  fputs("Cannot allocate memory for the returning TEXT packet.\n", stdout);
	  break;
	}
	is_buf = 1;
	
	strcpy(mls_buf, result);
	//	fputs("$\\displaystyle ",stdout);
	char* pKeyword;
	if( (pKeyword=strstr(mls_buf, "Graphics")) )
	  if( (mls_buf - pKeyword) == 0 )  { /* begin with keyword "Graphics" */
	    type = 1; fputs("Graphics\n", stdout); break;
	  }
	  
	// texput(result, stdout);
	//	fputs("$",stdout);
      }
      MLReleaseString(link,result);
      break;
    case MESSAGEPKT:
      MLGetSymbol(link,(const char**)&symbol);
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"MESSAGEPKT@command: \"%s\"  \"%s\"\n",symbol,result); fflush(log);
#endif
      MLReleaseSymbol(link,symbol);
      MLReleaseString(link,result);
      msg=1;
      break;
    case DISPLAYPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"DISPLAYPK@command: \"%s\"\n",result); fflush(log);
#endif
      if (non_ps) {
	fputs("\2ps:",stdout);
#ifdef LOG_PS
	psfile=fopen(LOG_PS,"w");
#endif
	prelude(pre_name); non_ps=0;
      }
      psput(result);
      MLReleaseString(link,result);
      break;
    case DISPLAYENDPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"DISPLAYENDPKT@command: \"%s\"\n",result); fflush(log);
#endif
      psput(result);
      prelude(post_name);
      fputs("\5{}{}\n\n",stdout);
#ifdef LOG_PS
      fclose(psfile);
#endif
      non_ps=1;
      MLReleaseString(link,result);
      break;
    case INPUTPKT:
      MLGetString(link,(const char**)&result);
#ifdef LOG
      fprintf(log,"INPUTPKT@command: \"%s\"\n",result); fflush(log);
#endif
      printf("\2prompt#\\red %s{}\5\5",result);
      fflush(stdout);
      MLReleaseString(link,result);
      if (getline(&input,&size,stdin)>=0) command(input);
      break;
    case CALLPKT:
#ifdef LOG
      fputs("CALLPKT@command\n",log); fflush(log);
#endif
      break;
    default:
#ifdef LOG
      fprintf(log,"UNKNOWN PACKET@command: %1d\n",pkt); fflush(log);
#endif
      break;
    }
    MLNewPacket(link);
    err=MLError(link);
    if (err==CLOSED) {
      fputs("\\red The end\5",stdout);
      MLClose(link);
      MLDeinitialize(env);
      exit(0);
    } else if (err) {
      printf("\\red Error %ld: %s\5",err,MLErrorMessage(link));
      exit(1);
    }
  } while (more);
  


  if(is_buf) {
    evaluate(type, mls_buf);
    free(mls_buf); is_buf = 0;
  }

  /* post-process, e.g., reading a generated figure */
  switch(type) {
  case 0: /* tex */
    break;
  case 1: /* figure */
    /* e.g., read the generated EPS file and dump to TeXmacs*/
    /* fputs("\2ps:",stdout); */
    /* prelude(pre_name); */
    /* eps_to_string(); */
    /* prelude(post_name); */
    /* fputs("\5{}{}\n\n",stdout);*/

    fputs("\2ps:", stdout);
    fflush(stdout);
    system("cat ~/tmp.eps");
    fputs("\5{}{}\n\n", stdout);


    /* or can use other methods, like loading .png, .svg., whatever */
    //fputs("$",stdout); 
    break;
  default:
    break;
  }
}



int main(int argc, char *argv[]) {
  int err;
  size_t InNum=1,l;
  char *tm_path;
#ifdef LOG
  log=fopen(LOG,"w");
#endif

  tm_path=getenv("TEXMACS_PATH");
  /* tm_path = (char*)"./"; */

  if (tm_path==NULL) exit(1);
  l=strlen(tm_path);
  pre_name=(char*)malloc(l+strlen(PRE_NAME)+1);
  post_name=(char*)malloc(l+strlen(POST_NAME)+1);
  strcpy(pre_name,tm_path);  strcpy(pre_name+l,PRE_NAME);
  strcpy(post_name,tm_path); strcpy(post_name+l,POST_NAME);

  input=(char*)malloc(size);

  env=MLInitialize((MLParametersPointer)0);
  if (env==(MLENV)0) {
    fputs("\2latex:\\red Initialization of MathLink failed\5",stdout);
    exit(1);
  }

  link=MLOpenString(env,"-linkname \"math -mathlink\"",&err);
  if (link==(MLINK)0) {
    fputs("\2latex:\\red Link with Mathematica failed",stdout);
    MLDeinitialize(env);
    exit(1);
  }

  fputs("\2latex:\\red Mathematica",stdout);

  while (1) {
    /* Prompt */
    printf("\2prompt#\\red In[%1d]:= {}\5\5",InNum++);
    fflush(stdout);
    if (getline(&input,&size,stdin)>=0) command(input);
  }

  return 0;
}
