/* make_strings

   Copyright (C) 2002 Free Software Foundation, Inc.

   Written by:  Alexander Malmberg <alexander@malmberg.org>
   Created: 2002

   This file is part of the GNUstep Project

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   You should have received a copy of the GNU General Public
   License along with this program; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSEnumerator.h>
#include <Foundation/NSArray.h>

#include "make_strings.h"

#include "StringsFile.h"

#include "SourceEntry.h"
#include "StringsEntry.h"


int verbose,aggressive_import,aggressive_match,aggressive_remove;


typedef struct
{
  const char *func_name;
  int num_args;
  int key_index,comment_index,table_index;
} loc_func_t;

/*
  List of functions we should look for (easy to extend).
*/
static loc_func_t loc_funcs[]=
  {
    {"_"                                  , 1,  0,-1,-1},
    {"NSLocalizedString"                  , 2,  0, 1,-1},
    {"NSLocalizedStringFromTable"         , 3,  0, 2, 1},
    {"NSLocalizedStringFromTableInBundle" , 4,  0, 3, 1},

    {"__"                                 , 1,  0,-1,-1},
    {"NSLocalizedStaticString"            , 2,  0, 1,-1},
    {},
  };
#define MAX_ARGS 4

static int isname1(unsigned char ch)
{
  if (ch=='_' || (ch>='a' && ch<='z') || (ch>='A' && ch<='Z')) return 1;
  return 0;
}

static int isname(unsigned char ch)
{
  if (isname1(ch)) return 1;
  if (ch>='0' && ch<='9') return 1;
  return 0;
}


/*
  This function attempts to parse the specified file and adds any calls to
  tables. To avoid excessive complexity and to easily handle comments and
  strings everywhere, it's written as a state machine.

  Things that can fool it:
  - Stupid use of pre-processor stuff, like '#define foo bar('.
  Solution: Don't do that.

  - Nested localization calls, like:
  'NSLocalizedString(@"foo",NSLocalizedString(@"bar",@"zot"))'.
  Solution: don't do that (with the current functions, there should never
  be any reason to).

*/
static int ParseFile(const char *filename,NSMutableDictionary *tables)
{
  FILE *f;
  NSString *filenamestr;

  /*
    0: normal parsing
    1: in '//' comment
    2: in '/ *' comment
    3: in string
    5: parsing potentially interesting name
    6: parsing uninteresting name
    7: got name, wait for '('
    8: parsing arguments
  */
  int state,old_state,skip;

  int ch,nch;

  int name=-1,nindex=0;

  int cur_line;

  unsigned char *args[MAX_ARGS];
  int arg_size[MAX_ARGS],arg_len[MAX_ARGS];
  int arg_ok[MAX_ARGS];
  int num_args;
  int i;

  int depth=0;

  void add_arg_ch(int ch)
    {
      if (arg_len[num_args]+1>=arg_size[num_args])
	{
	  arg_size[num_args]+=512;
	  args[num_args]=realloc(args[num_args],arg_size[num_args]);
	  if (!args[num_args])
	    {
	      NSLog(@"out of memory!\n");
	      exit(1);
	    }
	}
      args[num_args][arg_len[num_args]++]=ch;
      args[num_args][arg_len[num_args]]=0;
    }


  filenamestr = [NSString stringWithCString: filename
    encoding: [NSString defaultCStringEncoding]];
  if (verbose)
    printf("Parsing '%s'.\n", filename);
  f=fopen(filename,"rt");
  if (!f)
    {
      NSLog(@"Unable to open '%@': %m\n",filenamestr);
      return 1;
    }

  old_state=state=0;
  skip=0;
  cur_line=1;
  num_args=-1;

  for (i=0;i<MAX_ARGS;i++)
    {
      args[i]=NULL;
      arg_size[i]=0;
    }

  nch=fgetc(f);
  while (!feof(f))
    {
      ch=nch;
      if (ch==EOF) break;
      nch=fgetc(f);
      //		printf("ch=%02x '%c'  state=%i skip=%i\n",ch,ch,state,skip);
      if (skip)
	{
	  skip--;
	  continue;
	}

      if (ch=='\n') cur_line++;

      if (state==3)
	{
	  if (ch!='"' && num_args!=-1 && arg_ok[num_args])
	    add_arg_ch(ch);

	  if (ch=='\\')
	    {
	      if (num_args!=-1 && arg_ok[num_args])
		add_arg_ch(nch);
	      skip=1;
	    }
	  else if (ch=='"')
	    state=old_state;
	  continue;
	}

      if (state==1)
	{
	  if (ch=='\n' || ch=='\r')
	    state=old_state;
	  continue;
	}

      if (state==2)
	{
	  if (ch=='*' && nch=='/')
	    {
	      state=old_state;
	      skip=1;
	    }
	  continue;
	}

      if (ch=='"')
	{
	  old_state=state;
	  state=3;
	  continue;
	}

      if (ch=='/' && nch=='/')
	{
	  old_state=state;
	  state=1;
	  continue;
	}
      if (ch=='/' && nch=='*')
	{
	  old_state=state;
	  state=2;
	  /* skip a character so we'll parse '/ * /' correctly */
	  skip=1;
	  continue;
	}

      if (state==0 && isname1(ch))
	{
	  for (name=0;loc_funcs[name].func_name;name++)
	    if (ch==loc_funcs[name].func_name[0])
	      break;
	  if (loc_funcs[name].func_name)
	    {
	      state=5;
	      nindex=1;
	    }
	  else
	    state=6;
	  continue;
	}

      if (state==6)
	{
	  if (!isname(ch))
	    state=0;
	  continue;
	}

      if (state==5)
	{
	  if (isname(ch))
	    {
	      int old_name;
	      if (loc_funcs[name].func_name[nindex]==ch)
		{
		  nindex++;
		  continue;
		}

	      old_name=name;
	      for (name++;loc_funcs[name].func_name;name++)
		{
		  if (!strncmp(loc_funcs[old_name].func_name,loc_funcs[name].func_name,nindex) &&
		      loc_funcs[name].func_name[nindex]==ch)
		    break;
		}
	      if (loc_funcs[name].func_name)
		nindex++;
	      else
		state=6;
	      continue;
	    }
	  if (loc_funcs[name].func_name[nindex]!=0)
	    {
	      int old_name=name;
	      for (name++;loc_funcs[name].func_name;name++)
		{
		  if (!strncmp(loc_funcs[old_name].func_name,loc_funcs[name].func_name,nindex) &&
		      loc_funcs[name].func_name[nindex]==0)
		    break;
		}
	    }
	  if (!loc_funcs[name].func_name)
	    {
	      state=0;
	      continue;
	    }
	  else
	    {
	      //				printf("found call to '%s' at line %i\n",loc_funcs[name].func_name,cur_line);
	      state=7;
	    }
	}

      if (state==7)
	{
	  if (ch=='(')
	    {
	      num_args=0;
	      depth=0;
	      state=8;
	      for (i=0;i<MAX_ARGS;i++)
		{
		  arg_len[i]=0;
		  arg_ok[i]=1;
		}
	      //				printf(" start arg list, want %i args\n",loc_funcs[name].num_args);
	    }
	  else if (ch>32)
	    state=0;
	  continue;
	}

      if (state==8)
	{
	  if (depth)
	    {
	      if (ch==')' || ch==']' || ch=='}')
		depth--;
	      continue;
	    }
	  if (ch=='(' || ch=='[' || ch=='{')
	    {
	      arg_ok[num_args]=0;
	      depth++;
	      continue;
	    }
	  if (ch==')')
	    {
	      num_args++;

	      /*				{
						printf("got call to '%s', %i args\n",loc_funcs[name].func_name,num_args);
						for (i=0;i<num_args;i++)
						printf("  %3i : %i '%s'\n",i,arg_ok[i],args[i]);
						}*/

	      {
		loc_func_t *lf=&loc_funcs[name];
		if (arg_ok[lf->key_index] &&
		    (lf->comment_index==-1 || arg_ok[lf->comment_index]) &&
		    (lf->table_index==-1 || arg_ok[lf->table_index]))
		  {
		    SourceEntry *e;
		    NSString *key,*comment,*table;

		    /* TODO: let user specify source file encoding */
		    key=[NSString stringWithCString: (char*)args[lf->key_index]];

		    if (lf->comment_index==-1 || !arg_len[lf->comment_index])
		      comment=nil;
		    else
		      comment=[NSString stringWithCString: (char*)args[lf->comment_index]];

		    if (lf->table_index==-1)
		      table=@"Localizable"; /* TODO: customizable? */
		    else
		      table=[NSString stringWithCString: (char*)args[lf->table_index]];

		    e=[[SourceEntry alloc] initWithKey: key comment: comment  file: filenamestr line: cur_line];
		    [tables addEntry: e toTable: table];
		    [e release];
		  }
		else
		  {
		    NSLog(@"unable to parse call to '%s' at %s:%i\n",lf->func_name,filename,cur_line);
		  }
	      }

	      num_args=-1;
	      state=0;
	      continue;
	    }
	  if (ch==',')
	    {
	      num_args++;
	      if (num_args==MAX_ARGS)
		state=0;
	      continue;
	    }
	  if (ch>32 && ch!='@')
	    arg_ok[num_args]=0;
	  continue;
	}
    }

  for (i=0;i<MAX_ARGS;i++)
    if (args[i])
      free(args[i]);

  fclose(f);
  return 0;
}


@implementation NSMutableDictionary (make_strings)
-(void) addEntry: (SourceEntry *)e  toTable: (NSString *)table
{
  NSMutableArray *a;
  a=[self objectForKey: table];
  if (!a)
    {
      a=[[NSMutableArray alloc] init];
      [self setObject: a forKey: table];
      [a release];
    }
  [a addObject: e];
}
@end


static void UpdateTable(NSArray *source_table,NSString *filename)
{
  int i,c;
  StringsFile *sf;

  if (verbose)
    printf("Updating '%s'.\n", [filename cString]);
  sf=[[StringsFile alloc] initWithFile: filename];

  c=[source_table count];
  for (i=0;i<c;i++)
    {
      [sf addSourceEntry: [source_table objectAtIndex: i]];
    }

  [sf writeToFile: filename];
  DESTROY(sf);
}


static void HandleLanguage(NSString *language_name,NSMutableDictionary *source_entries)
{
  NSEnumerator *e;
  NSString *table_name;
  NSString *filename;
  CREATE_AUTORELEASE_POOL(arp);

  if (verbose)
    printf("Updating language '%s'.\n", [language_name cString]);
  for (e=[source_entries keyEnumerator];(table_name=[e nextObject]);)
    {
      filename=[[[NSString stringWithFormat: @"%@.lproj",language_name]
		  stringByAppendingPathComponent:
		    [NSString stringWithFormat: @"%@.strings",table_name]]
		 stringByStandardizingPath];
      UpdateTable([source_entries objectForKey: table_name],filename);
    }

#if GS_WITH_GC == 0
  DESTROY(arp);
#endif
}


int main(int argc, char **argv)
{
#if GS_WITH_GC == 0
  CREATE_AUTORELEASE_POOL(arp);
#endif

  NSMutableDictionary *source_entries;

  NSMutableArray *languages=[[NSMutableArray alloc] init];

  int error;

  {
    int i, j;
    char *c;
    for (j = i = 1; i < argc; i++)
      {
	c=argv[i];
	if (!strcmp (c,"--help"))
	  {
	    printf ("Syntax: %s [--help] [--verbose] [--aggressive-import] [--aggressive-match] [--aggressive-remove] [-L languages] files.[hmc...]\n",
		    argv[0]);
	    printf("\n");
	    printf("Example: %s -L \"English Swedish German\" *.[hm]\n",
		   argv[0]);
	    return 0;
	  }
	else if (!strcmp (c,"--verbose"))
	  {
	    verbose = 1;
	  }
	else if (!strcmp (c,"--aggressive-import"))
	  {
	    aggressive_import = 1;
	    aggressive_match = 1;
	  }
	else if (!strcmp (c,"--aggressive-match"))
	  {
	    aggressive_match = 1;
	  }
	else if (!strcmp(c,"--aggressive-remove"))
	  {
	    aggressive_remove=1;
	    aggressive_match=1;
	  }
	else if (!strcmp (c,"-L"))
	  {
	    char *d,*d2;
	    if (++i == argc)
	      {
		NSLog (@"syntax error\n");
		return 1;
	      }
	    d = argv[i];
	    while (1)
	      {
		d2 = strchr (d,' ');
		if (d2)
		  *d2=0;
		[languages addObject: [NSString stringWithCString: d]];
		d = d2 + 1;
		if (!d2)
		  break;
	      }
	  }
	else
	  {
	    argv[j++] = c;
	  }
      }
    argc = j;
  }

  /* Remove any empty language from the list.  */
  {
    int k;

    for (k = [languages count] - 1; k >= 0; k--)
      {
	NSString *language = [languages objectAtIndex: k];
	if ([language isEqualToString: @""])
	  {
	    [languages removeObjectAtIndex: k];
	  }
      }
  }


  if (![languages count])
    {
      NSLog (@"No languages specified!\n");
      return 1;
    }
  if (argc == 1)
    {
      NSLog (@"No files specified!\n");
      return 1;
    }

  source_entries = [[NSMutableDictionary alloc] init];
  error = 0;
  {
    int i;
    for (i = 1; i < argc; i++)
      error += ParseFile (argv[i], source_entries);
  }

  if (!error)
    {
      int i, c = [languages count];
      for (i = 0; i < c; i++)
	{
	  HandleLanguage ([languages objectAtIndex: i], source_entries);
	}
    }

#if GS_WITH_GC == 0
  DESTROY(arp);
#endif
  if (error)
    return 1;
  else
    return 0;
}

