/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - OrCAD file format support
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2023)
 *
 *  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.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

#include <librnd/core/safe_fs.h>
#include <librnd/core/compat_misc.h>
#include <libcschem/config.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_any_obj.h>
#include <genht/htip.h>
#include <genht/hash.h>

#include <stdio.h>
#include <librnd/core/error.h>
#include <plugins/lib_alien/read_helper.h>
#include <plugins/lib_alien/read_postproc.h>

#include "read.h"
#include "read_parse.h"
#include "read_fio.h"

/* optional trace */
#if 1
#	include <stdio.h>
#	define tprintf printf
#else
	static int tprintf(const char *fmt, ...) { return 0; }
#endif

csch_cgrp_t *orcad_wirenet_lookup(io_orcad_rctx_t *rctx, long net_id, int alloc)
{
	csch_cgrp_t *wn = htip_get(&rctx->wirenets, net_id);

	if (wn == NULL) {
		csch_source_arg_t *src;
		csch_sheet_t *sheet = rctx->alien.sheet;
		int lineno = 0;
		const char *fn = "TODO71";

		if (!alloc)
			return NULL;


		wn = csch_cgrp_alloc(sheet, &sheet->direct, csch_oid_new(sheet, &sheet->direct));
		src = csch_attrib_src_c(fn, lineno, 0, NULL);
		csch_cobj_attrib_set(sheet, wn, CSCH_ATP_HARDWIRED, "role", "wire-net", src);
		htip_set(&rctx->wirenets, net_id, wn);
	}

	return wn;
}

void orcad_cgrp_set_name(csch_cgrp_t *wn, const char *name)
{
	csch_source_arg_t *src;
	int lineno = 0;
	const char *fn = "TODO71";

	src = csch_attrib_src_c(fn, lineno, 0, NULL);

	if (csch_attrib_get_str(&wn->attr, "name") == NULL)
		csch_attrib_set(&wn->attr, CSCH_ATP_USER_DEFAULT, "name", name, src, NULL);
	else
		csch_attrib_append(&wn->attr, CSCH_ATP_USER_DEFAULT, "alt_names", name, src);
}

void orcad_sheet_set_extents(io_orcad_rctx_t *rctx, long width, long height)
{
	csch_sheet_t *sheet = rctx->alien.sheet;

TODO("need to figure the correct value for this /10 - page size seems to be in a different unit than normal coords");
	width = width/10;
	height = height/10;

#if 0
	csch_source_arg_t *src;
	int lineno = 0;
	const char *fn = "TODO71";
	char tmp[128];

	/* alternative to drawing the box; this one wouldn't claim 0;0 tho */
	sprintf(tmp, "%ld", csch_alien_coord_x(&rctx->alien, height));
	src = csch_attrib_src_c(fn, lineno, 0, NULL);
	csch_attrib_set(&sheet->direct.attr, CSCH_ATP_USER_DEFAULT, "drawing_min_height", tmp, src, NULL);

	sprintf(tmp, "%ld", csch_alien_coord_x(&rctx->alien, width));
	src = csch_attrib_src_c(fn, lineno, 0, NULL);
	csch_attrib_set(&sheet->direct.attr, CSCH_ATP_USER_DEFAULT, "drawing_min_width", tmp, src, NULL);
#endif

	rctx->alien.oy = height;

	csch_alien_mkline(&rctx->alien, &sheet->direct, 0, 0, width, 0, "sheet-decor");
	csch_alien_mkline(&rctx->alien, &sheet->direct, 0, 0, 0, height, "sheet-decor");
	csch_alien_mkline(&rctx->alien, &sheet->direct, width, height, width, 0, "sheet-decor");
	csch_alien_mkline(&rctx->alien, &sheet->direct, width, height, 0, height, "sheet-decor");
}

/**** binary ****/

TODO("Do we need this func?")
#if 0
static int read_cb(void *ctx, void *dst, long len)
{
	long res = ucdf_fread((ucdf_file_t *)ctx, dst, len);
	return (res == len) ? 0 : -1;
}
#endif

ucdf_direntry_t *cdf_path(ucdf_ctx_t *ucdf, const char **path, ucdf_direntry_t *from)
{
	ucdf_direntry_t *de = from;

	if (path[0] == NULL)
		return de;

	if (de == NULL)
		de = ucdf->root->children;
	else
		de = de->children;

	for(; de != NULL; de = de->next)
		if (strcmp(de->name, path[0]) == 0)
			return cdf_path(ucdf, path+1, de);

	return NULL;
}

void *io_orcad_test_parse_bundled(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	io_orcad_rctx_t *ctx = calloc(sizeof(io_orcad_rctx_t), 1);
	const char *path[] = { "Views", NULL };
	const char *path2[] = { "Pages", NULL };
	ucdf_direntry_t *de;

	if (ucdf_open(&ctx->ucdf, fn) != 0) {
		rnd_message(RND_MSG_ERROR, "io_orcad: failed to open cdf\n");
		free(ctx);
		return NULL;
	}

	de = cdf_path(&ctx->ucdf, path, NULL);
	if (de == NULL) {
		rnd_message(RND_MSG_ERROR, "io_orcad: failed to find Views/ in cdf\n");
		free(ctx);
		return NULL;
	}

	if (de->children == NULL) {
		rnd_message(RND_MSG_ERROR, "io_orcad: failed to find sheets in Views/\n");
		free(ctx);
		return NULL;
	}

	de = cdf_path(&ctx->ucdf, path2, de->children);
	if (de->children == NULL) {
		rnd_message(RND_MSG_ERROR, "io_orcad: failed to find sheets in Views/*/Pages/\n");
		free(ctx);
		return NULL;
	}

	ctx->next_page = de->children;

	ctx->alien.fmt_prefix = "io_orcad";
	ctx->alien.flip_y = 1;
	ctx->fn = fn;



	return ctx;
}


int io_orcad_load_cache(io_orcad_rctx_t *ctx)
{
	ucdf_direntry_t *de;
	const char *path[] = { "Cache", NULL };
	int res;

	rnd_message(RND_MSG_INFO, "io_orcad: reading cache...\n");

	htsp_init(&ctx->syms, strhash, strkeyeq);
	ctx->cache_loaded = 1;


	de = cdf_path(&ctx->ucdf, path, NULL);
	if (de == NULL)
		return 0;

	if (ucdf_fopen(&ctx->ucdf, &ctx->fp, de) != 0)
		return -1;

	ctx->has_fp = 1;

	TODO("workaround for missing short seek");
	ctx->cheat_buf = malloc(de->size);
	ucdf_fread(&ctx->fp, ctx->cheat_buf, de->size);
	ctx->cheat_offs = 0;
	ctx->cheat_len = de->size;

	res = orcad_dump_cache(ctx);

	ctx->has_fp = 0;
	free(ctx->cheat_buf);


	return res;
}

int io_orcad_free_cache(io_orcad_rctx_t *ctx)
{
	htsp_entry_t *e;

	assert(ctx->cache_loaded);

	for(e = htsp_first(&ctx->syms); e != NULL; e = htsp_next(&ctx->syms, e)) {
		free(e->key);
		csch_cgrp_free(e->value);
	}

	htsp_uninit(&ctx->syms);
	ctx->cache_loaded = 0;

	return 0;
}


int io_orcad_load_sheet_bundled(void *cookie, FILE *f, const char *fn, csch_sheet_t *dst)
{
	io_orcad_rctx_t *ctx = cookie;
	ucdf_direntry_t *de = ctx->next_page;
	const char *pagename;
	int res;


	pagename = ctx->next_page->name;

	if (ctx->has_fp)
		ctx->has_fp = 0;

	if (!ctx->cache_loaded)
		io_orcad_load_cache(ctx);

	rnd_message(RND_MSG_INFO, "io_orcad: reading page %s...\n", pagename);

	ctx->alien.sheet = dst;
	ctx->alien.sheet->hidlib.loadname = rnd_strdup(pagename);

/*	ctx->alien.coord_factor = io_orcad_conf.plugins.io_orcad.coord_mult;*/
	ctx->alien.coord_factor = 400;
	htip_init(&ctx->wirenets, longhash, longkeyeq);
	csch_alien_sheet_setup(&ctx->alien, 1);

	if (ucdf_fopen(&ctx->ucdf, &ctx->fp, ctx->next_page) != 0)
		return -1;

	ctx->has_fp = 1;


	TODO("workaround for missing short seek");
	ctx->cheat_buf = malloc(de->size);
	ucdf_fread(&ctx->fp, ctx->cheat_buf, de->size);
	ctx->cheat_offs = 0;
	ctx->cheat_len = de->size;

	TODO("orcad read API changed!");
	/*res = orcad_dump(ctx);*/
	res = -1;

	csch_cgrp_update(ctx->alien.sheet, &ctx->alien.sheet->direct, 1);
	csch_alien_postproc_sheet(&ctx->alien);

	/* free per page caches */
	free(ctx->cheat_buf);
	htip_uninit(&ctx->wirenets);

/*	ctx->alien.sheet->hidlib.fullpath = rnd_strdup_printf("%s_%ld.rs", fn, ++ctx->sheetno);
	ctx->alien.sheet = NULL;*/

	if (res == 0) {
		ctx->next_page = ctx->next_page->next;
		if (ctx->next_page == NULL) {
			io_orcad_free_cache(ctx);
			return 1; /* no more pages */
		}
		return 0; /* proceed to read next page */
	}

	io_orcad_free_cache(ctx);
	return -1; /* error */
}

void io_orcad_end_bundled(void *cookie, const char *fn)
{
	io_orcad_rctx_t *ctx = cookie;

	if (ctx->has_fp)
		ctx->has_fp = 0;

	ucdf_close(&ctx->ucdf);
	free(ctx);
}
