/***************************************************************************************
 *
 *  WRITEPAD(r): Handwriting Recognition Engine (HWRE) and components.
 *  Copyright (c) 2001-2016 PhatWare (r) Corp. All rights reserved.
 *
 *  Licensing and other inquires: <developer@phatware.com>
 *  Developer: Stan Miasnikov, et al. (c) PhatWare Corp. <http://www.phatware.com>
 *
 *  WRITEPAD HWRE 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 *  AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 *  INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 *  FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL PHATWARE CORP.
 *  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, SPECIAL, INCIDENTAL,
 *  INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER,
 *  INCLUDING WITHOUT LIMITATION, LOSS OF PROFIT, LOSS OF USE, SAVINGS
 *  OR REVENUE, OR THE CLAIMS OF THIRD PARTIES, WHETHER OR NOT PHATWARE CORP.
 *  HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 *  POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with WritePad.  If not, see <http://www.gnu.org/licenses/>.
 *
 **************************************************************************************/

#include "hwr_sys.h"
#include "ams_mg.h"
#include "pws.h"
#include "ws_p.h"

#if PG_DEBUG || PG_DEBUG_MAC
#include <stdio.h>
#endif

#if PG_DEBUG
#include "pg_debug.h"
#include "xr_exp.h"
#endif

/* ------------------ Switches ---------------------------------- */

#define  WS_USE_NNET     ON                // Switch for NN usage
#define  WS_USE_DIST_F   OFF               // Switch for NN usage
#define  WS_USE_DECN_F   OFF               // Switch for NN usage
#define  WS_NNET_NSEGLEV (-100)            // For sure initialization

/* ------------------ Internal defines -------------------------- */

#define HIST_ADD(v, loc) (*(loc) = (_UCHAR)((*(loc) + v < V_LIMIT) ? (*(loc) + v) : V_LIMIT))
#define HIST_SUB(v, loc) (*(loc) = (_UCHAR)((*(loc) > v) ? (*(loc) - v) : 1))
#define HIST_POS(x)      ((x)/HIST_REDUCT)
#define HIST_ADJPOS(x)   (((x)/HIST_REDUCT)*HIST_REDUCT)

#define WSAbs(v)         (((v) >= 0) ? (v) : (-(v)))

#define HORZ_GET(x)      (pwsd->horz[(x)/HORZ_REDUCT])
#define HORZ_PUT(x, v)   (pwsd->horz[(x)/HORZ_REDUCT] = (_SHORT)(v))

#define GET_AVE(g, l)    (((g) > 0) ? ((g) + (l))/2 : (l))

#define CUT_VAL (CUT_LINE_POS*HIST_REDUCT)
#define PIK_VAL ((PIK_UP+PIK_DN)*HIST_REDUCT)


WS_1

_INT  WordStrokes(PS_point_type _PTR stroke, p_ws_control_type pwsc, p_ws_results_type pwsr)
{
	p_ws_data_type          pwsd;
	p_ws_memory_header_type pwmh = _NULL;

	WS_2

	if (pwsc->flags & WS_FL_CLOSE)
	{
		ReleaseWSData(pwsc, &pwmh);
		goto exit;
	}

	// ------- Startup -----------------------------------------------------

	if (InitWSData(pwsc, &pwmh))
	{
		goto err;
	}

	pwsd = pwmh->pwsd;
	pwsd->stroke = stroke;
	pwsd->stroke_num_points = pwsc->num_points;

	// ------- If timeout -- no more data, null stroke -- final wordcut ----

	if (pwsd->global_cur_stroke > 0 && pwsd->stroke_num_points == 0 && (pwsd->stroke_flags & WS_FL_LAST))
	{
		pwsd->stroke_flags |= WS_FL_FAKE;
		goto done;
	}

	// ------- Check situation ---------------------------------------------

	if (pwsd->global_cur_stroke >= MAX_STROKES - 1)
	{
		goto err;
	}
	if (pwsd->stroke_num_points == 0)
	{
		goto err;
	}
	if (pwsr->pwsa == _NULL)
	{
		goto err;
	}

	// ------- Count max height of the stroke and filetering length --------

	if (WS_GetStrokeBoxAndSlope(pwsd))
	{
		goto err;
	}

	// ----------------- Guess h line size ---------------------------------

	WS_CalcLineHeight(pwsd);

	// ============= Consider start of a new line! ========================

	if (pwsd->line_cur_stroke > 0 && WS_NewLine(pwsd))
	{
		pwsd->in_x_delay = 0;
		pwsd->line_finished = WS_NEWLINE;
		if (WordLineStrokes(pwsd, pwsr))
		{
			goto err;
		}
		InitForNewLine(pwsd);
	}

	// ------------ Hist the stroke ---------------------------------------

	if (WS_HistTheStroke(pwsd))
	{
		goto err;
	}

	// ------------------ Add stroke info to Gen Hist ---------------------

	WS_AddStrokeToHist(pwsd);

	// --------- Write down HORZ values -----------------------------------

	WS_WriteStrokeHorzValues(pwsd);

	// --------------- Get info on inline spaces --------------------------

	if (WS_CalcGaps(pwsd))
	{
		goto err;
	}

	// ---- Recount postprocessed gap sizes ---------------------------

	WS_PostprocessGaps(pwsd);

	// --------------- Get number of line piks ----------------------------

	WS_CountPiks(pwsd);

	// ------------------ Line analysis -----------------------------------

	WS_SetLineVars(pwsd);

	// ------------------ Final word seg, if needed -----------------------

done:

	if (pwsc->flags & WS_FL_LAST)       // It was last stroke
	{
		pwsd->in_x_delay = 0;
		pwsd->line_finished = WS_ALLSENT;
		if (WordLineStrokes(pwsd, pwsr))
		{
			goto err;
		}

		WS_FlyLearn(pwsc, pwmh, pwsd);

		ReleaseWSData(pwsc, &pwmh);

		WS_10
	}
	else
	{
		if (pwsc->x_delay > 0)     // On line reading requested
		{
			pwsd->in_x_delay = pwsc->x_delay * pwsd->line_inword_dist * 2;

			if (WordLineStrokes(pwsd, pwsr))
			{
				goto err;
			}

			pwsd->line_last_ws_try = pwsd->line_end;
		}

		if ((pwsd->stroke_flags & WS_FL_FAKE) == 0)
		{
			pwsd->global_cur_stroke++;
			pwsd->line_cur_stroke++;
		}

		UnlockWSData(pwsc, &pwmh);
	}

exit:
	return 0;
err:
	ReleaseWSData(pwsc, &pwmh);
	return 1;
}

/* ************************************************************************* */
/*      Allocate and/or initialize ws data structure                         */
/* ************************************************************************* */
_INT  InitWSData(p_ws_control_type pwsc, p_ws_memory_header_type _PTR ppwmh)
{
	register p_ws_data_type pwsd;
	p_ws_memory_header_type pwmh;
	p_ws_lrn_type           lrn;

	if (pwsc == _NULL)
	{
		goto err;
	}
	if (pwsc->num_points <= 0 && (pwsc->flags & WS_FL_LAST) == 0)
	{
		goto err;
	}
	if (pwsc->flags & WS_FL_CLOSE)
	{
		goto err;
	}

	if (pwsc->hdata == _NULL)
	{
		pwsc->hdata = HWRMemoryAllocHandle(sizeof(ws_memory_header_type));
		if (pwsc->hdata == _NULL)
		{
			goto err;
		}
		pwmh = (p_ws_memory_header_type) HWRMemoryLockHandle(pwsc->hdata);
		if (pwmh == _NULL)
		{
			goto err;
		}
		HWRMemSet(pwmh, 0, sizeof(ws_memory_header_type));
	}
	else
	{
		pwmh = (p_ws_memory_header_type) HWRMemoryLockHandle(pwsc->hdata);
		if (pwmh == _NULL)
		{
			goto err;
		}
	}

	if (pwmh->hwsd == _NULL)
	{
		pwmh->hwsd = HWRMemoryAllocHandle(sizeof(*pwsd));
		if (pwmh->hwsd == _NULL)
		{
			goto err;
		}
		pwsd = (p_ws_data_type) HWRMemoryLockHandle(pwmh->hwsd);
		if (pwsd == _NULL)
		{
			goto err;
		}
		HWRMemSet(pwsd, 0, sizeof(*pwsd));

		pwsd->in_x_delay = 0;
		pwsd->sure_level = pwsc->sure_level;
		pwsd->in_word_dist = pwsc->word_dist_in;
		if (pwsd->in_word_dist < 0)
		{
			pwsd->in_word_dist = 0;
		}
		if (pwsd->in_word_dist > 10)
		{
			pwsd->in_word_dist = 10;
		}
		pwsd->in_line_dist = pwsc->line_dist_in;
		if (pwsd->in_line_dist < 0)
		{
			pwsd->in_line_dist = 0;
		}

		lrn = (pwsc->word_dist_in == 0 && pwmh->lrn.h_bord_history > 0) ? &(pwmh->lrn) : _NULL;

		pwsd->def_h_bord = (lrn) ? lrn->h_bord_history : pwsc->def_h_line;

		pwsd->line_h_bord = pwsd->def_h_bord;
		pwsd->line_word_dist = pwsd->def_h_bord;
		pwsd->line_inword_dist = (lrn) ? lrn->inword_dist_history : pwsd->def_h_bord;
		pwsd->line_inline_dist = (lrn) ? lrn->inline_dist_history : pwsd->def_h_bord;
		pwsd->line_sep_let_level = (lrn) ? lrn->sep_let_history : DEF_SEP_LET_LEVEL;

		pwsd->global_line_ave_y_size = pwsd->def_h_bord;
		pwsd->global_dy_sum = pwsd->def_h_bord;
		pwsd->global_num_dy_strokes = 1;

		if (lrn)
		{
			pwsd->global_h_bord = lrn->h_bord_history;
			pwsd->global_inword_dist = lrn->inword_dist_history;
			pwsd->global_inline_dist = lrn->inline_dist_history;
			pwsd->global_sep_let_level = lrn->sep_let_history;
			pwsd->global_slope = lrn->slope_history;
			pwsd->global_slope_dy = lrn->h_bord_history * 10;
			pwsd->global_slope_dx = (pwsd->global_slope*pwsd->global_slope_dy) / 100;
		}

		pwsd->cmp = pwsc->cmp;
		InitForNewLine(pwsd);
	}
	else
	{
		pwsd = (p_ws_data_type) HWRMemoryLockHandle(pwmh->hwsd);
		if (pwsd == _NULL)
		{
			goto err;
		}
		if (pwsd->gaps_handle)
		{
			pwsd->gaps = (ws_gaps_a_type) HWRMemoryLockHandle(pwsd->gaps_handle);
		}
		pwsd->in_flags = pwsc->flags;
		pwsd->stroke_flags = (pwsc->flags & (WS_FL_LAST | WS_FL_FAKE));
	}

	pwmh->pwsd = pwsd;
	*ppwmh = pwmh;

	return 0;
err:
	return 1;
}

/* ************************************************************************* */
/*      Free or clear ws data structure                                      */
/* ************************************************************************* */
_INT  ReleaseWSData(p_ws_control_type pwsc, p_ws_memory_header_type _PTR ppwmh)
{
	p_ws_data_type          pwsd = _NULL;
	p_ws_memory_header_type pwmh = _NULL;

	if (*ppwmh)
	{
		pwmh = *ppwmh;
	}
	else
		if (pwsc->hdata)
		{
			pwmh = (p_ws_memory_header_type) HWRMemoryLockHandle(pwsc->hdata);
		}

	if (pwmh)
	{
		pwsd = pwmh->pwsd;
	}

	if (pwmh && !pwsd) if (pwmh->hwsd)
		{
			pwsd = (p_ws_data_type) HWRMemoryLockHandle(pwmh->hwsd);
		}

	if (pwsd)
	{
		if (pwsd->s_hist)
		{
			HWRMemoryFree(pwsd->s_hist);
			pwsd->s_hist = _NULL;
		}
		if (pwsd->gaps)
		{
			HWRMemoryUnlockHandle(pwsd->gaps_handle);
			pwsd->gaps = _NULL;
		}
		if (pwsd->gaps_handle)
		{
			HWRMemoryFreeHandle(pwsd->gaps_handle);
		}

		HWRMemoryUnlockHandle(pwmh->hwsd);
		pwmh->pwsd = _NULL;
		HWRMemoryFreeHandle(pwmh->hwsd);
		pwmh->hwsd = _NULL;
	}

	if ((pwsc->flags & WS_FL_CLOSE) && pwsc->hdata)
	{
		if (pwmh)
		{
			HWRMemoryUnlockHandle(pwsc->hdata);
		}
		*ppwmh = _NULL;
		HWRMemoryFreeHandle(pwsc->hdata);
		pwsc->hdata = _NULL;
	}

	if (ppwmh) if (*ppwmh && pwsc->hdata)
		{
			HWRMemoryUnlockHandle(pwsc->hdata);
			*ppwmh = _NULL;
		}

	return 0;
}

/* ************************************************************************* */
/*      Unlock wsdata structure                                              */
/* ************************************************************************* */
_INT  UnlockWSData(p_ws_control_type pwsc, p_ws_memory_header_type _PTR ppwmh)
{
	p_ws_data_type          pwsd = _NULL;
	p_ws_memory_header_type pwmh = _NULL;

	if (ppwmh)
	{
		pwmh = *ppwmh;
	}
	else
		if (pwsc->hdata)
		{
			pwmh = (p_ws_memory_header_type) HWRMemoryLockHandle(pwsc->hdata);
		}

	if (pwmh)
	{
		pwsd = pwmh->pwsd;
	}

	if (pwmh && !pwsd) if (pwmh->hwsd)
		{
			pwsd = (p_ws_data_type) HWRMemoryLockHandle(pwmh->hwsd);
		}

	if (pwsd)
	{
		if (pwsd->s_hist)
		{
			HWRMemoryFree(pwsd->s_hist);
			pwsd->s_hist = _NULL;
		}
		if (pwsd->gaps)
		{
			HWRMemoryUnlockHandle(pwsd->gaps_handle);
			pwsd->gaps = _NULL;
		}

		HWRMemoryUnlockHandle(pwmh->hwsd);
		pwmh->pwsd = _NULL;
	}

	if (ppwmh) if (*ppwmh && pwsc->hdata)
		{
			HWRMemoryUnlockHandle(pwsc->hdata);
			*ppwmh = _NULL;
		}

	return 0;
}

/* ************************************************************************* */
/*      Initialize variables for starting of new line                        */
/* ************************************************************************* */
_INT  InitForNewLine(p_ws_data_type pws_data)
{
	register p_ws_data_type pwsd = pws_data;

	// ----------- Recalculate global data ------------------

	if (pwsd->global_cur_line > 0)
	{
		pwsd->global_num_extr += pwsd->line_extr;
		pwsd->global_word_len += pwsd->line_word_len;

		pwsd->global_slope_dx /= 2;    // Reduce influence of prev text
		pwsd->global_slope_dy /= 2;

		pwsd->global_inword_dist = GET_AVE(pwsd->global_inword_dist, pwsd->line_inword_dist);

		pwsd->global_word_dist = GET_AVE(pwsd->global_word_dist, pwsd->line_word_dist);
		pwsd->global_inline_dist = GET_AVE(pwsd->global_inline_dist, pwsd->line_inline_dist);
		pwsd->global_sep_let_level = GET_AVE(pwsd->global_sep_let_level, pwsd->line_sep_let_level);

		pwsd->global_bw_sp = GET_AVE(pwsd->global_bw_sp, pwsd->line_bw_sp);
		pwsd->global_sw_sp = GET_AVE(pwsd->global_sw_sp, pwsd->line_sw_sp);

		pwsd->line_inline_dist = pwsd->global_inline_dist;
		pwsd->line_inword_dist = pwsd->global_inword_dist;
		pwsd->line_sep_let_level = GET_AVE(pwsd->def_sep_let_level, pwsd->global_sep_let_level);

		HWRMemSet(pwsd->hist, 0, sizeof(pwsd->hist));
		HWRMemSet(pwsd->horz, 0, sizeof(pwsd->horz));

		if (pwsd->line_cur_stroke > 0)
		{
			pwsd->xstrokes[0] = pwsd->xstrokes[pwsd->line_cur_stroke];
			pwsd->line_cur_stroke = 0;
		}
	}

	// --------------- Init line data -------------------

	pwsd->line_start = TABLET_XS;
	pwsd->line_end = 0;
	pwsd->line_active_start = TABLET_XS;
	pwsd->line_active_end = 0;
	pwsd->line_extr = 0;
	pwsd->line_last_ws_try = 0;

	pwsd->line_word_len = 0;
	pwsd->line_bw_sp = 0;

	pwsd->line_finished = 0;
	pwsd->global_cur_line += 1;
	pwsd->line_st_word = pwsd->global_num_words;
	pwsd->line_st_stroke = pwsd->global_cur_stroke;

	pwsd->line_flags = 0;
	if (pwsd->stroke_flags & ST_FL_NL_GESTURE)
	{
		pwsd->line_flags |= LN_FL_NL_GESTURE;
	}

	return 0;
}

/* ************************************************************************* */
/*      Make line segmentation                                               */
/* ************************************************************************* */
_INT  WordLineStrokes(p_ws_data_type pws_data, p_ws_results_type pwsr)
{
	_INT     i, j, k, l, m, n;
	_INT     br, s;
	_INT     dist;
	_INT     num_words, wn;
	_INT     sep_let_level;
	_INT     inword_dist;
	_INT     line_end_stroke;
	_INT     start, end, st, en, ws, we, fws, fwe;
	_INT     finished_nw, finished_x, finished_ns;
	_UCHAR   finished_strokes[MAX_STROKES];
	_INT     word_dist;
	_INT     slope;
	register p_ws_data_type pwsd = pws_data;
	p_word_strokes_array_type w_str = pwsr->pwsa;
	_UCHAR(*sp)[MAX_WORDS][MAX_STROKES][2] = _NULL;

	// ---- Set local variables -----------------------------------

	line_end_stroke = pwsd->line_cur_stroke;
	if (pwsd->line_finished == WS_NEWLINE)
	{
		line_end_stroke--;
	}

	sep_let_level = GET_AVE(pwsd->global_sep_let_level, pwsd->line_sep_let_level);
	inword_dist = GET_AVE(pwsd->global_inword_dist, pwsd->line_inword_dist);
	//  inline_dist   = GET_AVE(pwsd->global_inline_dist, pwsd->line_inline_dist);

	// ---- Get current word dist -------------------------------------

	WS_GetWordDist(pwsd);

	word_dist = pwsd->line_word_dist;

	// ---- Get current 'finished' info -------------------------------

	finished_x = pwsd->line_start;
	finished_ns = 0;
	finished_nw = pwsd->line_st_word;
	for (i = pwsd->line_st_word; i < pwsd->global_num_words; i++)
	{
		if ((*w_str)[i].flags & WS_FL_PROCESSED)
		{
			finished_nw = i + 1;
			finished_x = (*w_str)[i].word_x_end;
			k = (*w_str)[i].first_stroke_index;
			for (j = 0; j < (*w_str)[i].num_strokes; j++)
			{
				finished_strokes[finished_ns++] = pwsr->stroke_index[k + j];
			}
		}
	}

	// ---------------------- Make word segmentation -----------------------

	num_words = WS_SegmentWords(finished_x, pwsd);

	// --------------- Assign strokes to words ---------------------

	sp = (_UCHAR(*)[MAX_WORDS][MAX_STROKES][2])HWRMemoryAlloc((sizeof((*sp)[0]))*num_words);
	if (sp == _NULL)
	{
		goto err;
	}

	wn = finished_nw;
	for (j = 0; j < num_words; j++, wn++)
	{
		st = pwsd->xwords[j].st_gap;
		ws = (*pwsd->gaps)[st].loc + (*pwsd->gaps)[st].size / 4;
		en = pwsd->xwords[j].en_gap;
		we = (*pwsd->gaps)[en].loc + (*pwsd->gaps)[en].size / 4;

		fws = (j == 0) ? pwsd->line_start : ws;
		fwe = (j == num_words - 1) ? pwsd->line_end : we;

		for (l = 0, m = 0; l <= line_end_stroke; l++)
		{
			if (l + pwsd->line_st_stroke == pwsd->global_cur_stroke &&
			        (pwsd->stroke_flags & WS_FL_FAKE))
			{
				break;    // Do not include fake stroke in words
			}

			for (i = 0, k = 0; i < finished_ns; i++)    // Check current stroke for not belonging to a finished  word
				if (l + pwsd->line_st_stroke == finished_strokes[i])
				{
					k = 1;
					break;
				}
			if (k)
			{
				continue;
			}

			start = pwsd->xstrokes[l].st;
			end = pwsd->xstrokes[l].end;

			if ((start >= fws && start <= fwe) ||
			        (end >= fws && end <= fwe) ||
			        (start <= fws && end >= fwe))
			{
				p_ws_gaps_type gap = &(*pwsd->gaps)[st + 1];

				(*sp)[j][m][0] = (_UCHAR) (pwsd->line_st_stroke + l);

				k = (pwsd->xstrokes[l].a_end + pwsd->xstrokes[l].end + 1) / 2;
				n = 0;
				for (i = st + 1; i < en; i++, gap++) // Search corresponding GAP
				{
					if (k >= gap->lst && k < gap->lst + gap->low)
					{
						n = gap->size;
						break;
					}
				}

				if (n)
				{
					(*sp)[j][m][1] = (_UCHAR) (gap->k_sure + 100);
				}
				else
				{
					(*sp)[j][m][1] = 0;
				}
				m++;
			}
		}

		if (j < num_words - 1)
		{
			k = (_INT) (100 * (_LONG) ((*pwsd->gaps)[en].size / (2 * (_LONG) word_dist)));
			if (k > 100)
			{
				k = 100;
			}
		}
		else
		{
			k = 100;
		}

		n = HWRMax(fws, finished_x);
		slope = pwsd->global_slope;
		if (slope < -127)
		{
			slope = -127;
		}
		if (slope > 127)
		{
			slope = 127;
		}

		(*w_str)[wn].word_num = (_UCHAR) ((finished_nw - pwsd->line_st_word) + j + 1);
		(*w_str)[wn].line_num = (_UCHAR) (pwsd->global_cur_line);
		(*w_str)[wn].seg_sure = (_UCHAR) (k);
		(*w_str)[wn].sep_let_level = (_UCHAR) (sep_let_level);
		(*w_str)[wn].slope = (_SCHAR) (slope);
		(*w_str)[wn].word_mid_line = (_SHORT) (HORZ_GET((n + fwe) / 2));
		(*w_str)[wn].word_x_st = (_SHORT) ((*pwsd->gaps)[st].loc + (*pwsd->gaps)[st].size / 2);
		(*w_str)[wn].word_x_end = (_SHORT) ((*pwsd->gaps)[en].loc - (*pwsd->gaps)[en].size / 2);
		(*w_str)[wn].writing_step = (_SHORT) (inword_dist);
		(*w_str)[wn].ave_h_bord = (_SHORT) (pwsd->line_h_bord);
		(*w_str)[wn].num_strokes = (_UCHAR) (m);
		(*w_str)[wn].flags = 0;
		if (pwsd->line_finished)
		{
			(*w_str)[wn].flags |= WS_FL_FINISHED;
		}
		if (wn == pwsd->line_st_word && (pwsd->line_flags & LN_FL_NL_GESTURE))
		{
			(*w_str)[wn].flags |= WS_FL_NL_GESTURE;
		}
		if (pwsd->global_num_extr + pwsd->line_extr > WS_SPNUMEXTRENOUGH)
		{
			(*w_str)[wn].flags |= WS_FL_SPSURE;
		}
	}

	// ---------------- detect overlapping strokes ---------------------

	for (s = 1; s <= pwsd->line_cur_stroke && s < MAX_STROKES; s++)
	{
		br = 0;

		for (i = 0, wn = finished_nw; i < num_words - 1 && br == 0; i++, wn++)
		{
			st = pwsd->xwords[i].st_gap;
			fws = (*pwsd->gaps)[st].loc + (*pwsd->gaps)[st].size / 4;
			en = pwsd->xwords[i].en_gap;
			fwe = (*pwsd->gaps)[en].loc + (*pwsd->gaps)[en].size / 4;

			for (j = 0; j < (*w_str)[wn].num_strokes && br == 0; j++)
			{
				for (k = 0; k < (*w_str)[wn + 1].num_strokes && br == 0; k++)
				{
					if ((*sp)[i][j][0] == (*sp)[i + 1][k][0])
					{
						l = (*sp)[i][j][0] - pwsd->line_st_stroke;
						start = pwsd->xstrokes[l].st;
						end = pwsd->xstrokes[l].end;

						m = WSAbs(fws - start);
						m += WSAbs(fwe - end);

						m = (HWRMax(fwe, end) - HWRMin(fws, start)) - m;

						st = pwsd->xwords[i + 1].st_gap;
						ws = (*pwsd->gaps)[st].loc + (*pwsd->gaps)[st].size / 4;
						en = pwsd->xwords[i + 1].en_gap;
						we = (*pwsd->gaps)[en].loc + (*pwsd->gaps)[en].size / 4;

						n = WSAbs(ws - start);
						n += WSAbs(we - end);

						n = (HWRMax(we, end) - HWRMin(ws, start)) - n;

						if (m >= n) // Belongs to first more
						{
							HWRMemCpy(&(*sp)[i + 1][k][0], &(*sp)[i + 1][k + 1][0], (MAX_STROKES - (k + 1))*sizeof((*sp)[0][0]));
							(*w_str)[wn + 1].num_strokes--;
						}
						else // Belongs to second more
						{
							HWRMemCpy(&(*sp)[i][j][0], &(*sp)[i][j + 1][0], (MAX_STROKES - (j + 1))*sizeof((*sp)[0][0]));
							(*w_str)[wn].num_strokes--;
							br = 1;
						}
					}
				}
			}
		}
	}

	// ---------------- Write output strokes lineout -------------------

	wn = finished_nw;
	n = pwsd->line_st_stroke + finished_ns;
	for (j = 0; j < num_words; j++, wn++)
	{
		for (k = 0; k < (*w_str)[wn].num_strokes; k++)
		{
			pwsr->stroke_index[n + k] = (*sp)[j][k][0];
			pwsr->k_surs[n + k] = (_SCHAR) ((*sp)[j][k][1] - 100);
		}

		(*w_str)[wn].first_stroke_index = (_UCHAR) (n);
		n += (*w_str)[wn].num_strokes;
	}

	// ---------------- If on-line mode -- clear illegal words ---------

	wn = finished_nw;
	for (j = 0; pwsd->in_x_delay > 0 && j < num_words; j++, wn++)
	{
		en = pwsd->xwords[j].en_gap;
		we = (*pwsd->gaps)[en].loc;
		if (we > pwsd->line_end - pwsd->in_x_delay)
		{
			(*w_str)[wn].flags |= WS_FL_TENTATIVE;
		}
	}

	// ---------------- Check & clear 0 stroke words -------------------

	wn = finished_nw;
	for (j = 0; j < num_words; j++)
	{
		if ((*w_str)[wn + j].num_strokes == 0)
		{
			HWRMemCpy(&(*w_str)[wn + j], &(*w_str)[wn + j + 1], sizeof((*w_str)[0]));
			num_words--;
		}
	}

	// ---------------- At end of line check for carriage dash ---------

	if (pwsd->line_finished == WS_NEWLINE && num_words > 0 && pwsd->prev_stroke_dx > pwsd->prev_stroke_dy * 2)
	{
		st = HIST_POS(pwsd->line_start);
		en = HIST_POS(pwsd->line_end);
		for (en = en - 1; pwsd->hist[en] == 0 && en > st; en--);
		for (i = en, j = 0; i > st && j <= 1; i--)
		{
			if ((k = (pwsd->hist[i] & HIST_FIELD)) == 0)
			{
				break;
			}
			if (k > 1)
			{
				if (k == 1 + CUT_VAL / HIST_REDUCT)
				{
					j++;
				}
				else
				{
					j = 2;
					break;
				}
			}
		}

		if (j == 1 && (*w_str)[finished_nw + num_words - 1].word_x_st < (i*HIST_REDUCT - inword_dist))
		{
			dist = (en - i)*HIST_REDUCT;
			if (dist > pwsd->line_h_bord / 2 && dist < pwsd->line_h_bord * 2)
			{
				(*w_str)[finished_nw + num_words - 1].flags |= WS_FL_CARRYDASH;
			}
		}
	}

	// ---------------- Write found word info --------------------------

	pwsd->global_num_words = finished_nw + num_words;

	pwsr->num_words = (_UCHAR) (pwsd->global_num_words);  /* Num of created words */
	pwsr->num_finished_words = (_UCHAR) (finished_nw); /* Num of finished words */
	pwsr->num_finished_strokes = (_UCHAR) (pwsd->line_st_stroke + finished_ns); /* Num of eternally finished strokes */

	// ---------------- Debug printing of the results ------------------

	WS_3

	WS_4

	WS_5

	if (sp != _NULL)
	{
		HWRMemoryFree(sp);
	}

	return 0;
err:
	if (sp != _NULL)
	{
		HWRMemoryFree(sp);
	}
	return 1;
}


/* ************************************************************************* */
/*      Calculate stroke initial parameters                                  */
/* ************************************************************************* */
_INT WS_GetStrokeBoxAndSlope(p_ws_data_type pws_data)
{
	_INT   i, j;
	_INT   x, y;
	_INT   dx, dy;
	_INT   adx, ady;
	_INT   num_points;
	_INT   min_stroke_x, max_stroke_x, min_stroke_y, max_stroke_y;
	_INT   hslp;
	_LONG  x_sum, y_sum;
	_LONG  dx_sum, dy_sum;
	p_ws_data_type pwsd = pws_data;
	PS_point_type _PTR stroke;


	hslp = pwsd->line_h_bord / 16;
	if (hslp < 3)
	{
		hslp = 3;
	}
	stroke = pwsd->stroke;
	num_points = pwsd->stroke_num_points;
	min_stroke_y = min_stroke_x = TABLET_XS;
	max_stroke_y = max_stroke_x = 0;
	x_sum = 0;
	y_sum = 0;
	dx_sum = 0;
	dy_sum = 0;
	for (i = 0, j = 0; i < num_points; i++)
	{
		x = stroke[i].x;
		y = stroke[i].y;

		if (y < 0)
		{
			break;
		}

		x_sum += x;
		y_sum += y;

		if (y > max_stroke_y)
		{
			max_stroke_y = y;
		}
		if (y < min_stroke_y)
		{
			min_stroke_y = y;
		}

		if (x > max_stroke_x)
		{
			max_stroke_x = x;
		}
		if (x < min_stroke_x)
		{
			min_stroke_x = x;
		}
		dx = (x - stroke[j].x);
		adx = WSAbs(dx);
		dy = -(y - stroke[j].y);
		ady = WSAbs(dy);

		if (ady + adx > hslp) // if too close, skip
		{
			j = i;

			if (dy != 0 && (100 * (_LONG) (adx)) / (_LONG) (ady) <= 100l) // if too horizontal -- skip
			{
				if (dy < 0)  // going  down the trace, notice more
				{
					dy = -(dy * 8);
					dx = -(dx * 8);
				}

				dx_sum += dx;
				dy_sum += dy;
			}
		}
	}

	if (i == 0)
	{
		goto err;
	}

	pwsd->stroke_num_points = i;
	pwsd->stroke_min_x = min_stroke_x;
	pwsd->stroke_max_x = max_stroke_x + 1;
	pwsd->stroke_min_y = min_stroke_y;
	pwsd->stroke_max_y = max_stroke_y + 1;

	pwsd->prev_stroke_dy = pwsd->stroke_dy;
	pwsd->prev_stroke_dx = pwsd->stroke_dx;

	pwsd->stroke_dy = max_stroke_y - min_stroke_y + 1;
	pwsd->stroke_dx = max_stroke_x - min_stroke_x + 1;

	pwsd->stroke_wx_pos = (_INT) (x_sum / i);
	pwsd->stroke_wy_pos = (_INT) (y_sum / i);

	pwsd->xstrokes[pwsd->line_cur_stroke].st = (_SHORT) (min_stroke_x);
	pwsd->xstrokes[pwsd->line_cur_stroke].end = (_SHORT) (max_stroke_x + 1);
	pwsd->xstrokes[pwsd->line_cur_stroke].top = (_SHORT) (pwsd->stroke_min_y);

	if (pwsd->stroke_num_points >= 10 && dy_sum > 160)
	{
		pwsd->global_slope_dx += dx_sum;
		pwsd->global_slope_dy += dy_sum;
		pwsd->global_slope = (_INT) ((100 * (_LONG) (pwsd->global_slope_dx)) / (_LONG) (pwsd->global_slope_dy));
		if (pwsd->global_slope_dy < 500)
		{
			pwsd->global_slope /= 2;    // Starting slope is unstable
		}

#if PG_DEBUG
		printw("\nStrokeSlope: %ld, Global: %d ", (100 * dx_sum) / dy_sum, pwsd->global_slope);
#endif
	}

	if (pwsd->stroke_dy > pwsd->line_h_bord / 4) // Escape influence of small dots
	{
		pwsd->global_dy_sum += pwsd->stroke_dy;
		pwsd->global_num_dy_strokes++;
		pwsd->global_line_ave_y_size = pwsd->global_dy_sum / pwsd->global_num_dy_strokes;
	}

	return 0;
err:
	return 1;
}

/* ************************************************************************* */
/*      Decide, if current stroke belongs to a new line                      */
/* ************************************************************************* */
_INT WS_NewLine(p_ws_data_type pws_data)
{
	_INT i, m, k, l, d, p;
	_INT horz;
	_INT high, low, min_line;
	_INT new_line = 0;
	register p_ws_data_type pwsd = pws_data;

	horz = HORZ_GET(pwsd->stroke_min_x);

	if (horz > 0 && CheckForSpaceGesture(pwsd))
	{
		// If started to left from the line end
		m = (pwsd->line_active_end - pwsd->line_inword_dist * 2) - pwsd->stroke_min_x;
		m /= 2;
		if (m < -pwsd->line_h_bord)
		{
			m = -pwsd->line_h_bord;
		}
		if (m > pwsd->line_h_bord)
		{
			m = pwsd->line_h_bord;
		}
		// If ended too left from line end
		k = (pwsd->line_active_end - pwsd->line_inword_dist) - pwsd->stroke_max_x;
		k /= 2;
		if (k < 0)
		{
			k = 0;
		}
		if (k > pwsd->line_h_bord)
		{
			k = pwsd->line_h_bord;
		}
		// If top is in current line
		l = (horz + pwsd->line_h_bord / 2) - pwsd->stroke_min_y;
		l *= 3;
		if (l < 0)
		{
			l = 0;
		}
		if (l > pwsd->line_h_bord)
		{
			l = pwsd->line_h_bord;
		}
		if (pwsd->stroke_dx > pwsd->stroke_dy)
		{
			l = 0;    // Appliable Only for 'j' type strokes
		}

		high = pwsd->line_h_bord * 3;
		low = pwsd->line_h_bord * 2;

		if (pwsd->stroke_dy < pwsd->line_h_bord) // Small strokes are unlikely to start new line
		{
			low += low / 3;
			if (pwsd->stroke_dx < pwsd->line_inline_dist)
			{
				low += low / 3;
			}
		}

		if (pwsd->stroke_dx < pwsd->line_inline_dist)
		{
			high += high / 3;
		}
		if (pwsd->stroke_dy < pwsd->line_h_bord)
		{
			high += high / 3;    // Small strokes are unlikely to start new line
		}

		if (pwsd->stroke_num_points < 100) // Crosses will not exceed that
		{
			for (i = 1, d = 0; i < pwsd->stroke_num_points; i++)
			{
				d += WSAbs(pwsd->stroke[i].x - pwsd->stroke[i - 1].x) + WSAbs(pwsd->stroke[i].y - pwsd->stroke[i - 1].y);
			}
			if (2 * d > 3 * (pwsd->stroke_dx + pwsd->stroke_dy))
			{
				p = 1;
			}
			else
			{
				p = 0;
				if (pwsd->stroke_dx < pwsd->line_h_bord * 2 && pwsd->stroke_dy < pwsd->line_h_bord * 2)
				{
					high += high / 3;
				}
			}
		}
		else
		{
			p = 1;
		}

		if (p) // Maybe it is long cursive word?
		{
			if (pwsd->stroke_dx > pwsd->line_h_bord * 3)
			{
				high -= high / 3;
			}
			if (pwsd->stroke_dx > pwsd->line_h_bord * 5)
			{
				high -= high / 3;
			}
		}

		high = high - m;
		low = low - m - k + l;

		min_line = pwsd->line_h_bord + pwsd->line_h_bord / 4;
		if (high < min_line)
		{
			high = min_line;
		}
		if (low < min_line)
		{
			low = min_line;
		}

		if (pwsd->in_line_dist > 0)
		{
			high = low = pwsd->in_line_dist;    // External in control
		}

		m = (pwsd->stroke_wy_pos + pwsd->stroke_max_y) / 2;
		l = pwsd->stroke_wy_pos;

		if (m < horz - high || // Upper line
		        l > horz + low)    // Lower line
		{
			new_line = 1;  // ---------------- New line stated !!! because of new line-------------------
		}
	}
	else
	{
		new_line = 1;  // ---------------- New line stated !!! because of undefined -------------------
	}

	return new_line;
}

/* ************************************************************************* */
/*      Decide, if current stroke belongs to a new line                      */
/* ************************************************************************* */
_INT CheckForSpaceGesture(p_ws_data_type pws_data)
{
	_INT sx, ex;
	register p_ws_data_type pwsd = pws_data;

	if ((pwsd->in_flags & WS_FL_SPGESTURE) == 0)
	{
		goto err;
	}
	if (pwsd->stroke_dx * 2 < pwsd->line_h_bord)
	{
		goto err;
	}
	if (pwsd->stroke_dx < pwsd->stroke_dy * 3)
	{
		goto err;
	}

	sx = pwsd->stroke[0].x;
	ex = pwsd->stroke[pwsd->stroke_num_points - 1].x;

	//  if ((WSAbs(sx-ex))*3 > pwsd->stroke_dx) goto err;
	if ((sx - pwsd->stroke_min_x) * 3 > pwsd->stroke_dx)
	{
		goto err;
	}
	if ((ex - pwsd->stroke_min_x) * 3 > pwsd->stroke_dx)
	{
		goto err;
	}

	pwsd->stroke_flags |= ST_FL_NL_GESTURE;

	return 0;
err:
	return 1;
}
/* ************************************************************************* */
/*      Write down stroke HORZ values                                        */
/* ************************************************************************* */
_INT WS_WriteStrokeHorzValues(p_ws_data_type pws_data)
{
	_INT i, k;
	_INT horz;
	_INT pos, spos;
	register p_ws_data_type pwsd = pws_data;

	horz = HORZ_GET(pwsd->stroke_min_x);
	spos = pwsd->stroke_wy_pos;

	if (pwsd->stroke_dy < pwsd->line_h_bord / 2)  // Do not honor small strokes
	{
		if (horz > 0)
		{
			spos = (3 * horz + spos) / 4;
		}
	}
	// Do not put 't' crossses etc. in the beg of word
	if (horz > 0 && pwsd->stroke_max_x < pwsd->line_end - pwsd->line_inword_dist)
	{
		goto err;
	}

	if (horz > 0)
	{
		if (pwsd->stroke_dy < pwsd->line_h_bord || pwsd->stroke_dx < pwsd->line_inline_dist)
		{
			pos = (3 * horz + spos) / 4;
		}
		else
		{
			pos = (horz + spos) / 2;
		}
	}
	else
	{
		pos = spos;
	}

	for (i = pwsd->stroke_max_x - 1; i >= 0; i -= HORZ_REDUCT)
	{
		horz = HORZ_GET(i);
		if (horz != 0 && i < pwsd->stroke_min_x)
		{
			break;
		}

		HORZ_PUT(i, pos);

		//    if (horz > 0) HORZ_PUT(i, (horz + str_y_pos)/2);
		//     else HORZ_PUT(i, str_y_pos);
	}

	k = pwsd->stroke_max_x + pwsd->line_h_bord * 6;
	if (k > TABLET_XS)
	{
		k = TABLET_XS;
	}
	for (i = pwsd->stroke_max_x; i < k; i += HORZ_REDUCT)
	{
		//    horz = HORZ_GET(i);

		HORZ_PUT(i, pos);

		//    if (horz > 0) HORZ_PUT(i, (horz + str_y_pos)/2);
		//     else HORZ_PUT(i, str_y_pos);
	}

	return 0;
err:
	return 1;
}

/* ************************************************************************* */
/*      Write down hist for the stroke                                       */
/* ************************************************************************* */
_INT WS_HistTheStroke(p_ws_data_type pws_data)
{
	_INT i;
	_INT k, l, m, n;
	_INT h, hh, hp, hv, dist, rasst;
	_INT x, dx, dy, xc, xp, yc, yp, xb, yb, mx, min_x, max_x;
	_INT filt_len, f_len, x_step;
	_INT up_lim, dn_lim;
	_INT up;
	_INT end = 0, suppr, sps;
	_INT num_points;
	_INT max_stroke_hist;
	_INT active_stroke_start, active_stroke_end;
	_INT base_line, slope;
	_INT hist_len, hist_base;
	p_ws_data_type pwsd = pws_data;
	PS_point_type _PTR stroke;


	stroke = pwsd->stroke;
	num_points = pwsd->stroke_num_points;
	slope = pwsd->global_slope;

	filt_len = pwsd->line_h_bord / FL_DIV;
	if (pwsd->line_extr <= MIN_LINE_EXTR)
	{
		filt_len /= 2;
	}

	if (filt_len < MIN_FL)
	{
		filt_len = MIN_FL;
	}
	if (filt_len > MAX_FL)
	{
		filt_len = MAX_FL;
	}

	base_line = HORZ_GET(pwsd->stroke_min_x);
	base_line = GET_AVE(base_line, pwsd->stroke_wy_pos);

	up_lim = base_line - pwsd->line_h_bord / 2;
	dn_lim = base_line + pwsd->line_h_bord / 2;

	xp = (_INT) (((_LONG) (pwsd->stroke_min_y - base_line) * (_LONG) slope) / 100);
	xc = (_INT) (((_LONG) (pwsd->stroke_max_y - base_line) * (_LONG) slope) / 100);

	min_x = pwsd->stroke_min_x + ((xp < xc) ? xp : xc);
	max_x = pwsd->stroke_max_x + ((xp > xc) ? xp : xc);

	if (min_x < 0)
	{
		min_x = 0;
	}
	if (min_x > TABLET_XS - 4)
	{
		min_x = TABLET_XS - 4;
	}
	if (max_x < 0)
	{
		max_x = 0;
	}
	if (max_x > TABLET_XS - 4)
	{
		max_x = TABLET_XS - 4;
	}


	hist_len = HIST_POS(max_x - min_x) + HIST_REDUCT * 2;
	hist_base = HIST_ADJPOS(min_x);
	pwsd->s_hist = (_UCHAR(_PTR)[HIST_SIZE])HWRMemoryAlloc(hist_len);
	pwsd->s_hist_base = hist_base;
	if (pwsd->s_hist == _NULL)
	{
		goto err;
	}
	HWRMemSet(&((*pwsd->s_hist)[0]), 0, hist_len);
	// Suppress initial fancies for long strokes  only
	if (max_x - min_x > pwsd->line_h_bord * 2)
	{
		suppr = 1;
	}
	else
	{
		suppr = 0;
	}

	if (suppr)
	{
		sps = 1;
	}
	else
	{
		sps = 0;
	}

	up = 1;
	m = 0;
	h = 1;
	hh = pwsd->line_h_bord / 4;
	n = hh / 2;
	x = mx = 0;
	max_x = 0;
	min_x = TABLET_XS;
	rasst = 0;
	for (i = 0; i < num_points; i++)
	{
		xc = stroke[i].x;
		yc = stroke[i].y;
		// Straighten
		xc += (_INT) (((_LONG) (yc - base_line) * (_LONG) slope) / 100l);

		if (xc < 0)
		{
			xc = 0;
		}
		if (xc > TABLET_XS - 4)
		{
			xc = TABLET_XS - 4;
		}

		if (min_x > xc)
		{
			min_x = xc;
		}
		if (max_x < xc)
		{
			max_x = xc;
		}

		if (i == 0)
		{
			xp = xb = xc;
			yp = yb = yc;
		}

		rasst += (WSAbs(xp - xc) + WSAbs(yp - yc));

		if (up)
		{
			if (yc < m - n)
			{
				up = 0;
			}
			else
			{
				if (yc > m)
				{
					m = yc;
				}
			}
		}
		else
		{
			if (yc > m + n)
			{
				up = 1;
				if (xc - x > hh)
				{
					HIST_ADD(PIK_VAL, &(*pwsd->s_hist)[HIST_POS(xc - hist_base)]);
					x = mx = xc;
				}
			}
			else
			{
				if (yc < m)
				{
					m = yc;
				}
			}
		}

		if (suppr && i > num_points - 5)
		{
			sps = 1;
		}

		f_len = (sps) ? filt_len * 2 : filt_len;

		if (rasst > f_len || i == num_points - 1)
		{
			if (i == num_points - 1)
			{
				end = 1;
			}

			dx = WSAbs(xb - xc);
			dy = WSAbs(yb - yc);

			hv = ((PIK_VAL * 3)*dy) / pwsd->line_h_bord; // Quater of line of vert stroke is enough for pik
			if (yc < up_lim || yc > dn_lim)
			{
				hv = 1;    // Do not hist up strech or dn strech
			}
			if (sps)
			{
				hv /= 4;
			}
			if (hv < 1)
			{
				hv = 1;
			}

			if (dx == 0)
			{
				if (!end)
				{
					h = hv*HIST_REDUCT;
				}
				HIST_ADD(h, &(*pwsd->s_hist)[HIST_POS(xc - hist_base)]);
			}
			else
			{
				if (!end)
				{
					h = 1 + (dy / dx);
				}
				if (h > hv)
				{
					h = hv;    // Fight begining 'fancies' in cursive
				}

				x_step = (xc > xb) ? 1 : -1;
				for (l = 0; l < dx; l++)
				{
					hp = xb + (l * x_step);
					hv = HIST_ADD(h, &(*pwsd->s_hist)[HIST_POS(hp - hist_base)]);
					if (hv >= PIK_VAL) // Pik found, set pos, destr prev close
					{
						if (mx > 0)
						{
							dist = WSAbs(mx - hp);
							if (dist < hh && dist > HIST_REDUCT)
							{
								HIST_SUB(PIK_VAL, &(*pwsd->s_hist)[HIST_POS(mx - hist_base)]);
								mx = 0;
							}
						}

						x = hp;
					}
				}
			}

			xb = xc;
			yb = yc;
			rasst = 0;
			sps = 0;
		}

		xp = xc;
		yp = yc;
	}

	// Get hist parameters for the stroke
	mx = max_x + HIST_REDUCT;
	active_stroke_start = active_stroke_end = 0;
	for (i = min_x, max_stroke_hist = 0; i <= mx; i += HIST_REDUCT)
	{
		k = (*pwsd->s_hist)[HIST_POS(i - hist_base)];
		if (k > max_stroke_hist)
		{
			max_stroke_hist = k;
		}

		if (k >= CUT_VAL)
		{
			if (active_stroke_start == 0)
			{
				active_stroke_start = i;
			}
			active_stroke_end = i;
		}
	}

	if (max_stroke_hist < CUT_VAL) // Small strokes save
	{
		k = HIST_ADJPOS((max_x + min_x) / 2);

		HIST_ADD(CUT_VAL, &(*pwsd->s_hist)[HIST_POS(k - hist_base)]);
		active_stroke_start = active_stroke_end = k;
		pwsd->stroke_flags |= ST_FL_JUNK;
	}

	if (active_stroke_start < min_x)
	{
		active_stroke_start = min_x;
	}
	if (active_stroke_end > max_x + 1)
	{
		active_stroke_end = max_x + 1;
	}

	pwsd->stroke_filt_len = filt_len;
	pwsd->stroke_active_st = HIST_ADJPOS(active_stroke_start);
	pwsd->stroke_active_end = HIST_ADJPOS(active_stroke_end) + HIST_REDUCT;

	pwsd->stroke_min_x = HIST_ADJPOS(min_x);
	pwsd->stroke_max_x = HIST_ADJPOS(max_x + HIST_REDUCT);
	pwsd->stroke_dx = pwsd->stroke_max_x - pwsd->stroke_min_x;

	pwsd->xstrokes[pwsd->line_cur_stroke].st = (_SHORT) (pwsd->stroke_min_x);
	pwsd->xstrokes[pwsd->line_cur_stroke].end = (_SHORT) (pwsd->stroke_max_x);
	pwsd->xstrokes[pwsd->line_cur_stroke].a_end = (_SHORT) (pwsd->stroke_active_end);


	return 0;
err:
	return 1;
}

/* ************************************************************************* */
/*      Add stroke hist to gen line hist                                     */
/* ************************************************************************* */
_INT WS_AddStrokeToHist(p_ws_data_type pws_data)
{
	_INT    i;
	_INT    b, v, hv, hb;
	p_UCHAR plh, psh;
	p_ws_data_type pwsd = pws_data;

	v = HORZ_GET(pwsd->stroke_active_st);
	if (pwsd->stroke_flags & ST_FL_JUNK) //Do not place dots and strokes upon word
	{
		if (pwsd->hist[HIST_POS(pwsd->stroke_active_st)] > 0) // Remove marker above covered already place
		{
			HIST_SUB(CUT_VAL, &(*pwsd->s_hist)[HIST_POS(pwsd->stroke_active_st - pwsd->s_hist_base)]);
		}
		if (pwsd->stroke_dx < pwsd->line_inword_dist / 2)
		{
			if (v > 0 && pwsd->stroke_min_y > v)
			{
				goto done;    // Do not put on hist periods and commas, only apostrofe
			}
		}
	}

	plh = &pwsd->hist[HIST_POS(pwsd->stroke_min_x)];
	psh = &(*pwsd->s_hist)[HIST_POS(pwsd->stroke_min_x - pwsd->s_hist_base)];
	for (i = pwsd->stroke_min_x; i < pwsd->stroke_max_x; i += HIST_REDUCT, plh++, psh++)
	{
		v = (*psh) / HIST_REDUCT;
		if (i >= pwsd->stroke_active_st && i < pwsd->stroke_active_end)
		{
			b = FL_BODY;
		}
		else
		{
			b = 0;
		}
		hv = (*plh) & HIST_FIELD;
		hb = (*plh) & FL_BODY;

		*plh = (_UCHAR) ((((hv + v) > HIST_FIELD) ? HIST_FIELD : (hv + v)) | (hb | b));
	}

done:
	if (pwsd->line_start > pwsd->stroke_min_x)
	{
		pwsd->line_start = pwsd->stroke_min_x;
	}
	if (pwsd->line_end < pwsd->stroke_max_x)
	{
		pwsd->line_end = pwsd->stroke_max_x;
	}
	if (pwsd->line_active_start > pwsd->stroke_active_st)
	{
		pwsd->line_active_start = pwsd->stroke_active_st;
	}
	if (pwsd->line_active_end < pwsd->stroke_active_end)
	{
		pwsd->line_active_end = pwsd->stroke_active_end;
	}

	return 0;
}

/* ************************************************************************* */
/*      Calculate line relative step and other vars                          */
/* ************************************************************************* */
_INT WS_CalcGaps(p_ws_data_type pws_data)
{
	_INT   j;
	_INT   cur_val, n_gap, sl, size;
	_INT   l, b, ls, bs;
	_INT   tail_preserve_level;
	_INT   state;
	p_ws_data_type pwsd = pws_data;

#define HIGH 1
#define LOW  0

	sl = GET_AVE(pwsd->global_sep_let_level, pwsd->line_sep_let_level);

	tail_preserve_level = 10 + sl;
	if (tail_preserve_level < 10)
	{
		tail_preserve_level = 10;
	}
	if (tail_preserve_level > 90)
	{
		tail_preserve_level = 90;
	}

	size = pwsd->global_cur_stroke - pwsd->line_st_stroke + 4;

	if (pwsd->gaps_handle)
	{
		if (pwsd->gaps)
		{
			HWRMemoryUnlockHandle(pwsd->gaps_handle);
		}
		HWRMemoryFreeHandle(pwsd->gaps_handle);
	}
	pwsd->gaps_handle = HWRMemoryAllocHandle(sizeof(ws_gaps_type)*size);
	if (pwsd->gaps_handle == _NULL)
	{
		goto err;
	}
	pwsd->gaps = (ws_gaps_a_type) HWRMemoryLockHandle(pwsd->gaps_handle);
	if (pwsd->gaps == _NULL)
	{
		goto err;
	}

	state = LOW;
	n_gap = 0;
	l = b = 0;
	ls = bs = pwsd->line_start;
	for (j = ls; j < pwsd->line_end + HIST_REDUCT; j += HIST_REDUCT)
	{
		cur_val = pwsd->hist[HIST_POS(j)];

		if (j >= pwsd->line_end)
		{
			cur_val |= FL_BODY;    // Write finishing gap
			state = LOW;
		}

		if (cur_val & FL_BODY)
		{
			if (state == HIGH)
			{
				ls = bs = j;
				continue;
			}

			(*pwsd->gaps)[n_gap].loc = (_SHORT) ((ls + j) / 2);
			(*pwsd->gaps)[n_gap].bst = (_SHORT) ((b == 0) ? ((ls + j) / 2) : bs);
			(*pwsd->gaps)[n_gap].lst = (_SHORT) ((l == 0) ? j : ls);
			(*pwsd->gaps)[n_gap].size =
			    (*pwsd->gaps)[n_gap].psize = (_SHORT) ((b + ((_LONG) (l - b)*(_LONG) (100 - tail_preserve_level)) / 100)*HIST_REDUCT);
			(*pwsd->gaps)[n_gap].blank = (_SHORT) (b*HIST_REDUCT);
			(*pwsd->gaps)[n_gap].low = (_SHORT) (l*HIST_REDUCT);
			(*pwsd->gaps)[n_gap].flags = (_SHORT) (0);
			(*pwsd->gaps)[n_gap].k_sure = (_SHORT) (WS_NNET_NSEGLEV);

			n_gap++;
			l = b = 0;
			state = HIGH;
		}
		else
		{
			if (state == HIGH)
			{
				state = LOW;
			}
			if ((cur_val & HIST_FIELD) == 0)
			{
				if (b == 0)
				{
					bs = j;    // Blank hist
				}
				b++;
			}
			if (l == 0)
			{
				ls = j;
			}
			l++;                                  // Low hist
		}
	}

	pwsd->line_ngaps = n_gap;

	return 0;
err:
	return 1;
}

/* ************************************************************************* */
/*      Calculate line relative step and other vars                          */
/* ************************************************************************* */
_INT WS_CountPiks(p_ws_data_type pws_data)
{
	_INT   j, k, m;
	_INT   low, count, cur_val, up, high;
	_INT   pik_step;
	_INT   sl;
	p_ws_data_type pwsd = pws_data;

	sl = GET_AVE(pwsd->global_sep_let_level, pwsd->line_sep_let_level);
	pik_step = pwsd->line_h_bord / PIK_STEP_CONST;
	pik_step = pik_step + (sl*pik_step) / 50; // Sep let has bigger step

	k = 1;
	low = 1;
	up = 1;
	high = 0;
	m = 0;
	count = 0;
	for (j = pwsd->line_active_start; j < pwsd->line_active_end; j += HIST_REDUCT)
	{
		cur_val = (pwsd->hist[HIST_POS(j)] & HIST_FIELD);

		if (m > 0)
		{
			m -= HIST_REDUCT;    // Skip after found extrem
			continue;
		}
		if (k != 0 && cur_val == 0)
		{
			continue;
		}

		k = 0;

		if (up && cur_val > high)
		{
			high = cur_val;
		}
		if (up && cur_val <= high - PIK_DN)
		{
			count++;
			up = 0;
			low = cur_val;
			m = pik_step;
			continue;
		}

		if (!up && cur_val < low)
		{
			low = cur_val;
		}
		if (!up && cur_val >= low + PIK_UP)
		{
			up = 1;
			high = cur_val;
			m = pik_step;
			continue;
		}
	}

	if (up)
	{
		count++;
	}

	// ------------------ Write values ---------------------------------------

	pwsd->line_extr = count;
	pwsd->line_pik_step = pik_step;

	return 0;
}

/* ************************************************************************* */
/*      Calculate line relative step and other vars                          */
/* ************************************************************************* */
_INT WS_SetLineVars(p_ws_data_type pws_data)
{
	_INT   i, m, n;
	_INT   s, size, deduct;
	_INT   bws_count, sws_count;
	_INT   h, hh, hhh, hhhh, small_sp_sum, betw_word_sp, blank_sp_sum;
	p_ws_data_type pwsd = pws_data;

	if (pwsd->line_extr > MIN_LINE_EXTR)
	{
		pwsd->line_inline_dist = (pwsd->line_end - pwsd->line_start) / pwsd->line_extr;
	}
	else
	{
		if (pwsd->global_inline_dist > 0)
		{
			pwsd->line_inword_dist = pwsd->line_inline_dist = pwsd->global_inline_dist;
		}
		else
		{
			pwsd->line_inword_dist = pwsd->line_inline_dist = pwsd->line_h_bord / 2;
		}
	}

	s = 30;
	m = GET_AVE(pwsd->global_inline_dist, pwsd->line_inline_dist);
	n = (pwsd->line_extr > MIN_LINE_EXTR) ? m : pwsd->line_h_bord;
	h = (_INT) (n + ((_LONG) n*(_LONG) s) / 100);

	hh = m * 3;
	hhh = m / 2;
	hhhh = m;

	sws_count = 0;
	bws_count = 0;

	small_sp_sum = 0;
	betw_word_sp = 0;
	blank_sp_sum = 0;

	deduct = 0;

	for (i = 1; i < pwsd->line_ngaps - 1; i++)
	{
		size = (*pwsd->gaps)[i].size;

		if (size > h) // Interword gap
		{
			if (size > hh)
			{
				deduct += size - hh;
			}
			betw_word_sp += (size > hh) ? hh : size;
			bws_count++;
		}
		else // Inword gap
		{
			n = (*pwsd->gaps)[i].blank;
			if (n > hhhh)
			{
				n = 0;
			}
			else
			{
				n = (n > hhh) ? hhh : n;
			}
			blank_sp_sum += n;
			small_sp_sum += (*pwsd->gaps)[i].low;
			sws_count++;
		}
	}

	// ------------------ Write values ---------------------------------------

	pwsd->line_word_len = (pwsd->line_end - pwsd->line_start) - betw_word_sp;
	if (pwsd->line_word_len < 1)
	{
		pwsd->line_word_len = 1;
	}

	if (bws_count > 0)
	{
		pwsd->line_bw_sp = betw_word_sp / bws_count;
	}
	else
	{
		pwsd->line_bw_sp = 0;
	}
	if (sws_count > 0)
	{
		pwsd->line_sw_sp = small_sp_sum / sws_count;
	}
	else
	{
		pwsd->line_sw_sp = 0;
	}

	if (pwsd->line_extr > MIN_LINE_EXTR)
	{
		pwsd->line_inword_dist = pwsd->line_word_len / pwsd->line_extr;
		pwsd->line_inline_dist = (pwsd->line_end - pwsd->line_start - deduct) / pwsd->line_extr;

		pwsd->line_sep_let_level = (_INT) ((200l * (_LONG) (small_sp_sum / 4 + blank_sp_sum)) / pwsd->line_word_len);
		if (pwsd->line_sep_let_level > 100)
		{
			pwsd->line_sep_let_level = 100;
		}
		if (pwsd->line_sep_let_level < 2)
		{
			pwsd->line_sep_let_level = 2;
		}
	}

	return 0;
}


/* ************************************************************************* */
/*      Estimate line height                                                 */
/* ************************************************************************* */
_INT WS_CalcLineHeight(p_ws_data_type pws_data)
{
	_INT   h, k;
	register p_ws_data_type pwsd = pws_data;

	//without last stroke!!!

	if (pwsd->global_num_extr + pwsd->line_extr > MIN_LINE_EXTR)
	{
		h = GET_AVE(pwsd->global_sep_let_level, pwsd->line_sep_let_level);
		h = h / 2;
		h = (_INT) (((_LONG) pwsd->global_line_ave_y_size * (_LONG) (40 + h)) / 100);
		k = pwsd->line_inline_dist;

		pwsd->line_h_bord = (pwsd->line_h_bord + h + k) / 3;
	}
	else
	{
		pwsd->line_h_bord = (pwsd->global_line_ave_y_size + pwsd->line_h_bord + pwsd->def_h_bord) / 3;
	}

	if (pwsd->line_h_bord < MIN_H_BORD)
	{
		pwsd->line_h_bord = MIN_H_BORD;
	}

	return 0;
}

/* ************************************************************************* */
/*      PostProcess line gaps                                                */
/* ************************************************************************* */
_INT WS_PostprocessGaps(p_ws_data_type pwsd)
{
	_INT i, l;
	_INT m, n;
	_INT size, start, end, h, hght, sub;
	_INT fws, fwe;
	_INT inline_dist, line_end_stroke, ngaps, dist;
	_INT ono;
	_INT ht;
	_UCHAR gps[MAX_STROKES];
	p_ws_gaps_type pgap, ppgap;


	inline_dist = pwsd->line_inline_dist;
	line_end_stroke = pwsd->line_cur_stroke;
	if (pwsd->line_finished == WS_NEWLINE)
	{
		line_end_stroke--;
	}

	// ----------- Presegment to groups ----------------------------

	ppgap = &((*pwsd->gaps)[0]);
	for (i = 0, ngaps = 0; i < pwsd->line_ngaps; i++, ppgap++)
	{
		ppgap->psize = ppgap->size;
		if (i == 0 || i == pwsd->line_ngaps - 1 || ppgap->size > inline_dist / 2)
		{
			gps[ngaps++] = (_UCHAR) i;
		}
	}

	// ----------- Searching for punctuation -----------------------

	dist = inline_dist * 2;

	for (i = 0; i < ngaps - 1; i++, pgap++, ppgap++)
	{
		ppgap = &((*pwsd->gaps)[gps[i]]);
		pgap = &((*pwsd->gaps)[gps[i + 1]]);

		size = pgap->lst - (ppgap->lst + ppgap->low);
		ono = (size < inline_dist / 2) ? 1 : 0;

		if (ono)
		{
			m = n = 32000;
			if (i > 0)
			{
				m = ppgap->blank;    // Left free space
			}
			if (i < ngaps - 2)
			{
				n = pgap->blank;    // Right free space
			}

			if (n < dist || m < dist)
			{
				if (m / 2 <= n)
				{
					pgap = ppgap;    // Merge to prev, else to next
				}
			}
		}

		if (ono)
		{
			pgap->psize = (_SHORT) (pgap->blank / 2);
			pgap->flags |= (_SHORT) (WS_GP_EPUNCT);
		}
	}


	// ----------- Searching for leading Capitals --------------------------

	ht = pwsd->line_h_bord - pwsd->line_h_bord / 4;

	for (i = 1; 0 && i < ngaps; i++, pgap++, ppgap++)
	{
		ppgap = &((*pwsd->gaps)[gps[i - 1]]);
		pgap = &((*pwsd->gaps)[gps[i]]);

		size = pgap->lst - (ppgap->lst + ppgap->low);
		sub = 0;

		ono = (size < inline_dist * 2) ? 1 : 0;

		if (i > 1 && ppgap->blank < inline_dist)
		{
			ono = 0;
		}
		if (pgap->blank > inline_dist * 2)
		{
			ono = 0;
		}

		if (ono)
		{
			fws = pgap->bst - inline_dist / 4;
			fwe = pgap->bst;
			h = HORZ_GET((fws + fwe) / 2);
			hght = 0;

			for (l = 0; l <= line_end_stroke; l++)  // If small enough -- let's compare height
			{
				start = pwsd->xstrokes[l].st;
				end = pwsd->xstrokes[l].end;

				if ((start >= fws && start <= fwe) ||
				        (end >= fws && end <= fwe) ||
				        (start <= fws && end >= fwe))
				{
					if (hght < h - pwsd->xstrokes[l].top)
					{
						hght = h - pwsd->xstrokes[l].top;
					}
				}
			}

			if (hght < ht)
			{
				ono = 0;
			}
			else
			{
				sub = (hght - ht);
			}
		}

		if (ono)
		{
			if (i == 1)
			{
				pgap->psize = (_SHORT) (pgap->size - sub);
			}
			else
			{
				pgap->psize = (_SHORT) (pgap->size - sub / 2);
			}
			if (pgap->psize < 0)
			{
				pgap->psize = 0;
			}
			pgap->flags |= (_SHORT) (WS_GP_LCAP);
		}
	}

	return 0;
}

/* ************************************************************************* */
/*      Estimate word segmentation distance                                  */
/* ************************************************************************* */
#if WS_USE_DIST_F
_INT WS_SegmentWords(_INT finished_x, p_ws_data_type pws_data)
{
	_INT i,s;
	_INT num_words, start, word_dist, max_wl, l;
	_INT delt;
	_INT a,b,c,d,e,f,g;
	_INT sure;
	register p_ws_data_type pwsd = pws_data;

#define WD_DIST1 word_dist/10
#define WD_DIST2 word_dist/2

	a = pwsd->nn_ssp;      // a
	b = pwsd->nn_n_ssp;    // b
	c = pwsd->nn_bsp;      // c
	d = pwsd->nn_n_bsp;    // d
	e = pwsd->nn_sl;       // e
	f = pwsd->nn_inw_dist; // f
	g = pwsd->nn_npiks;    // g

	word_dist = pwsd->line_word_dist;
	//  sure      = ((3-c+2*f-b*g)*(83-c) >= 100) ? 1: 0;
	sure      = ((b + (((f+35) > (c-e)) ? (4*b+((b > (c/5)) ? 4000 : (c/5+5*b))) : (f+35))) >= 100) ? 1: 0;
	delt      = (sure) ? WD_DIST1 : WD_DIST2;

	for (s = 0; s <= 6; s ++)
	{
		word_dist -= pwsd->line_word_dist*s/8;
		num_words  = 0;
		start      = 0;
		max_wl     = 0;
		for (i = 1; i < pwsd->line_ngaps; i ++)
		{
			if ((*pwsd->gaps)[i].lst <= finished_x)
			{
				continue;    // Take only new (not finished) gaps
			}
			if ((*pwsd->gaps)[i].psize > word_dist+delt|| i == pwsd->line_ngaps-1)
			{
				pwsd->xwords[num_words].st_gap = (_UCHAR)(start);
				pwsd->xwords[num_words].en_gap = (_UCHAR)(i);
				start = i;
				num_words ++;
			}
			else
			{
				if ((*pwsd->gaps)[i].psize > word_dist-delt)
				{
					_INT ks = (word_dist > 0) ? (100/2)*(*pwsd->gaps)[i].psize/word_dist : 100;
					if (ks > 100)
					{
						ks = 100;
					}
					(*pwsd->gaps)[i].k_sure = (_SCHAR)ks;
				}
				else
				{
					(*pwsd->gaps)[i].k_sure = (_SCHAR)(-100);
				}
			}

			if (max_wl < (l=(*pwsd->gaps)[i].loc - (*pwsd->gaps)[start].loc))
			{
				max_wl = l;
			}
		}

		if (pwsd->line_inline_dist > 0) if (max_wl/pwsd->line_inline_dist < w_lim)
			{
				break;
			}
	}

	return num_words;
}

#endif

#if WS_USE_NNET

int NeuroNetWS(int * pParams);

_INT WS_SegmentWords(_INT finished_x, p_ws_data_type pws_data)
{
	_INT i;
	_INT num_words, start, nn, na;
	int params[16];
	register p_ws_data_type pwsd = pws_data;


	num_words = 0;
	start = 0;
	for (i = 1; i < pwsd->line_ngaps; i++)
	{
		if ((*pwsd->gaps)[i].lst <= finished_x)
		{
			continue;    // Take only new (not finished) gaps
		}

		nn = 1;

		if (i == pwsd->line_ngaps - 1)
		{
			nn = 0;    // Cut on last gap
		}
		else
		{
			params[0] = pwsd->nn_ssp;
			params[1] = pwsd->nn_n_ssp;
			params[2] = pwsd->nn_bsp;
			params[3] = pwsd->nn_n_bsp;
			params[4] = pwsd->nn_sl;
			params[5] = pwsd->nn_inw_dist;
			params[6] = pwsd->nn_npiks;

			if (params[3] == 0)
				if (params[5] > 80)
				{
					params[5] = 80;
				}

			params[7] = i;
			params[8] = 100 * (*pwsd->gaps)[i].psize / (pwsd->ws_inline_dist);
			params[9] = 100 * (*pwsd->gaps)[i].blank / (pwsd->ws_inline_dist);
			params[10] = 100 * (*pwsd->gaps)[i].low / (pwsd->ws_inline_dist);

			if (pwsd->in_word_dist)
			{
				params[8] = params[8] + ((6 - pwsd->in_word_dist)*(params[8])) / 4;
				params[9] = params[9] + ((6 - pwsd->in_word_dist)*(params[9])) / 4;
				params[10] = params[10] + ((6 - pwsd->in_word_dist)*(params[10])) / 4;
			}

			na = NeuroNetWS(params);
		}

		if (na > 0 && WSAbs(na) > pwsd->sure_level)
		{
			nn = 0;
		}

		if (nn == 0)
		{
			pwsd->xwords[num_words].st_gap = (_UCHAR) (start);
			pwsd->xwords[num_words].en_gap = (_UCHAR) (i);
			start = i;
			num_words++;
		}
		else
		{
			(*pwsd->gaps)[i].k_sure = (_SCHAR) na;
		}
	}

	return num_words;
}

#endif

#if WS_USE_DECN_F

#define FORMULA(a,b,c,d,e,f,g)                                                \
(b-(73.2+(89.7-((d+(b-(89.7-(b-f))))-(73.2+(89.7-(79.6-a)))))))


_INT WS_SegmentWords(_INT finished_x, p_ws_data_type pws_data)
{
	_INT i;
	_INT num_words, start, nn, na;
	float  a,b,c,d,e,f,g;
	register p_ws_data_type pwsd = pws_data;


	num_words = 0;
	start     = 0;
	for (i = 1; i < pwsd->line_ngaps; i ++)
	{
		if ((*pwsd->gaps)[i].lst <= finished_x)
		{
			continue;    // Take only new (not finished) gaps
		}

		nn = 1;

		if (i == pwsd->line_ngaps-1)
		{
			nn = 0;    // Cut on last gap
		}
		else
		{
			a = pwsd->nn_ssp;
			b = 100*(*pwsd->gaps)[i].psize / (pwsd->ws_inline_dist);
			c = pwsd->nn_bsp;
			d = 100*(*pwsd->gaps)[i].blank / (pwsd->ws_inline_dist);
			e = pwsd->nn_sl;
			f = pwsd->nn_inw_dist;
			g = 100*(*pwsd->gaps)[i].low / (pwsd->ws_inline_dist);

			if (pwsd->in_word_dist)
			{
				b = b + ((6-pwsd->in_word_dist)*(b))/4;
				d = d + ((6-pwsd->in_word_dist)*(d))/4;
				g = g + ((6-pwsd->in_word_dist)*(g))/4;
			}

			na = FORMULA(a,b,c,d,e,f,g);
		}

		if (na < -100)
		{
			na = -100;
		}
		if (na >  100)
		{
			na =  100;
		}
		if (na > 0 && WSAbs(na) > pwsd->sure_level)
		{
			nn = 0;
		}

		if (nn == 0) // Delim zdes!
		{
			pwsd->xwords[num_words].st_gap = (_UCHAR)(start);
			pwsd->xwords[num_words].en_gap = (_UCHAR)(i);
			start = i;
			num_words ++;
		}
		else
		{
			(*pwsd->gaps)[i].k_sure = (_SCHAR)na;
		}
	}

	return num_words;
}
#endif
/* ************************************************************************* */
/*      Estimate word segmentation distance                                  */
/* ************************************************************************* */
_INT WS_GetWordDist(p_ws_data_type pws_data)
{
	_INT  i;
	_INT  ss, ns, sl, nl, size, sdist, bdist;
	_INT  inline_dist, sw_space, bw_space, word_dist, inword_dist, sep_let_level;
	register p_ws_data_type pwsd = pws_data;
	_INT  action;

	_UCHAR  ws_handle_limits[2][11] =  //0   1    2    3    4    5   6    7    8    9   10
	{
		{ 0, 40, 60, 80, 100, 120, 0, 150, 180, 200, 240 },
		{ 0, 50, 70, 90, 110, 130, 0, 160, 190, 210, 255 }
	};

#define GWD_SWD   40 //37  //41  //39  // 37   /* 32  Act1/Act2 IF small word dist coeff */
#define GWD_IWD   88 //80  //79  //79  // 80   /* 76  Act1/Act2 IF inword dist coeff */
#define GWD_BWD   91 //91  //83  //83  // 91   /* 86  Act1/Act2 IF inline dist coeff */
#define GWD_ACT1  48 //49  //49  //47  // 49   /* 42  Act1 Calc coeff   */
#define GWD_ACT2  245//150 //228 //195 //140   /* 136 Act2 Calc coeff  */
#define GWD_ACT21 90 //95  //96  //100 //140


	//and spaces betw words count with tails!
	//and 4 instead of 2

	inword_dist = GET_AVE(pwsd->global_inword_dist, pwsd->line_inword_dist);
	inline_dist = GET_AVE(pwsd->global_inline_dist, pwsd->line_inline_dist);
	sep_let_level = pwsd->line_sep_let_level;

	if (inline_dist <= 0)
	{
		inline_dist = 1;
	}

	bdist = inline_dist + (_INT) (((_LONG) inline_dist*(_LONG) (sep_let_level / 2)) / 100);
	sdist = inline_dist;// - inline_dist/16;

	ss = ns = sl = nl = 0;
	for (i = 1; i < pwsd->line_ngaps - 1; i++)
	{
		size = (*pwsd->gaps)[i].size;
		if (size < sdist)
		{
			ss += (size < inline_dist / 8) ? inline_dist / 8 : size;
			ns++;
		}
		if (size > bdist)
		{
			sl += (size > inline_dist * 3) ? inline_dist * 3 : size;
			nl++;
		}
	}

	sw_space = (ns > 0) ? (ss / ns) : 0;
	bw_space = (nl > 0) ? (sl / nl) : 0;

	pwsd->nn_ssp = (_INT) ((100 * (_LONG) sw_space) / inline_dist);     // a
	pwsd->nn_n_ssp = ns;                                            // b
	pwsd->nn_bsp = (_INT) ((100 * (_LONG) bw_space) / inline_dist);     // c
	pwsd->nn_n_bsp = nl;                                            // d
	pwsd->nn_sl = sep_let_level;                                 // e
	pwsd->nn_inw_dist = (_INT) ((100 * (_LONG) inword_dist) / inline_dist);  // f
	pwsd->nn_npiks = pwsd->line_extr;                               // g

	if (sw_space < (_INT) ((GWD_SWD*(_LONG) bw_space) / 100) &&
	        inword_dist < (_INT) ((GWD_IWD*(_LONG) bw_space) / 100) &&
	        inline_dist < (_INT) ((GWD_BWD*(_LONG) bw_space) / 100))
	{
		word_dist = sw_space + (_INT) ((GWD_ACT1*(_LONG) (bw_space - sw_space)) / 100);
		action = 1;
	}
	else
	{
		word_dist = (GWD_ACT21*inline_dist) / 100 + (_INT) ((GWD_ACT2*((_LONG) inline_dist*(_LONG) sep_let_level) / 100) / 100);
		action = 2;
	}

	word_dist = ((10 > pwsd->nn_npiks) ? 110 : 79) +
	            ((pwsd->nn_sl*pwsd->nn_n_ssp + pwsd->nn_npiks > 100) ? pwsd->nn_ssp : (pwsd->nn_sl - pwsd->nn_n_ssp + pwsd->nn_npiks));

	word_dist = inline_dist*word_dist / 100;

	if (word_dist < inline_dist - inline_dist / 4)
	{
		word_dist = inline_dist - inline_dist / 4;
	}
	if (word_dist > inline_dist * 3)
	{
		word_dist = inline_dist * 3;
	}

	if (pwsd->global_num_extr + pwsd->line_extr < MIN_LINE_EXTR * 2) // Too little data, stay with default
	{
		if (word_dist < pwsd->def_h_bord + (pwsd->def_h_bord >> 2))
		{
			word_dist = pwsd->def_h_bord + (pwsd->def_h_bord >> 2);
			action = 0;
		}
	}

	sdist = bdist = 0;
	if (ws_handle_limits[0][pwsd->in_word_dist] > 0)
	{
		sdist = (inline_dist*ws_handle_limits[0][pwsd->in_word_dist]) / 100;
	}
	if (ws_handle_limits[1][pwsd->in_word_dist] > 0)
	{
		bdist = (inline_dist*ws_handle_limits[1][pwsd->in_word_dist]) / 100;
	}

	if (sdist && word_dist < sdist)
	{
		word_dist = sdist;
	}
	if (bdist && word_dist > bdist)
	{
		word_dist = bdist;
	}


	pwsd->line_word_dist = word_dist;

	pwsd->ws_ssp = sw_space;
	pwsd->ws_bsp = bw_space;
	pwsd->ws_inline_dist = inline_dist;
	pwsd->ws_word_dist = word_dist;
	pwsd->ws_action = action;

	// debug

#if HWR_SYSTEM != MACINTOSH
	WS_100
#endif

	return word_dist;
}


/* ************************************************************************* */
/*    Fly learn some ws parameters                                           */
/* ************************************************************************* */
_INT WS_FlyLearn(p_ws_control_type pwsc, p_ws_memory_header_type pwmh, p_ws_data_type pwsd)
{
	_INT                  i;
	_INT                  loc;
	_ULONG                sh, sw, sl, sp, ss;
	p_ws_lrn_type         lrn;

	if (pwmh == _NULL)
	{
		goto err;
	}
	if (pwsc == _NULL)
	{
		goto err;
	}
	if (pwsd == _NULL)
	{
		goto err;
	}

	for (loc = 0; loc < WS_LRN_SIZE; loc++) if (pwmh->lrn_buf[loc].h_bord_history == 0)
		{
			break;
		}

	if (loc >= WS_LRN_SIZE) // The box is full
	{
		loc = WS_LRN_SIZE - 1;
		HWRMemCpy(&(pwmh->lrn_buf[0]), &(pwmh->lrn_buf[1]), sizeof(pwmh->lrn_buf[0])*(WS_LRN_SIZE - 1));
	}

	lrn = &(pwmh->lrn_buf[loc]);
	lrn->h_bord_history = (_SHORT) (pwsd->line_h_bord);
	lrn->inword_dist_history = (_SHORT) (GET_AVE(pwsd->global_inword_dist, pwsd->line_inword_dist));
	lrn->inline_dist_history = (_SHORT) (GET_AVE(pwsd->global_inline_dist, pwsd->line_inline_dist));
	lrn->slope_history = (_SHORT) (pwsd->global_slope);
	lrn->sep_let_history = (_UCHAR) (GET_AVE(pwsd->global_sep_let_level, pwsd->line_sep_let_level));

	if (loc >= WS_LRN_SIZE - 1) // The box is full
	{
		lrn = &(pwmh->lrn_buf[0]);
		for (i = 0, sh = sw = sl = sp = ss = 0l; i < WS_LRN_SIZE; i++, lrn++)
		{
			sh += (_ULONG) (lrn->h_bord_history);
			sw += (_ULONG) (lrn->inword_dist_history);
			sl += (_ULONG) (lrn->inline_dist_history);
			sp += (_ULONG) (lrn->slope_history);
			ss += (_ULONG) (lrn->sep_let_history);
		}

		lrn = &(pwmh->lrn);
		lrn->h_bord_history = (_SHORT) ((sh) / (WS_LRN_SIZE));
		lrn->inword_dist_history = (_SHORT) ((sw) / (WS_LRN_SIZE));
		lrn->inline_dist_history = (_SHORT) ((sl) / (WS_LRN_SIZE));
		lrn->slope_history = (_SHORT) ((sp) / (WS_LRN_SIZE));
		lrn->sep_let_history = (_UCHAR) ((ss) / (WS_LRN_SIZE));
	}
	else
	{
		goto err;
	}

	return 0;
err:
	return 1;
}
