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

  gbx_split.c

  (c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>

  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, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

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

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

#define __GBX_SPLIT_C

#include "gb_common.h"
#include "gb_error.h"
#include "gb_array.h"
#include "gbx_c_array.h"

static CARRAY *_array;
static bool _novoid;
static char *_entry;
static const char *_ptr;
static int _lptr;

static void add_char_real(const char *p)
{
	if (_lptr)
	{
		int old_len = STRING_length(_entry);

		_entry = STRING_extend(_entry, old_len + _lptr);
		memcpy(&_entry[old_len], _ptr, _lptr);
		_entry[old_len + _lptr] = 0;
	}

	_ptr = p;
	_lptr = p ? 1 : 0;
}

#define add_char(_p) \
({ \
	if ((_p) && (_p) == (_ptr + _lptr)) \
		_lptr++; \
	else \
		add_char_real(_p); \
})

static void add_entry()
{
	add_char_real(NULL);
	
	if (!_entry)
	{
		if (!_novoid)
			ARRAY_add_void((char ***)&_array->data);
	}
	else
	{
		*((char **)ARRAY_add((char ***)&_array->data)) = _entry;
		_entry = NULL;
	}
	
	//fprintf(stderr, "** add_entry\n");
}

static void split_fast(CARRAY *array, const char *str, int lstr, const char *sep, int lsep, bool no_void)
{
	const char *ptr = NULL;
	int lptr = 0;

	#define add_entry_fast() \
	({ \
		if (lptr) \
		{ \
			*((char **)ARRAY_add((char ***)&array->data)) = STRING_new(ptr, lptr); \
			lptr = 0; \
		} \
		else if (!no_void) \
		{ \
			ARRAY_add_void((char ***)&array->data); \
		} \
	})

	if (lsep == 1)
	{
		char csep = sep[0];

		while (lstr--)
		{
			if (*str == csep)
			{
				add_entry_fast();
			}
			else
			{
				if (!lptr) ptr = str;
				lptr++;
			}

			str++;
		}
	}
	else
	{
		while (lstr--)
		{
			if (memchr(sep, *str, lsep))
			{
				add_entry_fast();
			}
			else
			{
				if (!lptr) ptr = str;
				lptr++;
			}

			str++;
		}
	}

	add_entry_fast();
}

CARRAY *STRING_split(const char *str, int lstr, const char *sep, int lsep, const char *esc, int lesc, bool no_void, bool keep_esc)
{
	CARRAY *array;
	int i;
	char c;
	bool escape;
	char escl, escr;

	array = OBJECT_create(CLASS_StringArray, NULL, NULL, 0);
	if (lstr == 0)
		return array;

	if (sep == NULL || lsep == 0)
	{
		sep = ",";
		lsep = 1;
	}

	if (esc == NULL || lesc == 0)
	{
		split_fast(array, str, lstr, sep, lsep, no_void);
	}
	else
	{
		_array = array;
		_entry = NULL;
		_novoid = no_void;
		_ptr = NULL;
		_lptr = 0;

		escl = esc[0];
		if (lesc >= 2)
			escr = esc[1];
		else
			escr = escl;

		if (escr == *sep)
		{
			for (i = 0; i < lstr; i++)
			{
				c = *str;

				if (c == escl)
				{
					i++;
					str++;
					if (i < lstr)
						add_char(str);
				}
				else if (c == *sep || (lsep > 1 && memchr(&sep[1], c, lsep - 1)))
				{
					add_entry();
				}
				else
					add_char(str);

				str++;
			}
		}
		else
		{
			escape = FALSE;

			for (i = 0; i < lstr; i++)
			{
				c = *str;

				if (escape)
				{
					if (c != escr)
						add_char(str);
					else if ((i < (lstr - 1)) && str[1] == escr)
					{
						add_char(str);
						str++;
						i++;
					}
					else
					{
						escape = FALSE;
						if (keep_esc)
							add_char(str);
					}
				}
				else if (c == escl)
				{
					escape = TRUE;
					if (keep_esc)
						add_char(str);
				}
				else if (c == *sep || (lsep > 1 && memchr(&sep[1], c, lsep - 1)))
				{
					add_entry();
				}
				else
					add_char(str);

				str++;
			}
		}
		
		add_entry();
	}
	
	array->count = ARRAY_count(array->data);

	return array;
}
