/*
 * Copyright (C) 2012-2024 Robin Haberkorn
 *
 * 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 3 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, see .
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include 
#include 
#include "sciteco.h"
#include "view.h"
#include "undo.h"
#include "qreg.h"
#include "doc.h"
static inline teco_doc_scintilla_t *
teco_doc_scintilla_ref(teco_doc_scintilla_t *doc)
{
	if (doc)
		teco_view_ssm(teco_qreg_view, SCI_ADDREFDOCUMENT, 0, (sptr_t)doc);
	return doc;
}
static inline void
teco_doc_scintilla_release(teco_doc_scintilla_t *doc)
{
	if (doc)
		teco_view_ssm(teco_qreg_view, SCI_RELEASEDOCUMENT, 0, (sptr_t)doc);
}
TECO_DEFINE_UNDO_OBJECT(doc_scintilla, teco_doc_scintilla_t *,
                        teco_doc_scintilla_ref, teco_doc_scintilla_release);
static inline teco_doc_scintilla_t *
teco_doc_get_scintilla(teco_doc_t *ctx)
{
	/*
	 * FIXME: Perhaps we should always specify SC_DOCUMENTOPTION_TEXT_LARGE?
	 * SC_DOCUMENTOPTION_STYLES_NONE is unfortunately also not safe to set
	 * always as the Q-Reg might well be used for styling even in batch mode.
	 */
	if (G_UNLIKELY(!ctx->doc))
		ctx->doc = (teco_doc_scintilla_t *)teco_view_ssm(teco_qreg_view, SCI_CREATEDOCUMENT, 0, 0);
	return ctx->doc;
}
/**
 * Edit the given document in the Q-Register view.
 *
 * @param ctx The document to edit.
 * @param default_cp The codepage to configure if the document is new.
 *
 * @memberof teco_doc_t
 */
void
teco_doc_edit(teco_doc_t *ctx, guint default_cp)
{
	gboolean new_doc = ctx->doc == NULL;
	teco_view_ssm(teco_qreg_view, SCI_SETDOCPOINTER, 0,
	              (sptr_t)teco_doc_get_scintilla(ctx));
	teco_view_ssm(teco_qreg_view, SCI_SETFIRSTVISIBLELINE, ctx->first_line, 0);
	teco_view_ssm(teco_qreg_view, SCI_SETXOFFSET, ctx->xoffset, 0);
	teco_view_ssm(teco_qreg_view, SCI_SETSEL, ctx->anchor, (sptr_t)ctx->dot);
	/*
	 * NOTE: Thanks to a custom Scintilla patch, representations
	 * do not get reset after SCI_SETDOCPOINTER, so they have to be
	 * initialized only once.
	 */
	//teco_view_set_representations(teco_qreg_view);
	if (new_doc && default_cp != SC_CP_UTF8) {
		/*
		 * There is a chance the user will see this buffer even if we
		 * are currently in batch mode.
		 */
		for (gint style = 0; style <= STYLE_LASTPREDEFINED; style++)
			teco_view_ssm(teco_qreg_view, SCI_STYLESETCHARACTERSET,
			              style, default_cp);
		/* 0 is used for ALL single-byte encodings */
		teco_view_ssm(teco_qreg_view, SCI_SETCODEPAGE, 0, 0);
	} else if (!(teco_view_ssm(teco_qreg_view, SCI_GETLINECHARACTERINDEX, 0, 0)
							& SC_LINECHARACTERINDEX_UTF32)) {
		/*
		 * All UTF-8 documents are expected to have a character index.
		 * This allocates nothing if the document is not UTF-8.
		 * But it is reference counted, so it must not be allocated
		 * more than once.
		 *
		 * FIXME: This apparently gets reset with every SCI_SETDOCPOINTER
		 * (although I don't know why and where).
		 * Recalculating it could be inefficient.
		 * The index is reference-counted. Perhaps we could just allocate
		 * one more time, so it doesn't get freed when changing documents.
		 */
		teco_view_ssm(teco_qreg_view, SCI_ALLOCATELINECHARACTERINDEX,
		              SC_LINECHARACTERINDEX_UTF32, 0);
	}
}
/** @memberof teco_doc_t */
void
teco_doc_undo_edit(teco_doc_t *ctx)
{
	/*
	 * NOTE: see above in teco_doc_edit()
	 */
	//undo__teco_view_set_representations(teco_qreg_view);
	undo__teco_view_ssm(teco_qreg_view, SCI_SETSEL, ctx->anchor, (sptr_t)ctx->dot);
	undo__teco_view_ssm(teco_qreg_view, SCI_SETXOFFSET, ctx->xoffset, 0);
	undo__teco_view_ssm(teco_qreg_view, SCI_SETFIRSTVISIBLELINE, ctx->first_line, 0);
	undo__teco_view_ssm(teco_qreg_view, SCI_SETDOCPOINTER, 0,
	                    (sptr_t)teco_doc_get_scintilla(ctx));
}
/** @memberof teco_doc_t */
void
teco_doc_set_string(teco_doc_t *ctx, const gchar *str, gsize len, guint codepage)
{
	if (teco_qreg_current)
		teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
	teco_doc_scintilla_release(ctx->doc);
	ctx->doc = NULL;
	teco_doc_reset(ctx);
	teco_doc_edit(ctx, codepage);
	teco_view_ssm(teco_qreg_view, SCI_APPENDTEXT, len, (sptr_t)(str ? : ""));
	if (teco_qreg_current)
		teco_doc_edit(&teco_qreg_current->string, 0);
}
/** @memberof teco_doc_t */
void
teco_doc_undo_set_string(teco_doc_t *ctx)
{
	/*
	 * Necessary, so that upon rubout the
	 * string's parameters are restored.
	 */
	teco_doc_update(ctx, teco_qreg_view);
	if (teco_qreg_current && teco_qreg_current->must_undo && // FIXME
	    ctx == &teco_qreg_current->string)
		/* load old document into view */
		teco_doc_undo_edit(&teco_qreg_current->string);
	teco_doc_undo_reset(ctx);
	teco_undo_object_doc_scintilla_push(&ctx->doc);
}
/**
 * Get a document as a string.
 *
 * @param ctx The document.
 * @param str Pointer to a variable to hold the return string.
 *            It can be NULL if you are interested only in the string's length.
 *            Strings must be freed via g_free().
 * @param len Where to store the string's length (mandatory).
 * @param codepage Where to store the document's codepage or NULL
 *                 if that information is not necessary.
 *
 * @see teco_qreg_vtable_t::get_string()
 * @memberof teco_doc_t
 */
void
teco_doc_get_string(teco_doc_t *ctx, gchar **str, gsize *outlen, guint *codepage)
{
	if (!ctx->doc) {
		if (str)
			*str = NULL;
		if (outlen)
			*outlen = 0;
		if (codepage)
			*codepage = teco_default_codepage();
		return;
	}
	if (teco_qreg_current)
		teco_doc_update(&teco_qreg_current->string, teco_qreg_view);
	teco_doc_edit(ctx, teco_default_codepage());
	gsize len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0);
	if (str) {
		*str = g_malloc(len + 1);
		teco_view_ssm(teco_qreg_view, SCI_GETTEXT, len + 1, (sptr_t)*str);
	}
	if (outlen)
		*outlen = len;
	if (codepage)
		*codepage = teco_view_get_codepage(teco_qreg_view);
	if (teco_qreg_current)
		teco_doc_edit(&teco_qreg_current->string, 0);
}
/** @memberof teco_doc_t */
void
teco_doc_update_from_view(teco_doc_t *ctx, teco_view_t *from)
{
	ctx->anchor = teco_view_ssm(from, SCI_GETANCHOR, 0, 0);
	ctx->dot = teco_view_ssm(from, SCI_GETCURRENTPOS, 0, 0);
	ctx->first_line = teco_view_ssm(from, SCI_GETFIRSTVISIBLELINE, 0, 0);
	ctx->xoffset = teco_view_ssm(from, SCI_GETXOFFSET, 0, 0);
}
/** @memberof teco_doc_t */
void
teco_doc_update_from_doc(teco_doc_t *ctx, const teco_doc_t *from)
{
	ctx->anchor = from->anchor;
	ctx->dot = from->dot;
	ctx->first_line = from->first_line;
	ctx->xoffset = from->xoffset;
}
/**
 * Only for teco_qreg_stack_pop() which does some clever
 * exchanging of document data (without any deep copying)
 *
 * @memberof teco_doc_t
 */
void
teco_doc_exchange(teco_doc_t *ctx, teco_doc_t *other)
{
	teco_doc_t temp;
	memcpy(&temp, ctx, sizeof(temp));
	memcpy(ctx, other, sizeof(*ctx));
	memcpy(other, &temp, sizeof(*other));
}
/** @memberof teco_doc_t */
void
teco_doc_clear(teco_doc_t *ctx)
{
	teco_doc_scintilla_release(ctx->doc);
}