/* (c) 2001 by Daniel Vollmer (maven@maven.de)
   no warranty whatsover, gpl v2
	 thx to avery lee for the nice plugin-sdk (and vdub ;))
*/

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <math.h>

#include "ScriptInterpreter.h"
#include "ScriptError.h"
#include "ScriptValue.h"

#include "resource.h"
#include "filter.h"

///////////////////////////////////////////////////////////////////////////

int animeInitProc(FilterActivation *fa, const FilterFunctions *ff);
long animeParamProc(FilterActivation *fa, const FilterFunctions *ff);
void animeStringProc(const FilterActivation *fa, const FilterFunctions *ff, char *str);
void animeScriptConfig(IScriptInterpreter *isi, void *lpVoid, CScriptValue *argv, int argc);
bool animeFssProc(FilterActivation *fa, const FilterFunctions *ff, char *buf, int buflen);
int animeRunProc(const FilterActivation *fa, const FilterFunctions *ff);
int animeStartProc(FilterActivation *fa, const FilterFunctions *ff);
int animeEndProc(FilterActivation *fa, const FilterFunctions *ff);
int animeConfigProc(FilterActivation *fa, const FilterFunctions *ff, HWND hwnd);

///////////////////////////////////////////////////////////////////////////

#define col_eq(r1, g1, b1, r2, g2, b2, cmp) ((abs((r1) - (r2)) <= (cmp)) && (abs((g1) - (g2)) <= (cmp)) && (abs((b1) - (b2)) <= (cmp)))
#define overlap(xs1, xe1, xs2, xe2) (min((xe1), (xe2)) - max((xs1), (xs2)))

typedef struct MySpanData
{
	int			x1;
	int			len; // len = 0 -> terminator
	Pixel32 *dst;
	int			avg_r, avg_g, avg_b;
	unsigned char cmp_r, cmp_g, cmp_b, done;
} MySpanData;

typedef struct MyFilterData
{
	int					min_span_width;
	int					min_span_overlap;
	int					min_area_pixels;
	int					max_pixel_color_difference; // across 2 pixels
	int					max_span_color_difference; // across 2 spans
	int					noise_cutoff;
	bool				show_areas, filter_noise;
	// private
	IFilterPreview *ifp;
	int					max_spans; // for one row
	int					used_spans; // altogether
	int					num_spans, num_pixels; // for current area
	int					avg_r, avg_g, avg_b;
	unsigned char cmp_r, cmp_g, cmp_b;
	MySpanData	*spans;
	MySpanData	**area_spans;
} MyFilterData;

ScriptFunctionDef anime_func_defs[] =
{
	{ (ScriptFunctionPtr)animeScriptConfig, "Config", "0iiiiiii" },
  { NULL },
};

CScriptObject anime_obj={
    NULL, anime_func_defs
};

struct FilterDefinition filterDef_anime =
{
  NULL, NULL, NULL,       // next, prev, module
  "Area Smoother (0.1)",  // name
  "Replaces (more or less) uniformly colored areas by their average.",
                          // desc
  "Daniel Vollmer",       // maker
  NULL,                   // private_data
  sizeof (MyFilterData),  // inst_data_size
  animeInitProc,          // initProc
  NULL,                   // deinitProc
  animeRunProc,						// runProc
  animeParamProc,					// paramProc
  animeConfigProc,				// configProc
  animeStringProc,				// stringProc
  animeStartProc,					// startProc
  animeEndProc,						// endProc

  &anime_obj,							// script_obj
  animeFssProc,						// fssProc
};

///////////////////////////////////////////////////////////////////////////

extern "C" int __declspec(dllexport) __cdecl VirtualdubFilterModuleInit2(FilterModule *fm, const FilterFunctions *ff, int& vdfd_ver, int& vdfd_compat);
extern "C" void __declspec(dllexport) __cdecl VirtualdubFilterModuleDeinit(FilterModule *fm, const FilterFunctions *ff);

static FilterDefinition *fd_anime;

///////////////////////////////////////////////////////////////////////////

int __declspec(dllexport) __cdecl VirtualdubFilterModuleInit2(FilterModule *fm, const FilterFunctions *ff, int& vdfd_ver, int& vdfd_compat)
{
  if (!(fd_anime = ff->addFilter(fm, &filterDef_anime, sizeof(FilterDefinition))))
    return 1;

	vdfd_ver = VIRTUALDUB_FILTERDEF_VERSION;
	vdfd_compat = VIRTUALDUB_FILTERDEF_COMPATIBLE;

  return 0;
}

void __declspec(dllexport) __cdecl VirtualdubFilterModuleDeinit(FilterModule *fm, const FilterFunctions *ff)
{
  ff->removeFilter(fd_anime);
}

///////////////////////////////////////////////////////////////////////////

static bool g_MMXenabled;

int animeInitProc(FilterActivation *fa, const FilterFunctions *ff)
{
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;
	
	mfd->min_span_width = 8;
	mfd->min_span_overlap = 5;
	mfd->min_area_pixels = 64;
	mfd->max_pixel_color_difference = 13;
	mfd->max_span_color_difference = 9;
	mfd->noise_cutoff = 3248; // * 1024
	mfd->show_areas = false;
	mfd->filter_noise = true;

  return 0;
}

long animeParamProc(FilterActivation *fa, const FilterFunctions *ff)
{
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;

	// negotiate fa->dst.w, fa->dst.h & fa->dst.pitch
	fa->dst.offset	= fa->src.offset;
	fa->dst.modulo	= fa->src.modulo;
	fa->dst.pitch	= fa->src.pitch;// fa->dst.pitch = (fa->dst.w*4 + 7) & -8;

  return 0;
}

void animeStringProc(const FilterActivation *fa, const FilterFunctions *ff, char *str)
{
	MyFilterData *mfd = (MyFilterData *)fa->filter_data;

	sprintf(str, " (width: %d overlap: %d size: %d pixel diff: %d span diff: %d filter: %.3f, areas: %d)",
		mfd->min_span_width, mfd->min_span_overlap, mfd->min_area_pixels, mfd->max_pixel_color_difference,
		mfd->max_span_color_difference, mfd->filter_noise ? (double)mfd->noise_cutoff / 1024.0 : 0.0, mfd->show_areas);
}

void animeScriptConfig(IScriptInterpreter *isi, void *lpVoid, CScriptValue *argv, int argc)
{
  FilterActivation *fa = (FilterActivation *)lpVoid;
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;

	mfd->min_span_width = argv[0].asInt();
	mfd->min_span_overlap = argv[1].asInt();
	mfd->min_area_pixels = argv[2].asInt();
	mfd->max_pixel_color_difference = argv[3].asInt();
	mfd->max_span_color_difference = argv[4].asInt();
	mfd->noise_cutoff = argv[5].asInt();
	mfd->filter_noise = mfd->noise_cutoff > 0;
	mfd->show_areas = !!argv[6].asInt();	
}

bool animeFssProc(FilterActivation *fa, const FilterFunctions *ff, char *buf, int buflen)
{
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;

  _snprintf(buf, buflen, "Config(%d, %d, %d, %d, %d, %d, %d)", mfd->min_span_width, mfd->min_span_overlap,
		mfd->min_area_pixels, mfd->max_pixel_color_difference, mfd->max_span_color_difference, 
		mfd->filter_noise ? mfd->noise_cutoff : 0, mfd->show_areas);

  return true;
}

int animeStartProc(FilterActivation *fa, const FilterFunctions *ff)
{
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;

  g_MMXenabled = ff->isMMXEnabled();

	mfd->max_spans = fa->src.w / mfd->min_span_width + 1;

	// init tables
	if (!(mfd->spans = new MySpanData[fa->src.h * mfd->max_spans]))
		return 1;

	if (!(mfd->area_spans = new MySpanData*[fa->src.h * mfd->max_spans]))
	{
		delete[] mfd->spans;
		return 1;
	}

  return 0;
}

void trace_span_up(MySpanData *span, int y, MyFilterData *mfd)
{
	MySpanData *next_span;

	for (next_span = &mfd->spans[y * mfd->max_spans]; next_span->len > 0; next_span++)
	{
		if (!next_span->done && overlap(span->x1, span->x1 + span->len, next_span->x1, next_span->x1 + next_span->len) >= mfd->min_span_overlap
			&& col_eq(mfd->cmp_r, mfd->cmp_g, mfd->cmp_b, next_span->cmp_r, next_span->cmp_g, next_span->cmp_b, mfd->max_span_color_difference))
		{
			next_span->done = true;
			mfd->num_pixels += next_span->len;
			mfd->avg_r += next_span->avg_r;	mfd->avg_g += next_span->avg_g;	mfd->avg_b += next_span->avg_b;
			mfd->cmp_r = mfd->avg_r / mfd->num_pixels; mfd->cmp_g = mfd->avg_g / mfd->num_pixels; mfd->cmp_b = mfd->avg_b / mfd->num_pixels;
			mfd->area_spans[mfd->num_spans++] = next_span;
			if (y > 0)
				trace_span_up(next_span, y - 1, mfd);
		}
	}
}

void trace_span_down(MySpanData *span, int y, int max_y, MyFilterData *mfd)
{
	MySpanData *next_span;

	for (next_span = &mfd->spans[y * mfd->max_spans]; next_span->len > 0; next_span++)
	{
		if (!next_span->done && overlap(span->x1, span->x1 + span->len, next_span->x1, next_span->x1 + next_span->len) >= mfd->min_span_overlap
			&& col_eq(mfd->cmp_r, mfd->cmp_g, mfd->cmp_b, next_span->cmp_r, next_span->cmp_g, next_span->cmp_b, mfd->max_span_color_difference))
		{
			next_span->done = true;
			mfd->num_pixels += next_span->len;
			mfd->avg_r += next_span->avg_r;	mfd->avg_g += next_span->avg_g;	mfd->avg_b += next_span->avg_b;
			mfd->cmp_r = mfd->avg_r / mfd->num_pixels; mfd->cmp_g = mfd->avg_g / mfd->num_pixels; mfd->cmp_b = mfd->avg_b / mfd->num_pixels;
			mfd->area_spans[mfd->num_spans++] = next_span;
			if (y > 0)
				trace_span_up(next_span, y - 1, mfd);
			if (y + 1 < max_y)
				trace_span_down(next_span, y + 1, max_y, mfd);
		}
	}
}

int animeRunProc(const FilterActivation *fa, const FilterFunctions *ff)
{
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;
	int i, j, k;
  Pixel32 *src;
	MySpanData *span;

  mfd->used_spans = 0;
	for (i = 0; i < fa->src.h; i++)
	{
		src = (Pixel32 *)((char *)fa->src.data + i * fa->src.pitch);

		span = &mfd->spans[i * mfd->max_spans];
		span->x1 = 0;
		span->len = 1;
		span->dst = src;
		span->cmp_r = span->avg_r = ((*src) & 0xff0000) >> 16; span->cmp_g = span->avg_g = ((*src) & 0x00ff00) >> 8; span->cmp_b = span->avg_b = ((*src) & 0x0000ff);
		span->done = false;
		src++;
		for (j = 1; j < fa->src.w; j++)
		{
			int r, g, b;

			r = (*src & 0xff0000) >> 16;
			g = (*src & 0x00ff00) >> 8;
			b = (*src & 0x0000ff);

			if (col_eq(r, g, b, span->cmp_r, span->cmp_g, span->cmp_b, mfd->max_pixel_color_difference))
			{
				span->len++;
				span->avg_r += r; span->avg_g += g; span->avg_b += b;
				span->cmp_r = span->avg_r / span->len; span->cmp_g = span->avg_g / span->len;	span->cmp_b = span->avg_b / span->len;
			}
			else
			{ // start new span
				if (span->len >= mfd->min_span_width)
				{
					span++; // keep old span
					mfd->used_spans++;
				}
				span->x1 = j;
				span->len = 1;
				span->dst = src;
				span->cmp_r = span->avg_r = r; span->cmp_g = span->avg_g = g;	span->cmp_b = span->avg_b = b;
				span->done = false;
			}
			src++;
		}
		if (span->len >= mfd->min_span_width)
		{
			span++;
			mfd->used_spans++;
		}
		span->len = 0; // mark end
	}

  // now collect spans to form areas
	for (i = 0; i < fa->src.h; i++)
	{
		for (span = &mfd->spans[i * mfd->max_spans]; span->len > 0; span++)
			if (!span->done)
			{
				span->done = true;
				mfd->num_pixels = span->len;
				mfd->avg_r = span->avg_r; mfd->avg_g = span->avg_g;	mfd->avg_b = span->avg_b;
				mfd->cmp_r = span->cmp_r; mfd->cmp_g = span->cmp_g; mfd->cmp_b = span->cmp_b;
				mfd->num_spans = 1;
				mfd->area_spans[0] = span;
				if (i + 1 < fa->src.h)
					trace_span_down(span, i + 1, fa->src.h, mfd);

				if (mfd->num_pixels >= mfd->min_area_pixels)
				{ // count the noise of the "candidate" area (compare to single color)
					int noise = 0;
					MySpanData *sp;

					if (mfd->filter_noise)
					{
						for (j = 0; j < mfd->num_spans; j++)
						{
							sp = mfd->area_spans[j];
							for (src = sp->dst, k = 0; k < sp->len; k++, src++)
								noise += abs(mfd->cmp_r - ((*src & 0xff0000) >> 16)) + abs(mfd->cmp_g - ((*src & 0x00ff00) >> 8)) + abs(mfd->cmp_b - (*src & 0x0000ff));
						}
						noise = (int)((__int64)(noise * 1024) / (__int64)(mfd->num_pixels * 3));
					}
		
					if (noise <= mfd->noise_cutoff)
					{ // and fill it
						Pixel32 p = mfd->show_areas ? (mfd->avg_r << 16) + (mfd->avg_g << 8) + mfd->avg_b : (mfd->cmp_r << 16) + (mfd->cmp_g << 8) + mfd->cmp_b;

						for (j = 0; j < mfd->num_spans; j++)
						{
							sp = mfd->area_spans[j];
							for (src = sp->dst, k = 0; k < sp->len; k++)
								*src++ = p;
						}
					}
				}
				mfd->used_spans -= mfd->num_spans; // update span count
			}
	}
	
	return 0;
}


int animeEndProc(FilterActivation *fa, const FilterFunctions *ff)
{
  MyFilterData *mfd = (MyFilterData *)fa->filter_data;

	// free tables
	if (mfd->spans)
	{
		delete[] mfd->spans;
		mfd->spans = NULL;
	}

	if (mfd->area_spans)
	{
		delete[] mfd->area_spans;
		mfd->area_spans = NULL;
	}
  return 0;
}

BOOL CALLBACK animeConfigDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
  MyFilterData *mfd = (MyFilterData *)GetWindowLong(hdlg, DWL_USER);
  char buf[8];

  switch(msg)
	{
		case WM_INITDIALOG:
			SetWindowLong(hdlg, DWL_USER, lParam);
			mfd = (MyFilterData *)lParam;

			mfd->ifp->InitButton(GetDlgItem(hdlg, IDC_PREVIEW));

      SendDlgItemMessage(hdlg, IDC_SLIDER_SPANWIDTH, TBM_SETRANGE, false, MAKELONG (1, 64));
      SendDlgItemMessage(hdlg, IDC_SLIDER_SPANWIDTH, TBM_SETPOS, true, mfd->min_span_width);
      sprintf(buf, "%d", mfd->min_span_width);
      SetDlgItemText(hdlg, IDC_SPANWIDTH, buf);

      SendDlgItemMessage(hdlg, IDC_SLIDER_MINOVERLAP, TBM_SETRANGE, false, MAKELONG (1, 64));
      SendDlgItemMessage(hdlg, IDC_SLIDER_MINOVERLAP, TBM_SETPOS, true, mfd->min_span_overlap);
      sprintf(buf, "%d", mfd->min_span_overlap);
      SetDlgItemText(hdlg, IDC_MINOVERLAP, buf);
			
      SendDlgItemMessage(hdlg, IDC_SLIDER_AREAPIXELS, TBM_SETRANGE, false, MAKELONG (1, 256));
      SendDlgItemMessage(hdlg, IDC_SLIDER_AREAPIXELS, TBM_SETPOS, true, (int)sqrt(mfd->min_area_pixels));
      sprintf(buf, "%d", mfd->min_area_pixels);
      SetDlgItemText(hdlg, IDC_AREAPIXELS, buf);

      SendDlgItemMessage(hdlg, IDC_SLIDER_MAXPIXELDIFFERENCE, TBM_SETRANGE, false, MAKELONG (1, 64));
      SendDlgItemMessage(hdlg, IDC_SLIDER_MAXPIXELDIFFERENCE, TBM_SETPOS, true, mfd->max_pixel_color_difference);
      sprintf(buf, "%d", mfd->max_pixel_color_difference);
      SetDlgItemText(hdlg, IDC_MAXPIXELDIFFERENCE, buf);

      SendDlgItemMessage(hdlg, IDC_SLIDER_MAXSPANDIFFERENCE, TBM_SETRANGE, false, MAKELONG (1, 64));
      SendDlgItemMessage(hdlg, IDC_SLIDER_MAXSPANDIFFERENCE, TBM_SETPOS, true, mfd->max_span_color_difference);
      sprintf(buf, "%d", mfd->max_span_color_difference);
      SetDlgItemText(hdlg, IDC_MAXSPANDIFFERENCE, buf);

      SendDlgItemMessage(hdlg, IDC_SLIDER_NOISECUTOFF, TBM_SETRANGE, false, MAKELONG (1, 16383));
      SendDlgItemMessage(hdlg, IDC_SLIDER_NOISECUTOFF, TBM_SETPOS, true, mfd->noise_cutoff);
      sprintf(buf, "%.3f", (double)mfd->noise_cutoff / 1024.0);
      SetDlgItemText(hdlg, IDC_NOISECUTOFF, buf);
						
			CheckDlgButton(hdlg, IDC_CHECK_NOISE, mfd->filter_noise ? BST_CHECKED : BST_UNCHECKED);
			EnableWindow(GetDlgItem(hdlg, IDC_NOISECUTOFF), mfd->filter_noise);
			EnableWindow(GetDlgItem(hdlg, IDC_SLIDER_NOISECUTOFF), mfd->filter_noise);

			CheckDlgButton(hdlg, IDC_CHECK_SHOWAREA, mfd->show_areas ? BST_CHECKED : BST_UNCHECKED);
			return TRUE;
		case WM_HSCROLL:
			{
				bool redraw = false, reinit = false;
				int span_width, span_overlap, area_pixels, max_pixel_difference, max_span_difference, noise_cutoff;

				span_width = SendDlgItemMessage(hdlg, IDC_SLIDER_SPANWIDTH, TBM_GETPOS, 0, 0);
				if (span_width != mfd->min_span_width)
				{
					reinit = true;
		      sprintf(buf, "%d", span_width);
				  SetDlgItemText(hdlg, IDC_SPANWIDTH, buf);
				}

				span_overlap = SendDlgItemMessage(hdlg, IDC_SLIDER_MINOVERLAP, TBM_GETPOS, 0, 0);
				if (span_overlap  != mfd->min_span_overlap)
				{
					redraw = true;
		      sprintf(buf, "%d", span_overlap);
				  SetDlgItemText(hdlg, IDC_MINOVERLAP, buf);
				}

				area_pixels = SendDlgItemMessage(hdlg, IDC_SLIDER_AREAPIXELS, TBM_GETPOS, 0, 0);
				area_pixels *= area_pixels;
				if (area_pixels != mfd->min_area_pixels)
				{
					redraw = true;
		      sprintf(buf, "%d", area_pixels);
				  SetDlgItemText(hdlg, IDC_AREAPIXELS, buf);
				}

				max_pixel_difference = SendDlgItemMessage(hdlg, IDC_SLIDER_MAXPIXELDIFFERENCE, TBM_GETPOS, 0, 0);
				if (max_pixel_difference != mfd->max_pixel_color_difference)
				{
					redraw = true;
		      sprintf(buf, "%d", max_pixel_difference);
				  SetDlgItemText(hdlg, IDC_MAXPIXELDIFFERENCE, buf);
				}

				max_span_difference = SendDlgItemMessage(hdlg, IDC_SLIDER_MAXSPANDIFFERENCE, TBM_GETPOS, 0, 0);
				if (max_span_difference != mfd->max_span_color_difference)
				{
					redraw = true;
		      sprintf(buf, "%d", max_span_difference);
				  SetDlgItemText(hdlg, IDC_MAXSPANDIFFERENCE, buf);
				}

				noise_cutoff = SendDlgItemMessage(hdlg, IDC_SLIDER_NOISECUTOFF, TBM_GETPOS, 0, 0);
				if (noise_cutoff != mfd->noise_cutoff)
				{
					redraw = true;
		      sprintf(buf, "%.3f", (double)mfd->noise_cutoff / 1024.0);
				  SetDlgItemText(hdlg, IDC_NOISECUTOFF, buf);
				}

				if (reinit)
					mfd->ifp->UndoSystem();

				mfd->min_span_width = span_width;
				mfd->min_span_overlap = span_overlap;
				mfd->min_area_pixels = area_pixels;
				mfd->max_pixel_color_difference = max_pixel_difference;
				mfd->max_span_color_difference = max_span_difference;
				mfd->noise_cutoff = noise_cutoff;

				if (reinit)
					mfd->ifp->RedoSystem();
				else if (redraw)
					mfd->ifp->RedoFrame();
			}
			return TRUE;
		case WM_COMMAND:
			switch(LOWORD(wParam))
			{
				case IDOK:
					mfd->ifp->Close();
					EndDialog(hdlg, 0);
					return TRUE;
				case IDCANCEL:
					mfd->ifp->Close();
					EndDialog(hdlg, 1);
					return FALSE;
				case IDC_PREVIEW:
					mfd->ifp->Toggle(hdlg);
					return TRUE;
				case IDC_CHECK_SHOWAREA:
					mfd->show_areas = !!IsDlgButtonChecked(hdlg, IDC_CHECK_SHOWAREA);
					mfd->ifp->RedoFrame();
					return TRUE;
				case IDC_CHECK_NOISE:
					mfd->filter_noise = !!IsDlgButtonChecked(hdlg, IDC_CHECK_NOISE);
					EnableWindow(GetDlgItem(hdlg, IDC_NOISECUTOFF), mfd->filter_noise);
					EnableWindow(GetDlgItem(hdlg, IDC_SLIDER_NOISECUTOFF), mfd->filter_noise);
					mfd->ifp->RedoFrame();
					return TRUE;
			}
			break;
	}
  return FALSE;
}

int animeConfigProc(FilterActivation *fa, const FilterFunctions *ff, HWND hwnd)
{
	MyFilterData *mfd = (MyFilterData *)fa->filter_data;
	MyFilterData mfd2 = *mfd;
	int ret;

	mfd->ifp = fa->ifp;

	ret = DialogBoxParam(fa->filter->module->hInstModule, MAKEINTRESOURCE(IDD_FILTER_AREA), hwnd, animeConfigDlgProc, (LONG)mfd);

	if (ret)
		*mfd = mfd2;

	return ret;
}
