/*

Miranda NG: the free IM client for Microsoft* Windows*

Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-08 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

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.

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Created by Pescuma
*/

#include "stdafx.h"
#include "modern_image_array.h"

#pragma comment(lib, "msimg32.lib")

static mir_cs cs;

// To use this code in other places, replace the body of this func by the body of ske_CreateDIB32
static HBITMAP ImageArray_CreateBitmapPoint(int cx, int cy, void ** pt)
{
	return ske_CreateDIB32Point(cx, cy, pt);
}

// Initialize data
static BOOL ImageArray_Alloc(IMAGE_ARRAY_DATA *iad, int size)
{
	int size_grow = size;

	if (size_grow > iad->nodes_allocated_size) {
		size_grow += iad->grow_step - (size_grow % iad->grow_step);

		if (iad->nodes != nullptr) {
			IMAGE_ARRAY_DATA_NODE *tmp = (IMAGE_ARRAY_DATA_NODE *)realloc((void *)iad->nodes,
				sizeof(IMAGE_ARRAY_DATA_NODE) * size_grow);

			if (tmp == nullptr) {
				TRACE("Out of memory: realloc returned nullptr (ImageArray_Alloc)");
				ImageArray_Free(iad, FALSE);
				return FALSE;
			}

			iad->nodes = tmp;
			memset(&iad->nodes[iad->nodes_allocated_size], 0, (size_grow - iad->nodes_allocated_size) * sizeof(IMAGE_ARRAY_DATA_NODE));
		}
		else {
			iad->nodes = (IMAGE_ARRAY_DATA_NODE *)malloc(sizeof(IMAGE_ARRAY_DATA_NODE) * size_grow);

			if (iad->nodes == nullptr) {
				TRACE("Out of memory: alloc returned nullptr (ImageArray_Alloc)");

				ImageArray_Free(iad, FALSE);

				return FALSE;
			}

			memset(iad->nodes, 0, (sizeof(IMAGE_ARRAY_DATA_NODE) * size_grow));
		}

		iad->nodes_allocated_size = size_grow;
	}
	else if (size < iad->nodes_allocated_size) {
		// Give some more space to try to avoid a free
		if ((iad->nodes_allocated_size - size) / iad->grow_step >= 2) {
			IMAGE_ARRAY_DATA_NODE *tmp;

			size_grow += iad->grow_step - (size_grow % iad->grow_step);

			tmp = (IMAGE_ARRAY_DATA_NODE *)realloc((void *)iad->nodes, sizeof(IMAGE_ARRAY_DATA_NODE) * size_grow);

			if (tmp == nullptr) {
				TRACE("Out of memory: realloc returned nullptr when reducing size! (ImageArray_Alloc)");

				ImageArray_Free(iad, FALSE);

				return FALSE;
			}

			iad->nodes = tmp;
		}
	}

	iad->nodes_size = size;

	return TRUE;
}


// Initialize data
void ImageArray_Initialize(IMAGE_ARRAY_DATA *iad, BOOL width_based, int grow_step)
{
	iad->width_based = width_based;
	iad->grow_step = grow_step;
	if (iad->grow_step <= 0) {
		iad->grow_step = 1;
	}
	iad->hdc = CreateCompatibleDC(nullptr);
	iad->img = nullptr;

	iad->width = 0;
	iad->height = 0;

	iad->nodes = nullptr;
	iad->nodes_allocated_size = 0;
	iad->nodes_size = 0;
}


// Free data
// If keep_bitmap is TRUE, doesn't delete de bitmap and return its handle. Else, return nullptr
HBITMAP ImageArray_Free(IMAGE_ARRAY_DATA *iad, BOOL keep_bitmap)
{
	DeleteDC(iad->hdc);

	if (iad->img != nullptr && !keep_bitmap) {
		DeleteObject(iad->img);
		iad->img = nullptr;
		iad->width = 0;
		iad->height = 0;
	}

	if (iad->nodes != nullptr) {
		free(iad->nodes);
		iad->nodes = nullptr;
		iad->nodes_allocated_size = 0;
		iad->nodes_size = 0;
	}

	return iad->img;
}

// Free data but keep config
void ImageArray_Clear(IMAGE_ARRAY_DATA *iad)
{
	HDC tmpdc = CreateCompatibleDC(iad->hdc);
	if (iad->hdc) DeleteDC(iad->hdc);
	iad->hdc = tmpdc;

	if (iad->img != nullptr) {
		DeleteObject(iad->img);
		iad->img = nullptr;
		iad->width = 0;
		iad->height = 0;
	}

	if (iad->nodes != nullptr) {
		free(iad->nodes);
		iad->nodes = nullptr;
		iad->nodes_allocated_size = 0;
		iad->nodes_size = 0;
	}
}


// Add image to the list (return the index of the image or -1 on error)
// If pos == -1, add to the end of the list
int ImageArray_AddImage(IMAGE_ARRAY_DATA *iad, HBITMAP hBmp, int pos)
{
	BITMAP bm;
	int new_width, new_height;
	HBITMAP hNewBmp, old_bmp;
	HDC hdc_old;
	BOOL last_one;

	int i;

	if (hBmp == nullptr)
		return -1;

	mir_cslock lck(cs);

	if (pos < 0)
		pos = iad->nodes_size;

	// Add to end?
	if (pos >= iad->nodes_size) {
		pos = iad->nodes_size;
		last_one = TRUE;
	}
	else {
		last_one = FALSE;
	}

	// Get bounds
	if (!GetObject(hBmp, sizeof(BITMAP), &bm))
		return -1;

	if (iad->width_based) {
		new_width = max(bm.bmWidth, iad->width);
		new_height = iad->height + bm.bmHeight;
	}
	else {
		new_width = bm.bmWidth + iad->width;
		new_height = max(iad->height, bm.bmHeight);
	}

	// Alloc image
	hNewBmp = ImageArray_CreateBitmapPoint(new_width, new_height, &(iad->lpBits));
	if (hNewBmp == nullptr)
		return -1;

	// Alloc array
	if (!ImageArray_Alloc(iad, iad->nodes_size + 1)) {
		DeleteObject(hNewBmp);
		return -1;
	}

	// Move image...

	// Set some draw states
	SelectObject(iad->hdc, hNewBmp);
	hdc_old = CreateCompatibleDC(iad->hdc);
	old_bmp = (HBITMAP)GetCurrentObject(hdc_old, OBJ_BITMAP);

	SetBkMode(iad->hdc, TRANSPARENT);
	{
		POINT org;
		GetBrushOrgEx(iad->hdc, &org);
		SetStretchBltMode(iad->hdc, HALFTONE);
		SetBrushOrgEx(iad->hdc, org.x, org.y, nullptr);
	}

	{
		int x = 0, y = 0, w = 0, h = 0;

		// 1- old data
		if (pos > 0) {
			SelectObject(hdc_old, iad->img);

			if (iad->width_based) {
				w = iad->width;
				h = 0;
				for (i = 0; i < pos; i++) {
					h += iad->nodes[i].height;
				}
			}
			else {
				h = iad->height;
				w = 0;
				for (i = 0; i < pos; i++) {
					w += iad->nodes[i].width;
				}
			}
			BitBlt(iad->hdc, 0, 0, w, h, hdc_old, 0, 0, SRCCOPY);
		}

		// 2- new image
		if (iad->width_based) {
			x = 0;
			y = h;
		}
		else {
			x = w;
			y = 0;
		}
		SelectObject(hdc_old, hBmp);
		BitBlt(iad->hdc, x, y, bm.bmWidth, bm.bmHeight, hdc_old, 0, 0, SRCCOPY);

		// 3- old data
		if (!last_one) {
			int ox, oy;

			SelectObject(hdc_old, iad->img);

			if (iad->width_based) {
				ox = 0;
				oy = y;

				x = 0;
				y += bm.bmHeight;

				w = iad->width;
				h = iad->height - h;
			}
			else {
				ox = x;
				oy = 0;

				x += bm.bmWidth;
				y = 0;

				w = iad->width - w;
				h = iad->height;
			}
			BitBlt(iad->hdc, x, y, w, h, hdc_old, ox, oy, SRCCOPY);
		}
	}

	// restore things
	SelectObject(hdc_old, old_bmp);
	DeleteDC(hdc_old);
	if (iad->img != nullptr) DeleteObject(iad->img);
	iad->img = hNewBmp;

	// Move array
	if (!last_one && iad->nodes_size > 1) {
		memmove(&iad->nodes[pos + 1], &iad->nodes[pos], (iad->nodes_size - pos) * sizeof(IMAGE_ARRAY_DATA_NODE));
	}
	iad->nodes[pos].width = bm.bmWidth;
	iad->nodes[pos].height = bm.bmHeight;

	iad->width = new_width;
	iad->height = new_height;

	return pos;
}



// Change an image in the list (return TRUE on success)
BOOL ImageArray_ChangeImage(IMAGE_ARRAY_DATA *iad, HBITMAP hBmp, int pos)
{
	BITMAP bm;
	int new_width, new_height;
	HBITMAP hNewBmp;
	HDC hdc_old;
	int i;

	if (hBmp == nullptr)
		return FALSE;

	if (pos < 0)
		return FALSE;

	if (pos >= iad->nodes_size)
		return FALSE;

	mir_cslock lck(cs);

	// Get bounds
	if (!GetObject(hBmp, sizeof(BITMAP), &bm))
		return FALSE;

	if (iad->width_based) {
		new_width = max(bm.bmWidth, iad->width);
		new_height = iad->height + bm.bmHeight - iad->nodes[pos].height;
	}
	else {
		new_width = bm.bmWidth + iad->width - iad->nodes[pos].width;
		new_height = max(iad->height, bm.bmHeight);
	}

	// Alloc image
	hNewBmp = ImageArray_CreateBitmapPoint(new_width, new_height, &(iad->lpBits));
	if (hNewBmp == nullptr)
		return FALSE;

	// Move image...

	// Set some draw states
	SelectObject(iad->hdc, hNewBmp);
	hdc_old = CreateCompatibleDC(iad->hdc);

	SetBkMode(iad->hdc, TRANSPARENT);
	{
		POINT org;
		GetBrushOrgEx(iad->hdc, &org);
		SetStretchBltMode(iad->hdc, HALFTONE);
		SetBrushOrgEx(iad->hdc, org.x, org.y, nullptr);
	}

	{
		int x = 0, y = 0, w = 0, h = 0;

		// 1- old data
		if (pos > 0) {
			SelectObject(hdc_old, iad->img);

			if (iad->width_based) {
				w = iad->width;
				h = 0;
				for (i = 0; i < pos; i++) {
					h += iad->nodes[i].height;
				}
			}
			else {
				h = iad->height;
				w = 0;
				for (i = 0; i < pos; i++) {
					w += iad->nodes[i].width;
				}
			}
			BitBlt(iad->hdc, 0, 0, w, h, hdc_old, 0, 0, SRCCOPY);
		}

		// 2- new image
		if (iad->width_based) {
			x = 0;
			y = h;
		}
		else {
			x = w;
			y = 0;
		}
		SelectObject(hdc_old, hBmp);
		BitBlt(iad->hdc, x, y, bm.bmWidth, bm.bmHeight, hdc_old, 0, 0, SRCCOPY);

		// 3- old data
		if (pos < iad->nodes_size - 1) {
			int ox, oy;

			SelectObject(hdc_old, iad->img);

			if (iad->width_based) {
				ox = 0;
				oy = y + iad->nodes[pos].height;

				x = 0;
				y += bm.bmHeight;

				w = iad->width;
				h = iad->height - h - iad->nodes[pos].height;
			}
			else {
				ox = x + iad->nodes[pos].width;
				oy = 0;

				x += bm.bmWidth;
				y = 0;

				w = iad->width - w - iad->nodes[pos].width;
				h = iad->height;
			}
			BitBlt(iad->hdc, x, y, w, h, hdc_old, ox, oy, SRCCOPY);
		}
	}

	// restore things
	DeleteDC(hdc_old);
	if (iad->img != nullptr) DeleteObject(iad->img);
	iad->img = hNewBmp;

	// Move array
	iad->nodes[pos].width = bm.bmWidth;
	iad->nodes[pos].height = bm.bmHeight;

	iad->width = new_width;
	iad->height = new_height;

	return pos;
}


// Remove an image
BOOL ImageArray_RemoveImage(IMAGE_ARRAY_DATA *iad, int pos)
{
	int new_width, new_height;
	HBITMAP hNewBmp;
	HDC hdc_old;
	int i;

	if (pos < 0)
		return FALSE;

	if (pos >= iad->nodes_size)
		return FALSE;

	mir_cslock lck(cs);

	// Get bounds
	if (iad->width_based) {
		new_width = iad->width;
		new_height = iad->height - iad->nodes[pos].height;
	}
	else {
		new_width = iad->width - iad->nodes[pos].width;
		new_height = iad->height;
	}

	// Alloc image
	hNewBmp = ImageArray_CreateBitmapPoint(new_width, new_height, &(iad->lpBits));
	if (hNewBmp == nullptr)
		return FALSE;

	// Move image...

	// Set some draw states
	SelectObject(iad->hdc, hNewBmp);
	hdc_old = CreateCompatibleDC(iad->hdc);

	SetBkMode(iad->hdc, TRANSPARENT);
	{
		POINT org;
		GetBrushOrgEx(iad->hdc, &org);
		SetStretchBltMode(iad->hdc, HALFTONE);
		SetBrushOrgEx(iad->hdc, org.x, org.y, nullptr);
	}

	{
		int x = 0, y = 0, w = 0, h = 0;

		if (pos > 0) {
			SelectObject(hdc_old, iad->img);

			if (iad->width_based) {
				w = iad->width;
				h = 0;
				for (i = 0; i < pos; i++) {
					h += iad->nodes[i].height;
				}
			}
			else {
				h = iad->height;
				w = 0;
				for (i = 0; i < pos; i++)
					w += iad->nodes[i].width;
			}
			BitBlt(iad->hdc, 0, 0, w, h, hdc_old, 0, 0, SRCCOPY);
		}

		if (pos < iad->nodes_size - 1) {
			int ox, oy;

			SelectObject(hdc_old, iad->img);

			if (iad->width_based) {
				ox = 0;
				oy = h + iad->nodes[pos].height;

				x = 0;
				y = h;

				w = iad->width;
				h = iad->height - h - iad->nodes[pos].height;
			}
			else {
				ox = w + iad->nodes[pos].width;
				oy = 0;

				x = w;
				y = 0;

				w = iad->width - w - iad->nodes[pos].width;
				h = iad->height;
			}
			BitBlt(iad->hdc, x, y, w, h, hdc_old, ox, oy, SRCCOPY);
		}
	}

	// restore things
	DeleteDC(hdc_old);
	if (iad->img != nullptr) DeleteObject(iad->img);
	iad->img = hNewBmp;

	// Move array
	if (pos < iad->nodes_size - 1) {
		memmove(&iad->nodes[pos], &iad->nodes[pos + 1], (iad->nodes_size - pos - 1) * sizeof(IMAGE_ARRAY_DATA_NODE));
	}

	iad->nodes_size--;

	iad->width = new_width;
	iad->height = new_height;

	// Free array
	ImageArray_Alloc(iad, iad->nodes_size);

	return pos;
}



BOOL ImageArray_DrawImage(IMAGE_ARRAY_DATA *iad, int pos, HDC hdcDest, int nXDest, int nYDest, uint8_t Alpha)
{
	if (hdcDest == nullptr || pos < 0 || pos >= iad->nodes_size)
		return FALSE;

	mir_cslock lck(cs);
	{
		int w, h, i;

		if (iad->width_based) {
			w = 0;
			h = 0;
			for (i = 0; i < pos; i++) {
				h += iad->nodes[i].height;
			}
		}
		else {
			h = 0;
			w = 0;
			for (i = 0; i < pos; i++) {
				w += iad->nodes[i].width;
			}
		}

		BLENDFUNCTION bf = { AC_SRC_OVER, 0, Alpha, AC_SRC_ALPHA };
		AlphaBlend(hdcDest, nXDest, nYDest, iad->nodes[pos].width, iad->nodes[pos].height, iad->hdc, w, h, iad->nodes[pos].width, iad->nodes[pos].height, bf);
	}

	return FALSE;
}

BOOL ImageArray_GetImageSize(IMAGE_ARRAY_DATA *iad, int pos, SIZE *lpSize)
{
	mir_cslock lck(cs);
	if (lpSize) {
		lpSize->cx = iad->nodes[pos].width;
		lpSize->cy = iad->nodes[pos].height;
	}
	return TRUE;
}