/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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.
 *
 * The Original Code is Copyright (C) 2016 Blender Foundation.
 * All rights reserved.
 *
 *
 * Contributor(s): Blender Foundation,
 *                 Sergey Sharybin
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/editors/space_clip/tracking_ops_solve.c
 *  \ingroup spclip
 */

#include "MEM_guardedalloc.h"

#include "DNA_camera_types.h"
#include "DNA_object_types.h"   /* SELECT */
#include "DNA_screen_types.h"
#include "DNA_space_types.h"

#include "BLI_utildefines.h"
#include "BLI_string.h"

#include "BKE_context.h"
#include "BKE_movieclip.h"
#include "BKE_tracking.h"
#include "BKE_global.h"
#include "BKE_depsgraph.h"
#include "BKE_report.h"
#include "BKE_library.h"

#include "WM_api.h"
#include "WM_types.h"

#include "ED_clip.h"


#include "clip_intern.h"

/********************** solve camera operator *********************/

typedef struct {
	Scene *scene;
	MovieClip *clip;
	MovieClipUser user;

	ReportList *reports;

	char stats_message[256];

	struct MovieReconstructContext *context;
} SolveCameraJob;

static bool solve_camera_initjob(bContext *C,
                                 SolveCameraJob *scj,
                                 wmOperator *op,
                                 char *error_msg,
                                 int max_error)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	Scene *scene = CTX_data_scene(C);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
	int width, height;

	if (!BKE_tracking_reconstruction_check(tracking,
	                                       object,
	                                       error_msg,
	                                       max_error))
	{
		return false;
	}

	/* Could fail if footage uses images with different sizes. */
	BKE_movieclip_get_size(clip, &sc->user, &width, &height);

	scj->clip = clip;
	scj->scene = scene;
	scj->reports = op->reports;
	scj->user = sc->user;

	scj->context = BKE_tracking_reconstruction_context_new(clip,
	                                                       object,
	                                                       object->keyframe1,
	                                                       object->keyframe2,
	                                                       width,
	                                                       height);

	tracking->stats = MEM_callocN(sizeof(MovieTrackingStats), "solve camera stats");

	return true;
}

static void solve_camera_updatejob(void *scv)
{
	SolveCameraJob *scj = (SolveCameraJob *)scv;
	MovieTracking *tracking = &scj->clip->tracking;

	BLI_strncpy(tracking->stats->message,
	            scj->stats_message,
	            sizeof(tracking->stats->message));
}

static void solve_camera_startjob(void *scv, short *stop, short *do_update, float *progress)
{
	SolveCameraJob *scj = (SolveCameraJob *)scv;
	BKE_tracking_reconstruction_solve(scj->context,
	                                  stop,
	                                  do_update,
	                                  progress,
	                                  scj->stats_message,
	                                  sizeof(scj->stats_message));
}

static void solve_camera_freejob(void *scv)
{
	SolveCameraJob *scj = (SolveCameraJob *)scv;
	MovieTracking *tracking = &scj->clip->tracking;
	Scene *scene = scj->scene;
	MovieClip *clip = scj->clip;
	int solved;

	if (!scj->context) {
		/* job weren't fully initialized due to some error */
		MEM_freeN(scj);
		return;
	}

	solved = BKE_tracking_reconstruction_finish(scj->context, tracking);
	if (!solved) {
		BKE_report(scj->reports,
		           RPT_WARNING,
		           "Some data failed to reconstruct (see console for details)");
	}
	else {
		BKE_reportf(scj->reports,
		            RPT_INFO,
		            "Average re-projection error: %.3f",
		            tracking->reconstruction.error);
	}

	/* Set currently solved clip as active for scene. */
	if (scene->clip != NULL) {
		id_us_min(&clip->id);
	}
	scene->clip = clip;
	id_us_plus(&clip->id);

	/* Set blender camera focal length so result would look fine there. */
	if (scene->camera != NULL &&
	    scene->camera->data &&
	    GS(((ID *) scene->camera->data)->name) == ID_CA)
	{
		Camera *camera = (Camera *)scene->camera->data;
		int width, height;
		BKE_movieclip_get_size(clip, &scj->user, &width, &height);
		BKE_tracking_camera_to_blender(tracking, scene, camera, width, height);
		WM_main_add_notifier(NC_OBJECT, camera);
	}

	MEM_freeN(tracking->stats);
	tracking->stats = NULL;

	DAG_id_tag_update(&clip->id, 0);

	WM_main_add_notifier(NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, NULL);

	/* Update active clip displayed in scene buttons. */
	WM_main_add_notifier(NC_SCENE, scene);

	BKE_tracking_reconstruction_context_free(scj->context);
	MEM_freeN(scj);
}

static int solve_camera_exec(bContext *C, wmOperator *op)
{
	SolveCameraJob *scj;
	char error_msg[256] = "\0";
	scj = MEM_callocN(sizeof(SolveCameraJob), "SolveCameraJob data");
	if (!solve_camera_initjob(C, scj, op, error_msg, sizeof(error_msg))) {
		if (error_msg[0]) {
			BKE_report(op->reports, RPT_ERROR, error_msg);
		}
		solve_camera_freejob(scj);
		return OPERATOR_CANCELLED;
	}
	solve_camera_startjob(scj, NULL, NULL, NULL);
	solve_camera_freejob(scj);
	return OPERATOR_FINISHED;
}

static int solve_camera_invoke(bContext *C,
                               wmOperator *op,
                               const wmEvent *UNUSED(event))
{
	SolveCameraJob *scj;
	ScrArea *sa = CTX_wm_area(C);
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingReconstruction *reconstruction =
	        BKE_tracking_get_active_reconstruction(tracking);
	wmJob *wm_job;
	char error_msg[256] = "\0";

	if (WM_jobs_test(CTX_wm_manager(C), sa, WM_JOB_TYPE_ANY)) {
		/* only one solve is allowed at a time */
		return OPERATOR_CANCELLED;
	}

	scj = MEM_callocN(sizeof(SolveCameraJob), "SolveCameraJob data");
	if (!solve_camera_initjob(C, scj, op, error_msg, sizeof(error_msg))) {
		if (error_msg[0]) {
			BKE_report(op->reports, RPT_ERROR, error_msg);
		}
		solve_camera_freejob(scj);
		return OPERATOR_CANCELLED;
	}

	BLI_strncpy(tracking->stats->message,
	            "Solving camera | Preparing solve",
	            sizeof(tracking->stats->message));

	/* Hide reconstruction statistics from previous solve. */
	reconstruction->flag &= ~TRACKING_RECONSTRUCTED;
	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);

	/* Setup job. */
	wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), sa, "Solve Camera",
	                     WM_JOB_PROGRESS, WM_JOB_TYPE_CLIP_SOLVE_CAMERA);
	WM_jobs_customdata_set(wm_job, scj, solve_camera_freejob);
	WM_jobs_timer(wm_job, 0.1, NC_MOVIECLIP | NA_EVALUATED, 0);
	WM_jobs_callbacks(wm_job,
	                  solve_camera_startjob,
	                  NULL,
	                  solve_camera_updatejob,
	                  NULL);

	G.is_break = false;

	WM_jobs_start(CTX_wm_manager(C), wm_job);
	WM_cursor_wait(0);

	/* add modal handler for ESC */
	WM_event_add_modal_handler(C, op);

	return OPERATOR_RUNNING_MODAL;
}

static int solve_camera_modal(bContext *C,
                              wmOperator *UNUSED(op),
                              const wmEvent *event)
{
	/* No running solver, remove handler and pass through. */
	if (0 == WM_jobs_test(CTX_wm_manager(C), CTX_wm_area(C), WM_JOB_TYPE_ANY))
		return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;

	/* Running solver. */
	switch (event->type) {
		case ESCKEY:
			return OPERATOR_RUNNING_MODAL;
	}

	return OPERATOR_PASS_THROUGH;
}

void CLIP_OT_solve_camera(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Solve Camera";
	ot->description = "Solve camera motion from tracks";
	ot->idname = "CLIP_OT_solve_camera";

	/* api callbacks */
	ot->exec = solve_camera_exec;
	ot->invoke = solve_camera_invoke;
	ot->modal = solve_camera_modal;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** clear solution operator *********************/

static int clear_solution_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	MovieTrackingReconstruction *reconstruction =
	        BKE_tracking_get_active_reconstruction(tracking);

	for (MovieTrackingTrack *track = tracksbase->first;
	     track != NULL;
	     track = track->next)
	{
		track->flag &= ~TRACK_HAS_BUNDLE;
	}

	if (reconstruction->cameras != NULL) {
		MEM_freeN(reconstruction->cameras);
		reconstruction->cameras = NULL;
	}

	reconstruction->camnr = 0;
	reconstruction->flag &= ~TRACKING_RECONSTRUCTED;

	DAG_id_tag_update(&clip->id, 0);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_clear_solution(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Clear Solution";
	ot->description = "Clear all calculated data";
	ot->idname = "CLIP_OT_clear_solution";

	/* api callbacks */
	ot->exec = clear_solution_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
