/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  Copyright (C) 2017 Tibor 'Igor2' Palinkas
 *
 *  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., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#ifndef PCB_OBJ_PSTK_INLINES_H
#define PCB_OBJ_PSTK_INLINES_H

#include "board.h"
#include "data.h"
#include "obj_pstk.h"
#include "vtpadstack.h"
#include "layer.h"
#include "thermal.h"

typedef enum {
	PCB_BB_NONE, /* no drill */
	PCB_BB_THRU, /* all way thru */
	PCB_BB_BB,
	PCB_BB_INVALID
} pcb_bb_type_t;

/* Returns the ID of a proto within its parent's cache - don't assert on
   error; caller MUST check return value for PCB_PADSTACK_INVALID. 
    !  Rather use pcb_pstk_get_proto_id()  !
*/
RND_INLINE rnd_cardinal_t pcb_pstk_get_proto_id_failsafe(const pcb_pstk_proto_t *proto)
{
	pcb_data_t *data = proto->parent;
	if (data == NULL)
		return PCB_PADSTACK_INVALID;
	if ((proto >= data->ps_protos.array) && (proto < data->ps_protos.array + data->ps_protos.used))
		return proto - data->ps_protos.array;
	return PCB_PADSTACK_INVALID;
}

/* Returns the ID of a proto within its parent's cache */
RND_INLINE rnd_cardinal_t pcb_pstk_get_proto_id(const pcb_pstk_proto_t *proto)
{
	rnd_cardinal_t res = pcb_pstk_get_proto_id_failsafe(proto);
	if (res == PCB_PADSTACK_INVALID) {
		assert(!"padstack proto is not in its own parent!");
	}
	return res;
}


/* return the padstack prototype for a padstack reference - returns NULL if not found */
RND_INLINE pcb_pstk_proto_t *pcb_pstk_get_proto_(const pcb_data_t *data, rnd_cardinal_t proto)
{
	if (proto >= data->ps_protos.used)
		return NULL;
	if (data->ps_protos.array[proto].in_use == 0)
		return NULL;
	return data->ps_protos.array + proto;
}

/* return the padstack prototype for a padstack reference - returns NULL if not found */
RND_INLINE pcb_pstk_proto_t *pcb_pstk_get_proto(const pcb_pstk_t *ps)
{
	return pcb_pstk_get_proto_(ps->parent.data, ps->proto);
}

/* return the padstack transformed shape. Returns NULL if the proto or the
   tshape is not. */
RND_INLINE pcb_pstk_tshape_t *pcb_pstk_get_tshape_(const pcb_data_t *data, rnd_cardinal_t proto, int protoi)
{
	pcb_pstk_proto_t *pr = pcb_pstk_get_proto_(data, proto);
	if (protoi < 0)
		return NULL;
	if (pr == NULL)
		return NULL;
	if (protoi >= pr->tr.used)
		return NULL;
	return &pr->tr.array[protoi];
}

/* return the padstack transformed shape for a padstack reference considering its
   transformations - returns NULL if not found */
RND_INLINE pcb_pstk_tshape_t *pcb_pstk_get_tshape(pcb_pstk_t *ps)
{
	if (ps->protoi < 0) { /* need to update this */
		pcb_pstk_proto_t *pr = pcb_pstk_get_proto_(ps->parent.data, ps->proto);
		if (pr == NULL)
			return NULL;
		return pcb_pstk_make_tshape(ps->parent.data, pr, ps->rot, ps->xmirror, ps->smirror, &ps->protoi);
	}
	return pcb_pstk_get_tshape_(ps->parent.data, ps->proto, ps->protoi);
}

/* return the padstack *untransformed* prototype for a padstack reference - returns NULL if not found */
RND_INLINE pcb_pstk_tshape_t *pcb_pstk_get_untshape(pcb_pstk_t *ps)
{
	pcb_pstk_proto_t *pr = pcb_pstk_get_proto_(ps->parent.data, ps->proto);
	if (pr == NULL)
		return NULL;
	return &pr->tr.array[0];
}

/* return the type of drill and optionally fill in group IDs of drill ends ;
   if proto_out is not NULL, also load it with the proto */
RND_INLINE pcb_bb_type_t pcb_pstk_bbspan(pcb_board_t *pcb, const pcb_pstk_t *ps, rnd_layergrp_id_t *top, rnd_layergrp_id_t *bottom, pcb_pstk_proto_t **proto_out)
{
	pcb_bb_type_t res;
	int topi, boti;
	pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);

	if (proto_out != NULL)
		*proto_out = proto;

	if (proto == NULL)
		return PCB_BB_INVALID;

	/* most common case should be quick */
	if (!PCB_PSTK_PROTO_CUTS(proto)) {
		if (top != NULL) *top = -1;
		if (bottom != NULL) *bottom = -1;
		return PCB_BB_NONE;
	}

	if ((proto->htop == 0) && (proto->hbottom == 0)) {
		if ((top == NULL) && (bottom == NULL))
			return PCB_BB_THRU;
		res = PCB_BB_THRU;
	}
	else
		res = PCB_BB_BB;

	/* slower case: need to look up start and end */
	if (!pcb->LayerGroups.cache.copper_valid)
		pcb_layergrp_copper_cache_update(&pcb->LayerGroups);

	if (proto->htop >= 0) {
		/* positive: count from top copper down, bump at bottom */
		if (proto->htop < pcb->LayerGroups.cache.copper_len)
			topi = proto->htop;
		else
			topi = pcb->LayerGroups.cache.copper_len - 1;
	}
	else {
		/* negative: count from bottom copper up, bump at top */
		topi = pcb->LayerGroups.cache.copper_len - 1 - proto->htop;
		if (topi < 0)
			topi = 0;
	}

	if (proto->hbottom >= 0) {
		/* positive: count from bottom copper up, bump at top */
		if (proto->hbottom < pcb->LayerGroups.cache.copper_len-1)
			boti = pcb->LayerGroups.cache.copper_len-1-proto->hbottom;
		else
			boti = 0;
	}
	else {
		/* positive: count from top copper down, bump at bottom */
		boti = -proto->hbottom;
		if (boti > pcb->LayerGroups.cache.copper_len - 1)
			boti = pcb->LayerGroups.cache.copper_len - 1;
	}

	if (boti <= topi) {
		if (top != NULL) *top = -1;
		if (bottom != NULL) *bottom = -1;
		return PCB_BB_INVALID;
	}

	if ((topi < 0) || (boti < 0)) {
		/* special case: if there's no top or bottom copper, we do not know where to count from - do not support bbvias */
		return PCB_BB_THRU;
	}

	if (top != NULL) *top = pcb->LayerGroups.cache.copper[topi];
	if (bottom != NULL) *bottom = pcb->LayerGroups.cache.copper[boti];

	return res;
}

/* return whether a given padstack drills a given group
  (does not consider plating, only drill!) */
RND_INLINE rnd_bool_t pcb_pstk_bb_drills(pcb_board_t *pcb, const pcb_pstk_t *ps, rnd_layergrp_id_t grp, pcb_pstk_proto_t **proto_out)
{
	rnd_layergrp_id_t top, bot;
	pcb_bb_type_t res = pcb_pstk_bbspan(pcb, ps, &top, &bot, proto_out);
	switch(res) {
		case PCB_BB_THRU: return rnd_true;
		case PCB_BB_NONE: case PCB_BB_INVALID: return 0;
		case PCB_BB_BB: return (grp <= bot) && (grp >= top);
	}
	return rnd_false;
}

/* returns the shape the padstack has on the given layer group;
   WARNING: does not respect the NOSHAPE thermal, should NOT be
   called directly; use pcb_pstk_shape_*() instead. */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape(pcb_pstk_t *ps, pcb_layer_type_t lyt, pcb_layer_combining_t comb)
{
	int n;
	pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape(ps);
	if (ts == NULL)
		return NULL;

	lyt &= (PCB_LYT_ANYTHING | PCB_LYT_ANYWHERE);
	for(n = 0; n < ts->len; n++)
		if ((lyt == ts->shape[n].layer_mask) && (comb == ts->shape[n].comb))
			return ts->shape+n;

	return 0;
}

/* returns the shape the non-transformed padstack has on the given layer group;
   Useful for GUI dialogs only, for presenting the prototype (not the actual ps transformed reality)
   WARNING: does not respect the NOSHAPE thermal, should NOT be
   called directly; use pcb_pstk_shape_*() instead. */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_notransform(pcb_pstk_t *ps, pcb_layer_type_t lyt, pcb_layer_combining_t comb)
{
	int n;
	pcb_pstk_tshape_t *ts = pcb_pstk_get_untshape(ps);

	if (ts == NULL)
		return NULL;

	lyt &= (PCB_LYT_ANYTHING | PCB_LYT_ANYWHERE);
	for(n = 0; n < ts->len; n++)
		if ((lyt == ts->shape[n].layer_mask) && (comb == ts->shape[n].comb))
			return ts->shape+n;

	return 0;
}

/* If force is non-zero, return the shape even if a thermal says no-shape */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_at_(pcb_board_t *pcb, pcb_pstk_t *ps, pcb_layer_t *layer, int force)
{
	unsigned int lyt = pcb_layer_flags_(layer);
	pcb_layer_combining_t comb = layer->comb;

	if (lyt & PCB_LYT_COPPER) {
		rnd_layer_id_t lid;
		if (lyt & PCB_LYT_INTERN) {
			/* apply internal only if that layer has drill */
			if (!pcb_pstk_bb_drills(pcb, ps, pcb_layer_get_group_(layer), NULL))
				return NULL;
		}

		/* special case: if thermal says 'no shape' on this layer, omit the shape */
		if (!force) {
			layer = pcb_layer_get_real(layer);
			if ((layer != NULL) && (layer->parent.data != NULL)) {
				lid = pcb_layer_id(layer->parent.data, layer);
				if (lid < ps->thermals.used) {
					if ((ps->thermals.shape[lid] & PCB_THERMAL_ON) && ((ps->thermals.shape[lid] & 3) == PCB_THERMAL_NOSHAPE))
						return NULL;
				}
			}
		}
	}

	/* combining is always 0 for copper */
	if (lyt & PCB_LYT_COPPER)
		comb = 0;
	else
		comb = layer->comb;

	return pcb_pstk_shape(ps, lyt, comb);
}

RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_at(pcb_board_t *pcb, pcb_pstk_t *ps, pcb_layer_t *layer)
{
	return pcb_pstk_shape_at_(pcb, ps, layer, 0);
}


RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_gid(pcb_board_t *pcb, pcb_pstk_t *ps, rnd_layergrp_id_t gid, pcb_layer_combining_t comb, pcb_layergrp_t **grp_out)
{
	pcb_layergrp_t *grp = pcb_get_layergrp(pcb, gid);

	if (grp_out != NULL)
		*grp_out = grp;

	if ((grp == NULL) || (grp->len < 1))
		return NULL;

	if (grp->ltype & PCB_LYT_COPPER) {
		int n, nosh;

		/* blind/buried: intern layer has no shape if no hole */
		if (grp->ltype & PCB_LYT_INTERN) {
			/* apply internal only if that layer has drill */
			if (!pcb_pstk_bb_drills(pcb, ps, gid, NULL))
				return NULL;
		}

			/* if all layers of the group says no-shape, don't have a shape */
		for(n = 0, nosh = 0; n < grp->len; n++) {
			rnd_layer_id_t lid = grp->lid[n];
			if ((lid < ps->thermals.used) && (ps->thermals.shape[lid] & PCB_THERMAL_ON) && ((ps->thermals.shape[lid] & 3) == PCB_THERMAL_NOSHAPE))
				nosh++;
		}
		if (nosh == grp->len)
			return NULL;
	}

	/* normal procedure: go by group flags */
	return pcb_pstk_shape(ps, grp->ltype, comb);
}

/* Returns the mech shape (slot) if it affects grp */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_mech_gid(pcb_board_t *pcb, pcb_pstk_t *ps, rnd_layergrp_id_t grp)
{
	pcb_pstk_tshape_t *ts;
	pcb_pstk_proto_t *proto;

	if (!pcb_pstk_bb_drills(pcb, ps, grp, &proto))
		return NULL; /* span: blind/buried, not reaching grp */

	if (proto->mech_idx < 0)
		return NULL; /* no mech shape -> no slot */

	ts = pcb_pstk_get_tshape(ps);
	return ts->shape + proto->mech_idx;
}

/* Returns the mech shape (slot) if it affects layer */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_mech_at(pcb_board_t *pcb, pcb_pstk_t *ps, pcb_layer_t *layer)
{
	layer = pcb_layer_get_real(layer);
	if (layer == NULL)
		return NULL;
	return pcb_pstk_shape_mech_gid(pcb, ps, layer->meta.real.grp);
}

/* Returns a shape corresponding to the hole or the mech shape (slot) regardless
   of layers. 
   *DO NOT USE*, this call is designed for padstack internal code. Use
   pcb_pstk_shape_mech_or_hole_at() instead. (Hole shape is created in holetmp) */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_mech_or_hole_(pcb_pstk_t *ps, pcb_pstk_proto_t *proto, pcb_pstk_shape_t *holetmp)
{
	pcb_pstk_tshape_t *ts;

	/* hole has priority */
	if (proto->hdia > 0) {
		holetmp->shape = PCB_PSSH_CIRC;
		holetmp->data.circ.dia = proto->hdia;
		holetmp->data.circ.x = holetmp->data.circ.y = 0;
		return holetmp;
	}

	/* if there's no hole, check for slot */
	if (proto->mech_idx < 0)
		return NULL; /* no mech shape -> no slot */

	if (ps == NULL)
		ts = &proto->tr.array[0];
	else
		ts = pcb_pstk_get_tshape(ps);
	return ts->shape + proto->mech_idx;
}

/* Returns a shape corresponding to the hole or the mech shape (slot) if
   it affects layer; hole shape is created in holetmp */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_shape_mech_or_hole_at(pcb_board_t *pcb, pcb_pstk_t *ps, pcb_layer_t *layer, pcb_pstk_shape_t *holetmp)
{
	pcb_pstk_proto_t *proto;

	layer = pcb_layer_get_real(layer);
	if (layer == NULL)
		return NULL;

	if (!pcb_pstk_bb_drills(pcb, ps, pcb_layer_get_group_(layer), &proto))
		return NULL;

	return pcb_pstk_shape_mech_or_hole_(ps, proto, holetmp);
}

/* Return the shape of the hshadow, if it is not the same as the forbidden
   shape. The forbidden shape should be the shape that triggers the lookup.
   tmpshp should be a local temporary shape where the circular shape for a
   hole can be built. */
RND_INLINE pcb_pstk_shape_t *pcb_pstk_hshadow_shape(pcb_pstk_t *ps, pcb_pstk_shape_t *forbidden, pcb_pstk_shape_t *tmpshp)
{
	pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);
	pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape(ps);

	/* slot */
	if (proto->mech_idx >= 0) {
		pcb_pstk_shape_t *s = ts->shape + proto->mech_idx;
		if (s == forbidden) /* self-ref: hshadow in a mech layer type */
			return NULL;
		return s;
	}

	/* hole */
	tmpshp->shape = PCB_PSSH_CIRC;
	tmpshp->data.circ.x = 0;
	tmpshp->data.circ.y = 0;
	tmpshp->data.circ.dia = proto->hdia;
	return tmpshp;
}

#endif
