/*
 * Copyright 2011, 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:
 *		Jeroen Bakker
 *		Monique Dewanchand
 *		Sergey Sharybin
 */

#include "COM_MovieDistortionOperation.h"

extern "C" {
#  include "BKE_tracking.h"
#  include "BKE_movieclip.h"
#  include "BLI_linklist.h"
}


MovieDistortionOperation::MovieDistortionOperation(bool distortion) : NodeOperation()
{
	this->addInputSocket(COM_DT_COLOR);
	this->addOutputSocket(COM_DT_COLOR);
	this->setResolutionInputSocketIndex(0);
	this->m_inputOperation = NULL;
	this->m_movieClip = NULL;
	this->m_apply = distortion;
}

void MovieDistortionOperation::initExecution()
{
	this->m_inputOperation = this->getInputSocketReader(0);
	if (this->m_movieClip) {
		MovieTracking *tracking = &this->m_movieClip->tracking;
		MovieClipUser clipUser = {0};
		int calibration_width, calibration_height;

		BKE_movieclip_user_set_frame(&clipUser, this->m_framenumber);
		BKE_movieclip_get_size(this->m_movieClip,
		                       &clipUser,
		                       &calibration_width,
		                       &calibration_height);

		float delta[2];
		rcti full_frame;
		full_frame.xmin = full_frame.ymin = 0;
		full_frame.xmax = this->m_width;
		full_frame.ymax = this->m_height;
		BKE_tracking_max_distortion_delta_across_bound(tracking,
		                                               &full_frame,
		                                               !this->m_apply,
		                                               delta);

		/* 5 is just in case we didn't hit real max of distortion in
		 * BKE_tracking_max_undistortion_delta_across_bound
		 */
		m_margin[0] = delta[0] + 5;
		m_margin[1] = delta[1] + 5;

		this->m_distortion = BKE_tracking_distortion_new(tracking,
		                                                 calibration_width,
		                                                 calibration_height);
		this->m_calibration_width = calibration_width;
		this->m_calibration_height = calibration_height;
		this->m_pixel_aspect = tracking->camera.pixel_aspect;
	}
	else {
		m_margin[0] = m_margin[1] = 0;
		this->m_distortion = NULL;
	}
}

void MovieDistortionOperation::deinitExecution()
{
	this->m_inputOperation = NULL;
	this->m_movieClip = NULL;
	if (this->m_distortion != NULL) {
		BKE_tracking_distortion_free(this->m_distortion);
	}
}

void MovieDistortionOperation::executePixelSampled(float output[4],
                                                   float x,
                                                   float y,
                                                   PixelSampler /*sampler*/)
{
	if (this->m_distortion != NULL) {
		/* float overscan = 0.0f; */
		const float pixel_aspect = this->m_pixel_aspect;
		const float w = (float)this->m_width /* / (1 + overscan) */;
		const float h = (float)this->m_height /* / (1 + overscan) */;
		const float aspx = w / (float)this->m_calibration_width;
		const float aspy = h / (float)this->m_calibration_height;
		float in[2];
		float out[2];

		in[0] = (x /* - 0.5 * overscan * w */) / aspx;
		in[1] = (y /* - 0.5 * overscan * h */) / aspy / pixel_aspect;

		if (this->m_apply) {
			BKE_tracking_distortion_undistort_v2(this->m_distortion, in, out);
		}
		else {
			BKE_tracking_distortion_distort_v2(this->m_distortion, in, out);
		}

		float u = out[0] * aspx /* + 0.5 * overscan * w */,
		      v = (out[1] * aspy /* + 0.5 * overscan * h */) * pixel_aspect;

		this->m_inputOperation->readSampled(output, u, v, COM_PS_BILINEAR);
	}
	else {
		this->m_inputOperation->readSampled(output, x, y, COM_PS_BILINEAR);
	}
}

bool MovieDistortionOperation::determineDependingAreaOfInterest(
        rcti *input,
        ReadBufferOperation *readOperation,
        rcti *output)
{
	rcti newInput;
	newInput.xmin = input->xmin - m_margin[0];
	newInput.ymin = input->ymin - m_margin[1];
	newInput.xmax = input->xmax + m_margin[0];
	newInput.ymax = input->ymax + m_margin[1];
	return NodeOperation::determineDependingAreaOfInterest(&newInput,
	                                                       readOperation,
	                                                       output);
}
