/*
 * Copyright 2014, Blender Foundation.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Contributor:
 *		Lukas Toenne
 */

#include "COM_PlaneCornerPinOperation.h"
#include "COM_ReadBufferOperation.h"

#include "MEM_guardedalloc.h"

#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_math_color.h"

extern "C" {
#  include "BLI_jitter.h"

#  include "BKE_node.h"
}

static bool check_corners(float corners[4][2])
{
	int i, next, prev;
	float cross = 0.0f;

	for (i = 0; i < 4; i++) {
		float v1[2], v2[2], cur_cross;

		next = (i + 1) % 4;
		prev = (4 + i - 1) % 4;

		sub_v2_v2v2(v1, corners[i], corners[prev]);
		sub_v2_v2v2(v2, corners[next], corners[i]);

		cur_cross = cross_v2v2(v1, v2);
		if (fabsf(cur_cross) <= FLT_EPSILON)
			return false;
		
		if (cross == 0.0f)
			cross = cur_cross;
		else if (cross * cur_cross < 0.0f)
			return false;
	}

	return true;
}

static void readCornersFromSockets(rcti *rect, SocketReader *readers[4], float corners[4][2])
{
	for (int i = 0; i < 4; ++i) {
		float result[4] = {0.0f, 0.0f, 0.0f, 0.0f};
		readers[i]->readSampled(result, rect->xmin, rect->ymin, COM_PS_NEAREST);
		corners[i][0] = result[0];
		corners[i][1] = result[1];
	}
	
	/* convexity check:
	 * concave corners need to be prevented, otherwise
	 * BKE_tracking_homography_between_two_quads will freeze
	 */
	if (!check_corners(corners)) {
		/* simply revert to default corners
		 * there could be a more elegant solution,
		 * this prevents freezing at least.
		 */
		corners[0][0] = 0.0f; corners[0][1] = 0.0f;
		corners[1][0] = 1.0f; corners[1][1] = 0.0f;
		corners[2][0] = 1.0f; corners[2][1] = 1.0f;
		corners[3][0] = 0.0f; corners[3][1] = 1.0f;
	}
}


/* ******** PlaneCornerPinMaskOperation ******** */

PlaneCornerPinMaskOperation::PlaneCornerPinMaskOperation() :
    PlaneDistortMaskOperation(),
    m_corners_ready(false)
{
	addInputSocket(COM_DT_VECTOR);
	addInputSocket(COM_DT_VECTOR);
	addInputSocket(COM_DT_VECTOR);
	addInputSocket(COM_DT_VECTOR);
	
	/* XXX this is stupid: we need to make this "complex",
	 * so we can use the initializeTileData function
	 * to read corners from input sockets ...
	 */
	setComplex(true);
}

void PlaneCornerPinMaskOperation::initExecution()
{
	PlaneDistortMaskOperation::initExecution();
	
	initMutex();
}

void PlaneCornerPinMaskOperation::deinitExecution()
{
	PlaneDistortMaskOperation::deinitExecution();
	
	deinitMutex();
}

void *PlaneCornerPinMaskOperation::initializeTileData(rcti *rect)
{
	void *data = PlaneDistortMaskOperation::initializeTileData(rect);
	
	/* get corner values once, by reading inputs at (0,0)
	 * XXX this assumes invariable values (no image inputs),
	 * we don't have a nice generic system for that yet
	 */
	lockMutex();
	if (!m_corners_ready) {
		SocketReader *readers[4] = { getInputSocketReader(0),
		                             getInputSocketReader(1),
		                             getInputSocketReader(2),
		                             getInputSocketReader(3) };
		float corners[4][2];
		readCornersFromSockets(rect, readers, corners);
		calculateCorners(corners, true);
		
		m_corners_ready = true;
	}
	unlockMutex();
	
	return data;
}

void PlaneCornerPinMaskOperation::determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2])
{
	resolution[0] = preferredResolution[0];
	resolution[1] = preferredResolution[1];
}


/* ******** PlaneCornerPinWarpImageOperation ******** */

PlaneCornerPinWarpImageOperation::PlaneCornerPinWarpImageOperation() :
    PlaneDistortWarpImageOperation(),
    m_corners_ready(false)
{
	addInputSocket(COM_DT_VECTOR);
	addInputSocket(COM_DT_VECTOR);
	addInputSocket(COM_DT_VECTOR);
	addInputSocket(COM_DT_VECTOR);
}

void PlaneCornerPinWarpImageOperation::initExecution()
{
	PlaneDistortWarpImageOperation::initExecution();
	
	initMutex();
}

void PlaneCornerPinWarpImageOperation::deinitExecution()
{
	PlaneDistortWarpImageOperation::deinitExecution();
	
	deinitMutex();
}

void *PlaneCornerPinWarpImageOperation::initializeTileData(rcti *rect)
{
	void *data = PlaneDistortWarpImageOperation::initializeTileData(rect);
	
	/* get corner values once, by reading inputs at (0,0)
	 * XXX this assumes invariable values (no image inputs),
	 * we don't have a nice generic system for that yet
	 */
	lockMutex();
	if (!m_corners_ready) {
		/* corner sockets start at index 1 */
		SocketReader *readers[4] = { getInputSocketReader(1),
		                             getInputSocketReader(2),
		                             getInputSocketReader(3),
		                             getInputSocketReader(4) };
		float corners[4][2];
		readCornersFromSockets(rect, readers, corners);
		calculateCorners(corners, true);
		calculatePerspectiveMatrix();
		
		m_corners_ready = true;
	}
	unlockMutex();
	
	return data;
}

bool PlaneCornerPinWarpImageOperation::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output)
{
	for (int i = 0; i < 4; ++i)
		if (getInputOperation(i + 1)->determineDependingAreaOfInterest(input, readOperation, output))
			return true;
	
	/* XXX this is bad, but unavoidable with the current design:
	 * we don't know the actual corners and matrix at this point,
	 * so all we can do is get the full input image
	 */
	output->xmin = 0;
	output->ymin = 0;
	output->xmax = getInputOperation(0)->getWidth();
	output->ymax = getInputOperation(0)->getHeight();
	return true;
//	return PlaneDistortWarpImageOperation::determineDependingAreaOfInterest(input, readOperation, output);
}
