%{
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <dpComplex.h>
#include "dpuser.h"
#include "dpuserType.h"
#include "dpuser.yacchelper.h"
#include "dpuser.procs.h"
#include "procedures.h"
#include "functions.h"
#include "dpuser_utils.h"
#include <signal.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_rng.h>
#include <fits.h>
#include <locale.h>

#ifdef WIN32
#pragma warning (disable: 4996) // disable warning for deprecated POSIX name
#include <direct.h>
#endif /* WIN32 */

#include <iostream>
#include <string>
#include "dpuserAST.h"

/*
int astlex();
void asterror(const char *s);
*/

#ifndef NO_READLINE
#ifdef __cplusplus
extern "C" {
#endif
int read_history(const char *);
int write_history(const char *);
void stifle_history(int);
#ifdef __cplusplus
}
#endif
#define DPHISTORY_SIZE 500
#endif /* !NO_READLINE */

#ifdef DPQT
/* extern Fits *defaultBuffer; */
#endif /* DPQT */

int yyparse();
int yylex();
int parseError;
extern int ScriptInterrupt;
double test;
extern dpStringList script;

#define YYERROR_VERBOSE
%}

%union {
	long iValue;
	double dValue;
	double cValue;
	std::string *s;
	int f;
	ASTNode *astnode;
	std::string *sValue;
	std::vector<std::string> *sl;
}

%token ENTER
%token IF
%token FOR
%token WHILE
%token EXIT
%token<astnode> WHERE
%token<s> HELP
%token<s> OPTION
%token<iValue> INTEGER
%token<dValue> REAL
%token<cValue> COMPLEX
%token<sValue> STRING
%token<sValue> FITSFILE
%token<s> IDENTIFIER
%token<s> USERPRO
%token<s> USERFUNC
%type<sl> userpro_args userfunc_args
%type<astnode> expr boolean assignment stmt varchange list rangearg_list rangeargs procedure fullfunc funcarg_list userprocedure userfunction
%nonassoc IFX
%nonassoc ELSE

%left AND OR
%left EQ NE
%left GE LE '>' '<'
%right '='
%left '+' '-'
%left '%'
%left '*' '#' '/'
%left PlusE MinusE MulE DivE
%left PlusP MinusM
%left '^'
%right UMINUS
%nonassoc '!'
%nonassoc '[' ']'

%%

function:
    function stmt       { 
		try {
		  FitsInterrupt = 0;
		  $2->evaluate(); 
		} catch (dpuserTypeException e) {
      dp_output("%s\n", e.reason());
    }
		delete $2; /* unprotect(); */ freePointerlist(); looplock = 0; }
/*		| function stmt_list { std::cout << "function stmt_list" << std::endl; $2->evaluate(); delete $2; } */
		| function userprocedure     { delete $2; freePointerlist(); }
		| function userfunction     { delete $2; freePointerlist(); }
/*		| function expr     { std::cout << $2->evaluate() << std::endl; delete $2; } */ /* Causes 14 shift/reduce conflicts */
		|
		;

stmt: procedure         { $$ = $1; }
    | ENTER             { $$ = new statementNode(NULL); }
    | IDENTIFIER ENTER         { $$ = new procedureNode(*$1); delete $1; }
		| assignment ENTER  { $$ = $1; }
		| varchange ENTER   { $$ = $1; }
		| list '}'          { $$ = $1; } 
		| HELP ENTER    { $$ = new helpNode(*$1); delete $1; }
/*		| fullfunc ENTER    { $$ = new procedureNode($1); } */
		| FOR assignment ENTER boolean ENTER varchange ')' stmt { $$ = new forloopNode($2, $4, $6, $8); }
/*		| FOR assignment ';' boolean ';' varchange ')' stmt_list { $$ = new forloopNode($2, $4, $6, $8); } */
		| FOR assignment ',' expr stmt { $$ = new forloopNode($2, $4, $5); }
/*		| FOR assignment ',' PROC stmt { ASTNode *a = new variableNode(*$4); delete $4; $$ = new forloopNode($2, a, $5); } */
/*		| FOR assignment ',' expr stmt_list { $$ = new forloopNode($2, $4, $5); } */
    | WHILE boolean stmt      { $$ = new whileNode($2, $3); }
    | IF boolean stmt %prec IFX    { $$ = new ifNode($2, $3); }
    | IF expr stmt %prec IFX       { $$ = new ifNode($2, $3); }
/*    | IF boolean stmt_list %prec IFX    { $$ = new ifNode($2, $3); } */
    | IF boolean stmt ELSE stmt             { $$ = new ifNode($2, $3, $5); }
    | IF expr stmt ELSE stmt                { $$ = new ifNode($2, $3, $5); }
		| FITSFILE '=' expr { $$ = new writefitsNode(*$1, $3); delete $1; }
		| error ENTER       { $$ = new statementNode(NULL); }
		| EXIT              {
			for (auto el:userprocedures) delete el.second;
			for (auto el:userfunctions) delete el.second;
#ifndef NO_READLINE
			stifle_history(DPHISTORY_SIZE);
			write_history("dpuser.history");
#endif /* !NO_READLINE */
			if (xpa != NULL) {
				XPAClose(xpa);
				xpa = NULL;
			}
#ifdef DPQT
      return 0;
#else
			exit(0);
#endif
		}
    ;

userprocedure: USERPRO list '}'   { userprocedures[*$1] = $2; dp_output("stored procedure %s\n", $1->c_str()); delete $1; $$ = new statementNode(NULL); }
		| userpro_args list '}'       { userprocedures[$1->front()] = $2; userprocedure_arguments[$1->front()] = *$1; dp_output("stored procedure %s\n", $1->front().c_str()); delete $1; $$ = new statementNode(NULL); }
		;

userpro_args: USERPRO ',' IDENTIFIER { delete userprocedures[*$1]; $$ = new std::vector<std::string>; $$->push_back(*$1); $$->push_back(*$3); delete $1; delete $3; }
		| userpro_args ',' IDENTIFIER    { $$->push_back(*$3); delete $3; }
		| userpro_args ',' assignment    { $$ = $1; }
		;

userfunction: USERFUNC list '}'   { userfunctions[*$1] = $2; dp_output("stored function %s\n", $1->c_str()); delete $1; $$ = new statementNode(NULL); }
		| userfunc_args list '}'      { userfunctions[$1->front()] = $2; userfunction_arguments[$1->front()] = *$1; dp_output("stored function %s\n", $1->front().c_str()); delete $1; $$ = new statementNode(NULL); }
		;

userfunc_args: USERFUNC ',' IDENTIFIER { delete userfunctions[*$1]; $$ = new std::vector<std::string>; $$->push_back(*$1); $$->push_back(*$3); delete $1; delete $3; }
		| userfunc_args ',' IDENTIFIER    { $$->push_back(*$3); delete $3; }
		| userfunc_args ',' assignment    { $$ = $1; }
		;
/*
stmt_list:
    list '}'            { $$ = $1; }
		;
*/	
list: 
		'{'                 { $$ = new listNode(); }
    | list stmt         { $$ = $1->append($2); }
/*		| list stmt '}'    { $$ = $1->append($2); } */
    ;

assignment:
/*			VAR '=' expr      { assignmentNode *a = new assignmentNode(*$1, $3); a->evaluate(); delete $1; $$ = a; } */
			IDENTIFIER '=' expr      { $$ = new assignmentNode(*$1, $3); delete $1; }
		| IDENTIFIER '=' assignment { $$ = new assignmentNode(*$1, $3); delete $1; }
		| IDENTIFIER '=' boolean { $$ = new assignmentNode(*$1, $3); delete $1; }
/*			ASTNode *b = new numberNode($3->evaluate());
			assignmentNode *a = new assignmentNode(*$1, b); 
			a->evaluate(); 
			delete $1; 
			$$ = a;
		}*/
		;

varchange:
		  IDENTIFIER PlusP                     { $$ = new incrementNode(*$1); delete $1; }
		| IDENTIFIER MinusM                    { $$ = new decrementNode(*$1); delete $1; }
		| IDENTIFIER PlusE expr                { $$ = new plusEqualsNode(*$1, $3); delete $1; }
		| IDENTIFIER MinusE expr               { $$ = new minusEqualsNode(*$1, $3); delete $1; }
		| IDENTIFIER MulE expr                 { $$ = new multiplyEqualsNode(*$1, $3); delete $1; }
		| IDENTIFIER DivE expr                 { $$ = new divideEqualsNode(*$1, $3); delete $1; }
		| IDENTIFIER rangeargs '=' expr        { $$ = new operatorRangeEqualsNode(*$1, $2, $4); delete $1; }
		| IDENTIFIER rangeargs PlusP           { $$ = new operatorRangePlusPlusNode(*$1, $2); delete $1; }
		| IDENTIFIER rangeargs MinusM          { $$ = new operatorRangeMinusMinusNode(*$1, $2); delete $1; }
		| IDENTIFIER rangeargs PlusE expr      { $$ = new operatorRangePlusEqualNode(*$1, $2, $4); delete $1; }
		| IDENTIFIER rangeargs MinusE expr     { $$ = new operatorRangeMinusEqualNode(*$1, $2, $4); delete $1; }
		| IDENTIFIER rangeargs MulE expr       { $$ = new operatorRangeMultiplyEqualNode(*$1, $2, $4); delete $1; }
		| IDENTIFIER rangeargs DivE expr       { $$ = new operatorRangeDivideEqualNode(*$1, $2, $4); delete $1; }
/*
		| assignment                         { $$ = $1; }
*/
		;

/*
fullfunc:
      func ')'         { $$ = $1; }
		| funcarg_list ')' { $$ = $1; }
    ;

func:
      IDENTIFIER '('           { $$ = new functionNode(*$1); delete $1; }
		;

funcarg_list:
		  func expr                      { $$ = $1->append($2); }
		| funcarg_list ',' expr              { $$ = $1->append($3); }
		| funcarg_list ',' assignment        { $$ = $1->append($3); }
		| funcarg_list OPTION                { $$ = $1->append(*$2); delete $2; }
		| func boolean                   { $$ = $1->append($2); }
		| funcarg_list ',' boolean                   { $$ = $1->append($3); }
		;
*/

fullfunc:
    IDENTIFIER '(' ')'  { $$ = new functionNode(*$1); delete $1; }
		| funcarg_list ')' { $$ = $1; }
    ;

funcarg_list:
		  IDENTIFIER '(' expr                      { $$ = new functionNode(*$1); delete $1; $$->append($3); }
		| funcarg_list ',' expr              { $$ = $1->append($3); }
		| funcarg_list ',' assignment        { $$ = $1->append($3); }
		| funcarg_list OPTION                { $$ = $1->append(*$2); delete $2; }
		| IDENTIFIER '('  boolean                   { $$ = new functionNode(*$1); delete $1; $$->append($3); }
		| funcarg_list ',' boolean                   { $$ = $1->append($3); }
		;

rangeargs:
			rangearg_list ']'                  { $$ = $1; $$->append(" "); }
		;

rangearg_list:
		'[' expr                             { $$ = new rangeNode(); $$->append($2); $$->append(" "); }
		| '[' '*'                            { $$ = new rangeNode(); $$->append(NULL); $$->append(" "); }
		| rangearg_list ':' expr             { $$ = $1->append($3); $1->append(":"); }
		| rangearg_list ',' expr             { $$ = $1->append($3); $1->append(","); }
		| rangearg_list ',' '*'              { $$ = $1->append(NULL); $1->append(" "); }
		;

procedure:
		  IDENTIFIER expr         { $$ = new procedureNode(*$1); $$->append($2); delete $1; }
		| IDENTIFIER boolean      { $$ = new procedureNode(*$1); $$->append($2); delete $1; }
		| IDENTIFIER ',' expr     { $$ = new procedureNode(*$1); $$->append($3); delete $1; }
		| procedure ',' expr      { $$ = $1->append($3); }
		| procedure ',' assignment { $$ = $1->append($3); }
		| procedure OPTION        { $$ = $1->append(*$2); delete $2; }
/*		| fullfunc ',' expr { std::cout << "procedure from fullfunc ',' expr" << std::endl; $$ = new procedureNode($1); $$->append($3); } */
		| procedure ',' boolean   { $$ = $1->append($3); }
		;

expr: INTEGER           { $$ = new numberNode($1); }
    | REAL              { $$ = new numberNode($1); }
    | COMPLEX           { $$ = new numberNode(0.0, $1); }
    | STRING            { $$ = new numberNode(*$1); delete $1; }
    | FITSFILE          { $$ = new numberNode(*$1, false); delete $1; }
    | IDENTIFIER        { $$ = new variableNode(*$1); delete $1; }
    | expr '+' expr     { $$ = new plusNode($1, $3); }
    | expr '-' expr     { $$ = new minusNode($1, $3); }
    | expr '*' expr     { $$ = new multiplyNode($1, $3); }
    | expr '/' expr     { $$ = new divideNode($1, $3); }
    | expr '^' expr     { $$ = new powerNode($1, $3); }
    | expr '#' expr     { $$ = new matrixmulNode($1, $3); }
    | expr '%' expr     { $$ = new moduloNode($1, $3); }
    | '+' expr  %prec UMINUS    { $$ =  $2; }
    | '-' expr  %prec UMINUS    { $$ = new unaryMinusNode($2); }
    | '(' expr ')'              { $$ =  $2; }
		| fullfunc          { $$ = $1; }
		| expr rangeargs    { $$ = new extractrangeNode($1, $2); }
		| rangeargs         { $$ = new createrangeNode($1); }
		| WHERE boolean ')' { $$ = new whereNode($2); }
		| WHERE boolean ',' IDENTIFIER ')' { $$ = new whereNode($2, *$4); delete $4; }
		| boolean '?' expr ':' expr        { $$ = new ifNode($1, $3, $5); } /* causes 20 shift / reduce conflicts */
    ;

boolean: expr '<' expr                      { $$ = new lessThanNode($1, $3); }
    | expr '>' expr                      { $$ = new greaterThanNode($1, $3); }
    | expr GE expr                       { $$ = new greaterEqualNode($1, $3); }
    | expr LE expr                       { $$ = new lessEqualNode($1, $3); }
    | expr NE expr                       { $$ = new notEqualNode($1, $3); }
    | expr EQ expr                       { $$ = new equalNode($1, $3); }
		| '(' boolean ')'                    { $$ = $2; }
		| boolean AND boolean                { $$ = new andNode($1, $3); }
		| expr AND expr                      { $$ = new andNode($1, $3); }
		| boolean AND expr                      { $$ = new andNode($1, $3); }
		| expr AND boolean                      { $$ = new andNode($1, $3); }
		| boolean OR boolean                 { $$ = new orNode($1, $3); }
		| expr OR expr                       { $$ = new orNode($1, $3); }
		| boolean OR expr                       { $$ = new orNode($1, $3); }
		| expr OR boolean                       { $$ = new orNode($1, $3); }
		| '!' boolean                        { $$ = new notNode($2); }
		;

%%
const char *info_startup[] = {
DPUSERVERSION2 DP_VERSION,
#ifdef DBG
"DEBUG Version",
#endif
"Written since 1999 by Thomas Ott",
"Inspired by the original MPE dp_user speckle data reduction software",
" ",
"For basic information, type \"help\" at the DPUSER> prompt.",
"Online documentation available at: http://www.mpe.mpg.de/~ott/dpuser",
""
};

void controlcsignal(int signum) {
	dp_output("Interrupt encountered.\n");
	FitsInterrupt = 1;
	ScriptInterrupt = 1;
}

/* install new error handler */
void dpuser_gsl_error_handler(const char *reason, const char *file, int line, int gsl_errno) {
//	dp_output("GSL error: %s - %s\n", reason, gsl_strerror(gsl_errno));
}

#ifdef DPQT
int init_dpuser() {
#else
int main(int argc, char *argv[]) {
#endif /* DPQT */
	int i;

	setlocale(LC_NUMERIC, "C");
	init_functions();
	init_procedures();
	CreateHelpMaps();
	gsl_set_error_handler(&dpuser_gsl_error_handler);
	i = 0;
	while (strlen(info_startup[i]) > 0) {
		dp_output("%s\n", info_startup[i++]);
	}
	
	createGlobalVariables();

	nprotected = 100;
	protectedpntrs = (void **)malloc(nprotected * sizeof(void *));
	for (i = 0; i < nprotected; i++) protectedpntrs[i] = NULL;

	nglobals = 8;

#ifndef DPQT
/* command line arguments, if applicable */
	if (argc > 1) {
		dpuser_vars["argv"].type = typeStrarr;
		dpuser_vars["argv"].arrvalue = CreateStringArray();
		for (i = 1; i < argc; i++) *dpuser_vars["argv"].arrvalue += argv[i];
	}

/* PLPLOT init */
	init_dpplplot();
#endif /* DPQT */

/* add QFitsView default variable */
#ifdef DPQT
/*	svariables[8] = "buffer1";
	variables[8].fvalue = defaultBuffer;
	variables[8].type = typeFits; */
#endif /* DPQT */

/* set number of functions */
//	nfunctions = 0;
//	nfunctions = funcs.size();
//	while (strlen(funcss[nfunctions].name) > 0) nfunctions++;
	dp_output("%i functions registered.\n", funcs.size());
	
/* set number of pgplot routines */
	npg = 0;
	npg = procs.size();
//	while (strlen(pgs[npg].name) > 0) npg++;
	dp_output("%i procedures registered.\n", npg - 126);
#ifdef HAS_PGPLOT
	dp_output("126 pgplot procedures registered.\n");
#else
  dp_output("This version was compiled without PGPLOT support.\n");
#endif /* HAS_PLPLOT */

#ifndef DPQT
	signal(SIGINT, &controlcsignal);
#endif /* !DPQT */

#ifndef NO_READLINE
	read_history("dpuser.history");
#endif /* !NO_READLINE */

#ifdef WIN32
/* On Windows, set up directory for PGPLOT */
/* TO: Commented out 02072014: Not needed anymore with our own modified pgplot
	if (getenv("PGPLOT_DIR") == NULL) {
		char *env, *cwd;
		
		cwd = (char *)malloc(256 * sizeof(char));
		if (cwd != NULL) {
			getcwd(cwd, 255);
			env = (char *)malloc((strlen(cwd) + 12) * sizeof(char));
			if (env != NULL) {
				sprintf(env, "PGPLOT_DIR=%s", cwd);
				putenv(env);
				dp_output("Environment variable %s set.\n", env);
			}
			free(cwd);
		}
	}
*/
#endif /* WIN32 */

/* Set up random number generator */
	const gsl_rng_type * T;

	gsl_rng_env_setup();

	gsl_rng_default_seed = time(NULL);
	T = gsl_rng_default;
	gsl_rng_r = gsl_rng_alloc (T);
	
//	idum = -time(NULL);
//	ran1(&idum);

/* run startup script, if available */
	silence = 1;

	script.clear();
	script += "{startup_=getenv(\"DPUSER\");if startup_==\"\" startup_=getenv(\"HOME\");if(startup_!=\"\"){if startup_[strlen(startup_)]!=\"/\" startup_+=\"/\";}startup_+=\"startup.dpuser\";if fileexists(startup_)==1 run startup_}";
	script += "echo 1";
	
	yyparse();

	if (xpa != NULL) {
		XPAClose(xpa);
		xpa = NULL;
	}

#ifdef DPQT
	return 0;
#else
	exit(0);
#endif
}

/*
extern int astparse();

int main() { initialize_dpuser_functions(); astparse(); }

void asterror(const char *s) { std::cout << s << std::endl; }
*/
