#include "fribidixetex-defines.h"
#include "fribidixetex-bidi.h"
#include "fribidixetex-util.h"
#include "fribidixetex-dict.h"
#include "fribidixetex-ignore.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fribidixetex-io.h"

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif

#ifndef max
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif


/***********************/
/* Global Data */

typedef struct COMMAND {
       char *name;
       struct COMMAND *next;
}
       bidi_cmd_t;

bidi_cmd_t *bidi_command_list;

enum { MODE_BIDIOFF, MODE_BIDION, MODE_BIDILTR };


static int bidi_mode;
static FriBidiLevel bidi_embed[MAX_LINE_SIZE];
static FriBidiChar      translation_buffer[MAX_LINE_SIZE];

/* only ASCII charrecters mirroring are
* supported - the coded below 128 according to
* http://www.unicode.org/Public/4.1.0/ucd/BidiMirroring.txt */

/* TODO: implement full mirroring list according to unicode
* standard */
static const char *bidi_mirror_list[][2] =
{
       {"(",  ")"},
       {")",  "("},
       {"<",  ">"},
       {">",  "<"},
       {"[",  "]"},
       {"]",  "["},
       {"\\{","\\}"},
       {"\\}","\\{"},
       {NULL,NULL}
};

static const char *bidi_hack_list[][2] =
{
       {"---","{\\fribidixetexemdash}"},
       {"--","{\\fribidixetexendash}"},
       {NULL,NULL}
};

/********/
/* TAGS */
/********/

#define TAG_BIDI_ON                     "%BIDION"
#define TAG_BIDI_OFF            "%BIDIOFF"
#define TAG_BIDI_NEW_TAG        "%BIDITAG"
#define TAG_BIDI_LTR            "%BIDILTR"
#define TAG_BIDI_DIC_TAG        "%BIDIDICTAG"
#define TAG_BIDI_DIC_ENV        "%BIDIDICENV"

#define TAG_RTL                 "\\fribidixetexRLE{"
#define TAG_LTR                 "\\fribidixetexLRE{"
#define TAG_CLOSE               "}"

#define TAG_LATIN_NUM           "\\fribidixetexlatinnumbers{"
#define TAG_NONLATIN_NUM                "\\fribidixetexnonlatinnumbers{"

/***********************/

/* Compares begining of string U and C
* for example "Hello!" == "Hel" */
static int bidi_strieq_u_a(const FriBidiChar *u,const char *c)
{
       while(*u && *c) {
               if(*u!=*c)
                       return 0;
               u++; c++;
       }
       if(*u ==0 && *c!=0) {
               return 0;
       }
       return 1;
}

static int bidi_strlen(FriBidiChar *in)
{
       int len;
       for(len=0;*in;len++) in++;
       return len;
}

/* Safe functions for adding charrecters to buffer
* if the line is too long the program just exits */

static void bidi_add_char_u(FriBidiChar *out,int *new_len,
                                                       int limit,FriBidiChar ch)
{
       if(*new_len+2<limit){
               out[*new_len]=ch;
               ++*new_len;
               out[*new_len]=0;
       }
       else {
               fprintf(stderr,"The line is too long: line %d\n",io_line_number);
               exit(1);
       }
}
static void bidi_add_str_c(FriBidiChar *out,int *new_len,
                                                       int limit,const char *str)
{
       while(*str){
               bidi_add_char_u(out,new_len,limit,*str);
               str++;
       }
}

/* Function looks if the first charrecter or sequence
* in "in" is mirrored charrecter and returns its mirrot
* in this case. If it is not mirrored then returns NULL */
static const char *bidi_one_mirror(FriBidiChar *in,int *size,
                                                               const char *list[][2])
{
       int pos=0;
       while(list[pos][0]) {
               if(bidi_strieq_u_a(in,list[pos][0])) {
                       *size=strlen(list[pos][0]);
                       return list[pos][1];
               }
               pos++;
       }
       return NULL;
}

static const char *bidi_mirror(FriBidiChar *in,int *size,
                                                               int replace_minus)
{
       const char *ptr;
       if((ptr=bidi_one_mirror(in,size,bidi_mirror_list))!=NULL) {
               return ptr;
       }
       if(replace_minus) {
               return bidi_one_mirror(in,size,bidi_hack_list);
       }
       return NULL;
}


/* Returns the minimal embedding level for required direction */
int bidi_basic_level(int is_rtl)
{
       if(is_rtl)
               return 1;
       return 0;
}

void bidi_add_command(const char *name)
{
       bidi_cmd_t *new_cmd;
       char *new_text;

       new_cmd=(bidi_cmd_t*)utl_malloc(sizeof(bidi_cmd_t));
       new_text=(char*)utl_malloc(strlen(name)+1);

       new_cmd->next=bidi_command_list;
       new_cmd->name=new_text;
       strcpy(new_text,name);
       bidi_command_list = new_cmd;
}

int bidi_is_cmd_char(FriBidiChar ch)
{
       if( ('a'<= ch && ch <='z') || ('A' <=ch && ch <= 'Z') )
               return 1;
       return 0;
}

/* Verirfies wether then text of length "len" is
* in command list */
int bidi_in_cmd_list(FriBidiChar *text,int len)
{
       int i;
       bidi_cmd_t *p;
       for(p=bidi_command_list;p;p=p->next) {
               for(i=0;i<len;i++) {
                       if(text[i]!=p->name[i])
                               break;
               }
               if(i==len && p->name[len]==0){
                       return 1;
               }
       }
       return 0;
}


/* Find special charrecters */
int bidi_is_latex_special_char(FriBidiChar *text)
{
       if(text[0]!='\\')
               return FALSE;
       /* Special charrecters according to lshort.pdf
        *      # $ % ^ & _ { } ~ \, "{}" should be mirrored
        * thus not included to the list */
       switch (text[1]) {
               case '#' :
               case '$' :
               case '%' :
               case '^' :
               case '&' :
               case '_' :
               case '\\':
                       return TRUE;
               default :
                       return FALSE;
       }

}


/*Verifies wether the next string is command
* ie: "\\[a-zA-Z]+" or "\\[a-zA-Z]+\*" */
int bidi_is_command(FriBidiChar *text,int *command_length)
{
       int len;

       if(bidi_is_latex_special_char(text)) {
               /* Charrecters like \\ or \$ that should be treated
                * as `commands' */
               *command_length=2;
               return TRUE;
       }

       if(*text != '\\' || !bidi_is_cmd_char(text[1])) {
               return FALSE;
       }
       len=1;
       while(bidi_is_cmd_char(text[len])) {
               len++;
       }
       if(text[len] == '*') {
               len++;
       }
       *command_length = len;
       return TRUE;
}

/* This is implementation of state machine with stack
* that distinguishs between text and commands */

/* STACK VALUES */
enum {
       EMPTY,
       SQ_BRACKET ,SQ_BRACKET_IGN,
       BRACKET, BRACKET_IGN,
       CMD_BRACKET, CMD_BRACKET_IGN
};
/* STATES */
enum { ST_NO, ST_NORM, ST_IGN };

/* Used for ignore commands */
int bidi_is_ignore(int top)
{
       return  top == SQ_BRACKET_IGN
                       || top== BRACKET_IGN
                       || top == CMD_BRACKET_IGN;
}

int bidi_state_on_left_br(int top,int *after_command_state)
{
       int ign_addon;
       int push,state = *after_command_state;
       if(bidi_is_ignore(top) || state == ST_IGN) {
               ign_addon = 1;
       }
       else {
               ign_addon = 0;
       }

       if(state) {
               push = CMD_BRACKET;
       }
       else{
               push = BRACKET;
       }

       *after_command_state = ST_NO;
       return push + ign_addon;
}

int bidi_state_on_left_sq_br(int top,int *after_command_state)
{
       int push;
       if(bidi_is_ignore(top) || *after_command_state == ST_IGN) {
               push = SQ_BRACKET_IGN;
       }
       else {
               push = SQ_BRACKET;
       }
       *after_command_state = ST_NO;
       return push;
}

void bidi_state_on_right_br(int top,int *after_command_state)
{
       if(top == CMD_BRACKET) {
               *after_command_state = ST_NORM;
       }
       else if(top == BRACKET || top == BRACKET_IGN) {
               *after_command_state = ST_NO;
       }
       else {/*top == CMD_BRACKET_IGN*/
               *after_command_state = ST_IGN;
       }
}

void bidi_state_on_right_sq_br(int top,int *after_command_state)
{
       if(top == SQ_BRACKET_IGN) {
               *after_command_state = ST_IGN;
       }
       else { /* top == SQ_BRACKET */
               *after_command_state = ST_NORM;
       }
}
/* Using marks "$$" */
int bidi_calc_equation_inline(FriBidiChar *in)
{
       int len=1;
       while(in[len] && in[len]!='$') {
               if(in[len]=='\\' && (in[len+1]=='$' || in[len+1]=='\\')) {
                       len+=2;
               }
               else {
                       len++;
               }
       }
       if(in[len]=='$')
               len++;
       return len;
}

/* using \[ and \] marks */
int bidi_calc_equation_display(FriBidiChar *in)
{
       int len=2;
       while(in[len]){
               if(in[len]=='\\' && in[len+1]=='\\')
                       len+=2;
               else if(in[len]=='\\' && in[len+1]==']')
                       return len+2;
               else
                       len++;
       }
       return len;
}

/* Support of equations */
int bidi_calc_equation(FriBidiChar *in)
{
       if(*in=='$')
               return bidi_calc_equation_inline(in);
       else
               return bidi_calc_equation_display(in);
}

/* This function parses the text "in" in marks places that
* should be ignored by fribidi in "is_command" as true */
void bidi_mark_commands(FriBidiChar *in,int len,char *is_command,int is_rtl)
{

       char *parthness_stack;
       int stack_size=0;
       int cmd_len,top;
       int after_command_state=ST_NO;
       int mark,pos,symbol,i,push;

       /* Assumption - depth of stack can not be bigger then text length */
       parthness_stack = utl_malloc(len);

       pos=0;

       while(pos<len) {

               top = stack_size == 0 ? EMPTY : parthness_stack[stack_size-1];
               symbol=in[pos];
#ifdef DEBUG_STATE_MACHINE
               printf("pos=%d sybol=%c state=%d top=%d\n",
                       pos,(symbol < 127 ? symbol : '#'),after_command_state,top);
#endif
               if(bidi_is_command(in+pos,&cmd_len)) {
                       for(i=0;i<cmd_len;i++) {
                               is_command[i+pos]=TRUE;
                       }
                       if(bidi_in_cmd_list(in+pos+1,cmd_len-1)) {
                               after_command_state = ST_IGN;
                       }
                       else {
                               after_command_state = ST_NORM;
                       }
                       pos+=cmd_len;
                       continue;
               }
               else if(symbol=='$' || (symbol=='\\' && in[pos+1]=='[')) {
                       cmd_len=bidi_calc_equation(in+pos);

                       for(i=0;i<cmd_len;i++) {
                               is_command[i+pos]=TRUE;
                       }
                       pos+=cmd_len;
                       continue;
               }
               else if( symbol == '{' ) {
                       push = bidi_state_on_left_br(top,&after_command_state);
                       parthness_stack[stack_size++] = push;
                       mark=TRUE;
               }
               else if(symbol == '[' && after_command_state) {
                       push = bidi_state_on_left_sq_br(top,&after_command_state);
                       parthness_stack[stack_size++] = push;
                       mark=TRUE;
               }
               else if(symbol == ']' && (top == SQ_BRACKET || top == SQ_BRACKET_IGN)){
                       bidi_state_on_right_sq_br(top,&after_command_state);
                       stack_size--;
                       mark=TRUE;
               }
               else if(symbol == '}' && (BRACKET <= top && top <= CMD_BRACKET_IGN)) {
                       bidi_state_on_right_br(top,&after_command_state);
                       stack_size--;
                       mark=TRUE;
               }
               else {
                       mark = bidi_is_ignore(top);
                       after_command_state = ST_NO;
               }
               is_command[pos++]=mark;
       }

       utl_free(parthness_stack);
}

#if ( FRIBIDI_MAJOR_VERSION * 100 + FRIBIDI_MINOR_VERSION ) > 10 // 0.10
static void get_bidi_levels(FriBidiChar *in,int length,int is_rtl,FriBidiLevel *embed)
{
       FriBidiCharType *types = utl_malloc(sizeof(FriBidiCharType)*length);
       FriBidiParType direction = is_rtl ? FRIBIDI_PAR_RTL : FRIBIDI_PAR_LTR;

       fribidi_get_bidi_types(in,length,types);
       fribidi_get_par_embedding_levels(types,length,&direction,embed);

       utl_free(types);
}

#else // old fribidi
static void get_bidi_levels(FriBidiChar *in,int length,int is_rtl,FriBidiLevel *embed)
{
       FriBidiCharType direction;
       if(is_rtl)
               direction = FRIBIDI_TYPE_RTL;
       else
               direction = FRIBIDI_TYPE_LTR;

       fribidi_log2vis_get_embedding_levels(in,length,&direction,embed);
}
#endif

/* This function marks embedding levels at for text "in",
* it ignores different tags */
void bidi_tag_tolerant_fribidi_l2v(     FriBidiChar *in,int len,
                                                                       int is_rtl,
                                                                       FriBidiLevel *embed,
                                                                       char *is_command)
{
       int in_pos,out_pos,cmd_len,i;
       FriBidiChar *in_tmp;
       FriBidiLevel *embed_tmp,fill_level;

       in_tmp=(FriBidiChar*)utl_malloc(sizeof(FriBidiChar)*(len+1));
       embed_tmp=(FriBidiLevel*)utl_malloc(sizeof(FriBidiLevel)*len);

       /**********************************************
        * This is main parser that marks commands    *
        * across the text i.e. marks non text        *
        **********************************************/
       bidi_mark_commands(in,len,is_command,is_rtl);

       /**********************************************/
       /* Copy all the data without tags for fribidi */
       /**********************************************/
       in_pos=0;
       out_pos=0;

       while(in_pos<len) {
               if(is_command[in_pos]){
                       in_pos++;
                       continue;
               }
               /* Copy to buffer */
               in_tmp[out_pos]=in[in_pos];
               out_pos++;
               in_pos++;
       }

       /***************
        * RUN FRIBIDI *
        ***************/

       /* Note - you must take the new size for firibidi */
       get_bidi_levels(in_tmp,out_pos,is_rtl,embed_tmp);

       /****************************************************
        * Return the tags and fill missing embedding level *
        ****************************************************/
       in_pos=0;
       out_pos=0;

       while(in_pos<len) {
               if(is_command[in_pos]){
                       /* Find the length of the part that
                        * has a command/tag */
                       for(cmd_len=0;in_pos+cmd_len<len;cmd_len++) {
                               if(!is_command[cmd_len+in_pos])
                                       break;
                       }

                       if(in_pos == 0 || in_pos + cmd_len == len){
                               /* When we on start/end assume basic direction */
                               fill_level = bidi_basic_level(is_rtl);
                       }
                       else {
                               /* Fill with minimum on both sides */
                               fill_level = min(embed_tmp[out_pos-1],embed_tmp[out_pos]);
                       }

                       for(i=0;i<cmd_len;i++){
                               embed[in_pos]=fill_level;
                               in_pos++;
                       }
                       continue;
               }
               /* Retrieve embedding */
               embed[in_pos]=embed_tmp[out_pos];
               out_pos++;
               in_pos++;
       }

       /* Not forget to free */
       utl_free(embed_tmp);
       utl_free(in_tmp);
}

int bidi_only_latin_number(FriBidiLevel *levels,FriBidiChar *string)
{
       FriBidiChar ch;
       while(*string && (*levels & 1)==0 ) {
               ch=*string;
               if(ch>127) { /* other foreign language */
                       return 0;
               }
               if(('a'<=ch && ch<='z') || ('A'<=ch && ch<='Z')) {
                       /* Find latin characters */
                       return 0;
               }
               string++;
               levels++;
       }
       return 1;
}

int bidi_only_nonlatin_number(FriBidiLevel *levels,FriBidiChar *string)
{
       FriBidiChar ch;
       while(*string && (*levels & 1)==0 ) {
               ch=*string;
               if('0'<=ch && ch<='9') { /* Find latin numbers */
                       return 0;
               }
               if(('a'<=ch && ch<='z') || ('A'<=ch && ch<='Z')) {
                       /* Find latin characters */
                       return 0;
               }
               string++;
               levels++;
       }
       return 1;
}

/* Mark Unicode  LRM & RLM charrecters */
int bidi_is_directional_mark(FriBidiChar c)
{
       if(c==0x200F || c==0x200E) {
               return 1;
       }
       return 0;
}

/* The function that parses line and adds required \R \L tags */
void bidi_add_tags(FriBidiChar *in,FriBidiChar *out,int limit,
                                       int is_rtl,int replace_minus,int no_mirroring)
{
       int len,new_len,level,new_level,brakets;
       int i,size;
       int is_number_env=0;

       const char *tag;
       char *is_command;


       len=bidi_strlen(in);

       is_command=(char*)utl_malloc(len);

       bidi_tag_tolerant_fribidi_l2v(in,len,is_rtl,bidi_embed,is_command);

       level=bidi_basic_level(is_rtl);

       new_len=0;
       out[0]=0;
       brakets=0;
       for(i=0,new_len=0;i<len;i++){
               new_level=bidi_embed[i];

               if(new_level>level) {
                       /* LTR Direction according to odd/even value of level */
                       is_number_env=FALSE;
                       if((new_level & 1) == 0) {
                               if(bidi_only_latin_number(bidi_embed+i,in+i)){
                                       tag=TAG_LATIN_NUM;
                                       is_number_env=TRUE;
                               }
                               else if(bidi_only_nonlatin_number(bidi_embed+i,in+i)){
                                       tag=TAG_NONLATIN_NUM;
                                       is_number_env=TRUE;
                               }
                               else {
                                       tag=TAG_LTR;
                               }
                       }
                       else {
                               tag=TAG_RTL;
                       }
                       brakets++;
                       bidi_add_str_c(out,&new_len,limit,tag);
               }
               else if(new_level<level) {
                       bidi_add_str_c(out,&new_len,limit,TAG_CLOSE);
                       brakets--;
               }

               if(
                       (new_level & 1)!=0
                       && !is_command[i]
                       && !no_mirroring
                       && (tag=bidi_mirror(in+i,&size,replace_minus))!=NULL)
               {
                       /* Replace charrecter with its mirror only in case
                        * we are in RTL direction */

                       /* Note this can be a sequence like "\{" */
                       bidi_add_str_c(out,&new_len,limit,tag);
                       i+=size-1;
               }
               else if(
                       (new_level & 1)!=1
                       && is_number_env
                       && replace_minus
                       && !is_command[i]
                       && (tag=bidi_one_mirror(in+i,&size,bidi_hack_list))!=NULL)
               {
                       /* Replace "--/---" with a tag "\tex{en|em}dash" only in LTR
                        * direction only if this is nubmers environment */

                       bidi_add_str_c(out,&new_len,limit,tag);
                       i+=size-1;
               }
               else if(bidi_is_directional_mark(in[i])){
                       /* Do nothing -- erase marks that are
                        * not actual charrecters and thus not presented in fonts */
               }
               else {
                       bidi_add_char_u(out,&new_len,limit,in[i]);
               }
               level=new_level;
       }
       /* Fill all missed brakets */
       while(brakets){
               bidi_add_str_c(out,&new_len,limit,TAG_CLOSE);
               brakets--;
       }
       utl_free(is_command);
}

/*************************/
/* T R A N S L A T I O N */
/*************************/

static void bidi_error(char *text)
{
       fprintf(stderr,"ERROR in line %d: `%s'\n",io_line_number,text);
       exit(1);
}


static int is_hebrew_tag(FriBidiChar buffer[MAX_COMMAND_LEN],FriBidiChar **ptr)
{
       FriBidiChar *tmp_ptr = *ptr;
       int size=0;
       if(!dict_is_hebrew_letter(*tmp_ptr)) {
               return FALSE;
       }
       while(dict_is_hebrew_letter(*tmp_ptr)) {
               if(size<MAX_COMMAND_LEN-1) {
                       buffer[size]=*tmp_ptr;
                       tmp_ptr++;
                       size++;
               }
               else {
                       bidi_error("Hebrew tag is too long");
                       return FALSE;
               }
       }
       buffer[size]=0;
       *ptr=tmp_ptr;
       return TRUE;

}

static int is_ascii_tag(char buffer[MAX_COMMAND_LEN],FriBidiChar **ptr)
{
       FriBidiChar *tmp_ptr = *ptr;
       int size=0;
       if(!bidi_is_cmd_char(*tmp_ptr)) {
               return FALSE;
       }
       while(bidi_is_cmd_char(*tmp_ptr)) {
               if(size<MAX_COMMAND_LEN-1) {
                       buffer[size]=*tmp_ptr;
                       tmp_ptr++;
                       size++;
               }
               else {
                       /* No warning need - no limit */
                       return FALSE;
               }
       }
       buffer[size]=0;
       *ptr=tmp_ptr;
       return TRUE;

}


static char const *is_tag_begin_or_end(FriBidiChar **ptr)
{
       char ascii_tag[MAX_COMMAND_LEN];
       char *trans=NULL;
       FriBidiChar *tmp_ptr=*ptr;
       FriBidiChar unicode_tag[MAX_COMMAND_LEN];

       if(is_ascii_tag(ascii_tag,&tmp_ptr) ||
                 (
                       is_hebrew_tag(unicode_tag,&tmp_ptr)
                       && (trans=dict_translate_tag(unicode_tag))
                 )
         )
       {
               if(trans) strcpy(ascii_tag,trans);

               if(strcmp(ascii_tag,"begin")==0){
                       *ptr=tmp_ptr;
                       return "begin";
               }
               else if(strcmp(ascii_tag,"end")==0){
                       *ptr=tmp_ptr;
                       return "end";
               }
               else {
                       return NULL;
               }
       }
       else {
               return NULL;
       }
}

#define is_open_bracket(x)  ( **(x) == '{' ? ++*(x) : FALSE )
#define is_close_bracket(x) ( **(x) == '}' ? ++*(x) : FALSE )

static const char *is_heb_word(FriBidiChar **ptr,trans_func_t tras_func)
{
       FriBidiChar *tmp_ptr=*ptr;
       FriBidiChar unicode_tag[MAX_COMMAND_LEN];
       char *trans;
       if(is_hebrew_tag(unicode_tag,&tmp_ptr)) {
               if((trans=tras_func(unicode_tag))!=NULL) {
                       *ptr=tmp_ptr;
                       return trans;
               }
               else {
                       bidi_error("Unknown hebrew tag - not translated");
                       return NULL;
               }
       }
       else {
               return NULL;
       }
}

#define is_heb_environment(x)   is_heb_word((x),dict_translate_env)
#define is_heb_tag(x)                   is_heb_word((x),dict_translate_tag)

static char *is_hebrew_command(FriBidiChar *text,int *length)
{
       char const *tag_txt,*env_txt;

       static char buffer[MAX_COMMAND_LEN*2+3];
       /* The command can consist at most of name of tag,
        * environment and breakets */

       FriBidiChar *end_ptr;
       if(*text!='\\') {
               return NULL;
       }

       end_ptr=text+1;
       if((tag_txt = is_tag_begin_or_end(&end_ptr))!=NULL
               && is_open_bracket(&end_ptr)
               && (env_txt=is_heb_environment(&end_ptr))!=NULL
               && is_close_bracket(&end_ptr))
       {
               *length=end_ptr-text;
               strcpy(buffer,"\\");
               strcat(buffer,tag_txt);
               strcat(buffer,"{");
               strcat(buffer,env_txt);
               strcat(buffer,"}");
               return buffer;
       }

       end_ptr=text+1;
       if((tag_txt=is_heb_tag(&end_ptr))!=NULL){
               *length=end_ptr-text;
               strcpy(buffer,"\\");
               strcat(buffer,tag_txt);
               return buffer;
       }
       return NULL;
}

void bidi_translate_tags(FriBidiChar *in,FriBidiChar *out,int limit)
{
       int ptr;
       int new_length,cmd_len;
       char *tr_command;
       ptr=0;
       new_length=0;

       out[0]=0;

       while(in[ptr]) {
               /* In case there is \\ before hebrew word */
               if(in[ptr]=='\\' && in[ptr+1]=='\\'){
                       ptr+=2;
                       bidi_add_str_c(out,&new_length,limit,"\\\\");
                       continue;
               }
               else if((tr_command = is_hebrew_command(in+ptr,&cmd_len))!=NULL) {
                       ptr+=cmd_len;
                       bidi_add_str_c(out,&new_length,limit,tr_command);
               }
               else {
                       bidi_add_char_u(out,&new_length,limit,in[ptr]);
                       ptr++;
               }
       }

}

void bidi_grammar_skip_blank(FriBidiChar **ptr)
{
       FriBidiChar ch;
       while((ch=**ptr)==' ' || ch=='\t' || ch=='\r' || ch=='\n')
               ++*ptr;
}

/*
* format is following:
* "lh" - latin word, hebrew word,
* "l" - one latin word
* parameters are (FriBidiChar *) for h and (char *) for l
*/
int bidi_grammar(FriBidiChar *in,char *tagname,char *format,
                                       FriBidiChar hebrew[MAX_COMMAND_LEN],
                                       char ascii[MAX_COMMAND_LEN])
{
       if(!bidi_strieq_u_a(in,tagname)) {
               return 0;
       }

       in+=strlen(tagname);

       bidi_grammar_skip_blank(&in);

       while(*format) {
               if(*format=='h'){
                       if(!is_hebrew_tag(hebrew,&in)){
                               bidi_error("Hebrew word expected");
                       }
               }
               else if(*format == 'l') {
                       if(!is_ascii_tag(ascii,&in)){
                               bidi_error("Latin word expected");
                       }
               }
               else {
                       fprintf(stderr,"Internall error\n");
                       exit(1);
               }
               format++;
               bidi_grammar_skip_blank(&in);
       }
       if(*in!=0) {
               bidi_error("Unexpected charrecter");
       }
       return 1;
}

void bidi_parse_fribidixetex_command(FriBidiChar *in)
{
       FriBidiChar unicode[MAX_COMMAND_LEN];
       char ascii[MAX_COMMAND_LEN];

       if(bidi_grammar(in,TAG_BIDI_ON,"",NULL,NULL)) {
               bidi_mode = MODE_BIDION;
       }
       else if(bidi_grammar(in,TAG_BIDI_OFF,"",NULL,NULL)) {
               bidi_mode = MODE_BIDIOFF;
       }
       else if(bidi_grammar(in,TAG_BIDI_LTR,"",NULL,NULL)) {
               bidi_mode = MODE_BIDILTR;
       }
       else if(bidi_grammar(in,TAG_BIDI_NEW_TAG,"l",NULL,ascii)) {
               bidi_add_command(ascii);
       }
       else if(bidi_grammar(in,TAG_BIDI_DIC_TAG,"hl",unicode,ascii)) {
               dict_add_tans(unicode,ascii,DICT_TAG);
       }
       else if(bidi_grammar(in,TAG_BIDI_DIC_ENV,"hl",unicode,ascii)) {
               dict_add_tans(unicode,ascii,DICT_ENV);
       }
       else {
               bidi_error("Unknown fribidixetex command");
       }
}

/*********************
* M A I N   L O O P *
*********************/

/* Main line processing function */
int bidi_process(FriBidiChar *in,FriBidiChar *out,
                                               int replace_minus,int tranlate_only,int no_mirroring)
{
       int i,is_rtl;

       if(bidi_strieq_u_a(in,"%BIDI")) {
               bidi_parse_fribidixetex_command(in);
               return 0;
       }

       if(bidi_mode != MODE_BIDIOFF) {
               is_rtl = (bidi_mode == MODE_BIDION);
               if(tranlate_only) {
                       /* In case of translation only put directly to output buffer */
                       bidi_translate_tags(in,out,MAX_LINE_SIZE);
               }
               else { /* Normal processing */
                       /* Translate hebrew tags to ascii tags */
                       bidi_translate_tags(in,translation_buffer,MAX_LINE_SIZE);

                       /* Apply BiDi algorithm */
                       bidi_add_tags(translation_buffer,out,MAX_LINE_SIZE,
                                       is_rtl ,replace_minus,no_mirroring);
               }
       }
       else {
               for(i=0;in[i];i++){
                       out[i]=in[i];
               }
               out[i]=0;
       }
       return 1;
}

void bidi_finish(void)
{
       bidi_cmd_t *tmp;
       while(bidi_command_list) {
               tmp=bidi_command_list;
               bidi_command_list=bidi_command_list->next;
               utl_free(tmp->name);
               utl_free(tmp);
       }
       if(bidi_mode != MODE_BIDIOFF) {
               fprintf(stderr,"Warning: No %%BIDIOFF Tag at the end of the file\n");
       }
}

void bidi_init(FILE *f_out)
{
       int i;

       bidi_mode = MODE_BIDIOFF;

       for(i=0;ignore_tags_list[i][0];i++) {
               bidi_add_command(ignore_tags_list[i]);
       }

}