/* 
 * mplci.c
 *
 * Author       Karsten Keil <kkeil@linux-pingi.de>
 *
 * Copyright 2011  by Karsten Keil <kkeil@linux-pingi.de>
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
 * version 2.1 as published by the Free Software Foundation.
 *
 * This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
 *
 */

#include <sched.h>
#include "m_capi.h"
#include "mc_buffer.h"
#include <mISDN/q931.h>

struct mPLCI *new_mPLCI(struct pController *pc, unsigned int pid)
{
	struct mPLCI *plci;
	int ret;

	plci = calloc(1, sizeof(*plci));
	if (!plci) {
		eprint("Controller:%x PID:%x no memory for PLCI\n", pc->mNr, pid);
		return NULL;
	}
	ret = init_cobj_registered(&plci->cobj, &pc->cobjPLCI, Cot_PLCI, 0x01ff);
	if (ret) {
		eprint("Controller:%x PID:%x Error on init - no IDs left\n", pc->mNr, pid);
		free(plci);
		plci = NULL;
	} else {
		plci->cobj.id2 = pid;
		plci->pc = pc;
	}
	return plci;
}

void dump_controller_plci(struct pController *pc)
{
	struct mCAPIobj *co;
	struct mPLCI *plci;

	pthread_rwlock_rdlock(&pc->cobjPLCI.lock);
	co = pc->cobjPLCI.listhead;
	while (co) {
		plci = container_of(co, struct mPLCI, cobj);
		iprint("%s refcnt:%d number lPLCI:%d %s%s\n", CAPIobjIDstr(co), co->refcnt, co->itemcnt,
			plci->alerting ? "alerting " : "", plci->outgoing ? "outgoing" : "incoming");
		pthread_rwlock_rdlock(&co->lock);
		if (co->listhead)
			dump_Lplcis(container_of(co->listhead, struct lPLCI, cobj));
		pthread_rwlock_unlock(&co->lock);
		co = co->next;
	}
	pthread_rwlock_unlock(&pc->cobjPLCI.lock);
}

void Free_PLCI(struct mCAPIobj *co)
{
	struct mPLCI *plci = container_of(co, struct mPLCI, cobj);

	if (!co->cleaned) {
		delist_cobj(co);
		co->cleaned = 1;
	}
	plci->pc = NULL;
	if (co->parent) {
		put_cobj(co->parent);
		co->parent = NULL;
	}
	dprint(MIDEBUG_PLCI, "%s: freeing done\n", CAPIobjIDstr(co));
	free_capiobject(co, plci);
}

static void cleanup_mPLCI(struct mPLCI *plci)
{
	struct pController *pc = plci->pc;
	struct mCAPIobj *co;
	struct lPLCI *lp;

	plci->cobj.cleaned = 1;
	co = get_next_cobj(&plci->cobj, NULL);
	while (co) {
		lp = container_of(co, struct lPLCI, cobj);
		cleanup_lPLCI(lp);
		co = get_next_cobj(&plci->cobj, co);
	}
	if (plci->cobj.itemcnt) {
		wprint("%s: lPLCI count %d not zero\n", CAPIobjIDstr(&plci->cobj), plci->cobj.itemcnt);
	}
	if (pc) {
		plci->pc = NULL;
		delist_cobj(&plci->cobj);
	} else
		eprint("%s: no pcontroller assigned\n", CAPIobjIDstr(&plci->cobj));
}

void plciDetachlPLCI(struct lPLCI *lp)
{
	struct mPLCI *p;
	int mt;
	struct l3_msg *l3m;

	p = p4lPLCI(lp);
	if (!p) {
		eprint("%s: detach no PLCI\n", CAPIobjIDstr(&lp->cobj));
		return;
	}
	if (lp->rel_req) {
		/* need to store cause for final answer */
		if (lp->cause_loc == CAUSE_LOC_USER) {
			dprint(MIDEBUG_PLCI, "%s: set final cause plci:#%d lplci:#%d\n",
				CAPIobjIDstr(&lp->cobj), p->cause, lp->cause);
			if (p->cause <= 0) {
				p->cause = lp->cause;
			} else if (lp->cause < p->cause) {
				/* for now we prefer lower values, maybe need changed */
				p->cause = lp->cause;
			}
			p->cause_loc = CAUSE_LOC_USER;
		} else
			wprint("%s: cause got owerwritten loc:#%d cause #%d - not stored\n",
				CAPIobjIDstr(&lp->cobj), lp->cause_loc, lp->cause);

	}
	delist_cobj(&lp->cobj);
	if (p->cobj.itemcnt == 0) {
		if (p->cause > 0) {
			dprint(MIDEBUG_PLCI, "%s: last lPLCI gone clear call cause #%d\n",
				CAPIobjIDstr(&p->cobj), p->cause);
			l3m = alloc_l3_msg();
			if (!l3m) {
				eprint("%s: disconnect not send no l3m\n", CAPIobjIDstr(&p->cobj));
			} else {
				if (p->alerting)
					mt = MT_DISCONNECT;
				else
					mt = MT_RELEASE_COMPLETE;
				mi_encode_cause(l3m, p->cause, p->cause_loc, 0, NULL);
				plciL4L3(p, mt, l3m);
			}
		}
		dprint(MIDEBUG_PLCI, "%s: All lPLCIs are gone remove PLCI now\n", CAPIobjIDstr(&p->cobj));
		cleanup_mPLCI(p);
	}
}

static void plciHandleSetupInd(struct mPLCI *plci, int pr, struct mc_buf *mc)
{
	uint32_t CIPmask, cipm;
	struct pController *pc;
	struct mCAPIobj *co;
	struct lController *lc;
	struct lPLCI *lp;
	uint8_t found = 0;
	int cause = CAUSE_INCOMPATIBLE_DEST;
	int ret, *fds, *cur;

	if (!mc || !mc->l3m) {
		eprint("%s: SETUP without message\n", CAPIobjIDstr(&plci->cobj));
		return;
	}
	CIPmask = q931CIPMask(mc);
	pc = plci->pc;
	dprint(MIDEBUG_PLCI, "%s: check CIPMask(%08x) with controller CIPmask %08x chanIE:%s\n",
		CAPIobjIDstr(&plci->cobj), CIPmask, pc->CIPmask, mc->l3m->channel_id ? "yes" : "no");
	if (CIPmask & pc->CIPmask) {
		/* at least one Application is listen for this service */
		co = get_next_cobj(&pc->cobjLC, NULL);
		while (co) {
			lc = container_of(co, struct lController, cobj);
			cipm  = lc->CIPmask & CIPmask;
			if ((lc->CIPmask & CIPmask) || (lc->CIPmask & 1)) {
				ret = lPLCICreate(&lp, lc, plci, cipm);
				if (ret == 0) {
					found++; 
					put_cobj(&lp->cobj);
				} else {
					wprint("%s: cannot create lPLCI\n", CAPIobjIDstr(&plci->cobj));
				}
			}
			co = get_next_cobj(&pc->cobjLC, co);
		}
		if (plci->cobj.itemcnt) {
			/* at least one lplci was created */
			fds = calloc(found, sizeof(int));
			if (!fds)
				eprint("%s: cannot allocate fds buffer for %d fd - will crash soon\n", CAPIobjIDstr(&plci->cobj), found);
			cur = fds;
			pthread_rwlock_rdlock(&plci->cobj.lock);
			co = plci->cobj.listhead;
			while (co) {
				lp = container_of(co, struct lPLCI, cobj);
				*cur++ = lp->Appl->fd;
				co = co->next;
			}
			pthread_rwlock_unlock(&plci->cobj.lock);
			/* disable answers until all controller are informed */
			send_master_control(MICD_CTRL_DISABLE_POLL, found * sizeof(int), fds);
			sched_yield(); /* make sure that the disable could be processed */
			co = get_next_cobj(&plci->cobj, NULL);
			while (co) {
				lp = container_of(co, struct lPLCI, cobj);
				lPLCI_l3l4(lp, pr, mc);
				dprint(MIDEBUG_PLCI, "%s: SETUP %s\n",
					CAPIobjIDstr(&lp->cobj), lp->ignored ? "ignored - no B-channel" : "delivered");
				if (lp->ignored)
					cleanup_lPLCI(lp);
				co = get_next_cobj(&plci->cobj, co);
			}
			/* Now enable answers again */
			send_master_control(MICD_CTRL_ENABLE_POLL, found * sizeof(int), fds);
			free(fds);
		}
	}
	if (found == 0) {
		struct l3_msg *l3m;

		l3m = alloc_l3_msg();
		if (l3m) {
			dprint(MIDEBUG_PLCI, "%s: send %s cause #%d (0x%02x) to layer3\n",
				CAPIobjIDstr(&plci->cobj), _mi_msg_type2str(MT_RELEASE_COMPLETE), cause, cause);
			if (!mi_encode_cause(l3m, cause, CAUSE_LOC_USER, 0, NULL)) {
				ret = pc->l3->to_layer3(pc->l3, MT_RELEASE_COMPLETE, plci->cobj.id2, l3m);
				if (ret) {
					wprint("%s: Error %d -  %s on sending %s to pid %x\n", CAPIobjIDstr(&plci->cobj), ret,
						strerror(-ret), _mi_msg_type2str(MT_RELEASE_COMPLETE), plci->cobj.id2);
					free_l3_msg(l3m);
				}
			}
		} else
			eprint("%s: cannot allocate l3 message plci\n", CAPIobjIDstr(&plci->cobj));

		cleanup_mPLCI(plci);
	}
}

int plci_l3l4(struct mPLCI *plci, int pr, struct l3_msg *l3m)
{
	struct mc_buf *mc;
	struct mCAPIobj *co;

	mc = alloc_mc_buf();
	if (!mc) {
		wprint("%s: Cannot allocate mc_buf for %s\n", CAPIobjIDstr(&plci->cobj), _mi_msg_type2str(pr));
		return -ENOMEM;
	}
	mc->l3m = l3m;
	switch (pr) {
	case MT_SETUP:
		plciHandleSetupInd(plci, pr, mc);
		break;
	default:
		co = get_next_cobj(&plci->cobj, NULL);
		while (co) {
			lPLCI_l3l4(container_of(co, struct lPLCI, cobj), pr, mc);
			co = get_next_cobj(&plci->cobj, co);
		}
		break;
	}
	free_mc_buf(mc);
	return 0;
}

int mPLCISendMessage(struct lController *lc, struct mc_buf *mc)
{
	struct mPLCI *plci;
	struct lPLCI *lp;
	int ret;
	struct pController *pc;

	pc = p4lController(lc);
	switch (mc->cmsg.Command) {
	case CAPI_CONNECT:
		plci = new_mPLCI(pc, 0);
		if (plci) {
			ret = lPLCICreate(&lp, lc, plci, 0);
			if (!ret) {
				ret = lPLCISendMessage(lp, mc);
				put_cobj(&lp->cobj);
			} else {
				wprint("%s: cannot create lPLCI Appl-%03d", CAPIobjIDstr(&plci->cobj), lc->cobj.id2);
				ret = CapiMsgOSResourceErr;
			}
			put_cobj(&plci->cobj);
		} else {
			wprint("Cannot create PLCI for controller %d\n", pc->profile.ncontroller);
			ret = CapiMsgOSResourceErr;
		}
		break;
	default:
		wprint("Message %s not handled yet\n", capi20_cmd2str(mc->cmsg.Command, mc->cmsg.Subcommand));
		ret = CapiMessageNotSupportedInCurrentState;
		break;
	}
	return ret;
}

struct lPLCI *get_lPLCI4Id(struct mPLCI *plci, uint16_t appId)
{
	struct mCAPIobj *co;

	if (!plci)
		return NULL;
	co = get_next_cobj(&plci->cobj, NULL);
	while (co) {
		if (appId == co->id2)
			break;
		co = get_next_cobj(&plci->cobj, co);
	}
	return co ? container_of(co, struct lPLCI, cobj) : NULL;
}

struct mPLCI *getPLCI4pid(struct pController *pc, int pid)
{
	struct mCAPIobj *co;

	co = get_next_cobj(&pc->cobjPLCI, NULL);
	while (co) {
		if (co->id2 == pid)
			break;
		co = get_next_cobj(&pc->cobjPLCI, co);
	}
	return co ? container_of(co, struct mPLCI, cobj) : NULL;
}

struct mPLCI *getPLCI4Id(struct pController *pc, uint32_t id)
{
	struct mCAPIobj *co;

	co = get_next_cobj(&pc->cobjPLCI, NULL);
	while (co) {
		if (co->id == id)
			break;
		co = get_next_cobj(&pc->cobjPLCI, co);
	}
	return co ? container_of(co, struct mPLCI, cobj) : NULL;
}

int plciL4L3(struct mPLCI *plci, int mt, struct l3_msg *l3m)
{
	int ret;

	ret = plci->pc->l3->to_layer3(plci->pc->l3, mt, plci->cobj.id2, l3m);
	if (ret < 0) {
		wprint("%s: Error sending %s to controller %d pid %x %s msg\n", CAPIobjIDstr(&plci->cobj), _mi_msg_type2str(mt),
			plci->pc->profile.ncontroller, plci->cobj.id2, l3m ? "with" : "no");
		if (l3m)
			free_l3_msg(l3m);
	}
	dprint(MIDEBUG_PLCI, "%s: Sending %s to layer3 %s msg\n",
		CAPIobjIDstr(&plci->cobj), _mi_msg_type2str(mt), l3m ? "with" : "no");
	return ret;
}

unsigned int plci_new_pid(struct mPLCI *plci)
{
	return request_new_pid(plci->pc->l3);
}

void release_lController(struct lController *lc)
{
	struct mCAPIobj *cop, *colp;
	struct lPLCI *lp;
	struct pController *pc = p4lController(lc);

	if (pc) {
		cop = get_next_cobj(&pc->cobjPLCI, NULL);
		while (cop) {
			colp = get_next_cobj(cop, NULL);
			while (colp) {
				lp = container_of(colp, struct lPLCI, cobj);
				if (lc == lp->lc) {
					if (!lp->rel_req) {
						dprint(MIDEBUG_PLCI, "%s do release\n", CAPIobjIDstr(colp));
						lPLCIRelease(lp);
					} else
						dprint(MIDEBUG_PLCI, "%s: release already done\n", CAPIobjIDstr(colp));
				}
				colp = get_next_cobj(cop, colp);
			}
			cop = get_next_cobj(&pc->cobjPLCI, cop);
		}
	}
}
