/* ************************************************************************
 * Copyright 2013 Advanced Micro Devices, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ************************************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <clBLAS.h>

#include <devinfo.h>
#include "clblas-internal.h"
#include "solution_seq.h"

//#define DEBUG_TBSV
static clblasUplo

getUpLo(CLBlasKargs *kargs)
{
    if (kargs->order == clblasRowMajor)
    {
        return kargs->uplo;
    }

    if (kargs->uplo == clblasUpper)
    {
        return clblasLower;
    }
    return clblasUpper;
}

static clblasStatus
orchestrateNonTransposeTBSV(CLBlasKargs *kargs, ListHead *trtriSeq, ListHead *gbmvSeq, cl_uint numEventsInWaitList,
                const cl_event *eventWaitList, cl_event *events)
{
    clblasStatus err;
    SolutionStep *trtri, *gbmv;
    size_t nLoops, i;
    cl_event *triangleEventArray, *rectangleEventArray;
    size_t TARGET_ROWS;
    bool gbmvExecute;
    size_t temp;

    ListNode *f = listNodeFirst(trtriSeq);
    trtri = container_of(f, node, SolutionStep);
    f = listNodeFirst(gbmvSeq);
    gbmv = container_of(f, node, SolutionStep);

    TARGET_ROWS = trtri->subdims->y;
    TARGET_ROWS = (TARGET_ROWS > kargs->K) ? kargs->K : TARGET_ROWS;
    TARGET_ROWS = (TARGET_ROWS == 0) ? 1 : TARGET_ROWS;

    trtri->numEventsInWaitList = numEventsInWaitList;
    trtri->eventWaitList = eventWaitList;

    if (kargs->N <= TARGET_ROWS)
    {
        trtri->event = events;
        trtri->args.startRow = 0;
        trtri->args.endRow = (cl_int)((kargs->N)-1);
        err = executeSolutionSeq(trtriSeq);
        return err;
    }

    //
    // Allocate Event Chain
    //
    nLoops = ((kargs->N) / TARGET_ROWS);
    if ((kargs->N % TARGET_ROWS))
    {
        nLoops++;
    }
    //
    // Allocate Event Arrays to order the orchestration
    //
    triangleEventArray = malloc(nLoops*sizeof(cl_event));
    rectangleEventArray = malloc(nLoops*sizeof(cl_event));
    if ((triangleEventArray == NULL) || (rectangleEventArray == NULL))
    {
        if (triangleEventArray)
        {
            free (triangleEventArray);
        }
        if (rectangleEventArray)
        {
            free (rectangleEventArray);
        }
        return clblasOutOfHostMemory;
    }
    //
    //  Solve 1 Triangle using Triangle Kernel Followed by Rectangle Kernels
    //
    trtri->event = &triangleEventArray[0];
    if (getUpLo(kargs) == clblasUpper)
    {
        trtri->args.startRow = (cl_int)((kargs->N) - TARGET_ROWS);
        trtri->args.endRow = (cl_int)((kargs->N)-1);
    } else {
        trtri->args.startRow = 0;
        trtri->args.endRow = (cl_int)(TARGET_ROWS-1);
    }
    err = executeSolutionSeq(trtriSeq);

/*#define GET_OFFA(offa, lda, r, c, k)\
if(r < k) \
offa = r * lda + col + k - r;\
else if (r == k) \
offa = r * lda + col;\
else\
offa = r * lda + col - (r - k);
*/
#define GET_OFFA_LOWER(offa, lda, row, col, kl) (offa) = ((row) * (lda)) + (col) + (kl) - (row);
#define GET_OFFA_UPPER(offa, lda, row, col) (offa) = ((row) * (lda)) + (col) - (row);

if (err == CL_SUCCESS)
    {
        //
        // Solve the Rectangles one by one
        //
        //nLoops = 1;
        for(i=1; i<nLoops; i++)
        {
            #ifdef DEBUG_TBSV
                printf("Calling gbmv-");
            #endif
            gbmv->numEventsInWaitList = 1;
            gbmv->eventWaitList = &triangleEventArray[i-1];
            gbmv->event = &rectangleEventArray[i-1];

            if (getUpLo(kargs) == clblasUpper)
            {
                gbmv->args.N = TARGET_ROWS;
                gbmv->args.M = ((trtri->args.startRow) >= (int)(kargs->K)) ? kargs->K : (size_t)trtri->args.startRow;
                gbmv->args.startRow = (trtri->args.startRow - gbmv->args.M);
                gbmv->args.endRow = (trtri->args.startRow - 1);
                gbmv->args.KU = (trtri->args.startRow >= (int)(kargs->K)) ? 0 : (kargs->K - trtri->args.startRow);
                gbmv->args.KL = gbmv->args.M - 1;

                GET_OFFA_UPPER(gbmv->args.offA, kargs->lda.matrix, gbmv->args.startRow, trtri->args.startRow);
                gbmv->args.offA -= gbmv->args.KL;
                gbmv->args.offA += kargs->offA;
                gbmv->args.offa = gbmv->args.offA;

                if(kargs->ldb.Vector < 0)
                {
                    gbmv->args.offBX = kargs->offBX + ((i-1) * TARGET_ROWS) * abs(kargs->ldb.Vector);
                    gbmv->args.offCY = kargs->offBX + ((i * TARGET_ROWS) ) * abs(kargs->ldb.Vector);
                }
                else
                {
                    gbmv->args.offBX = kargs->offBX + (trtri->args.startRow) * kargs->ldb.Vector;
                    gbmv->args.offCY = kargs->offBX + (gbmv->args.startRow) * kargs->ldb.Vector;
                }

            } else {
                gbmv->args.startRow = (cl_int)((i)*TARGET_ROWS);
                gbmv->args.endRow   = (cl_int)((((TARGET_ROWS*i) + kargs->K) > kargs->N) ? kargs->N : (TARGET_ROWS*i + kargs->K));
                gbmv->args.N = TARGET_ROWS;
                gbmv->args.M = (gbmv->args.endRow - gbmv->args.startRow);
                gbmv->args.KU = TARGET_ROWS - 1;
                gbmv->args.KL = ((trtri->args.startRow + kargs->K) < kargs->N) ? (kargs->K - TARGET_ROWS) : (kargs->N - trtri->args.startRow - 1 - TARGET_ROWS);

                GET_OFFA_LOWER(gbmv->args.offA, kargs->lda.matrix, gbmv->args.startRow, trtri->args.startRow, kargs->K);
                gbmv->args.offA -= gbmv->args.KL;
                gbmv->args.offA += kargs->offA;
                gbmv->args.offa = gbmv->args.offA;
                if(kargs->ldb.Vector < 0)
                {
                    gbmv->args.offBX = kargs->offBX + (kargs->N - gbmv->args.startRow) * abs(kargs->ldb.Vector);
                    gbmv->args.offCY = kargs->offBX + (kargs->N - (gbmv->args.startRow + gbmv->args.M) ) * abs(kargs->ldb.Vector);
                }
                else
                {
                    gbmv->args.offBX = kargs->offBX + (trtri->args.startRow) * kargs->ldb.Vector;
                    gbmv->args.offCY = kargs->offBX + (gbmv->args.startRow) * kargs->ldb.Vector;
                }

            }

            #ifdef DEBUG_TBSV
            printf("GBMV ITER %d, startRow %d, endRow %d, N %d, M %d , KU %d, KL %d, offBX %d, offA %d, offCY %d\n", i-1, gbmv->args.startRow, gbmv->args.endRow, \
                                            gbmv->args.N, gbmv->args.M, gbmv->args.KU, gbmv->args.KL, gbmv->args.offBX, gbmv->args.offA, gbmv->args.offCY);
            #endif
            // This is required when KL or KU is 0 for TBSV.
            gbmvExecute = (gbmv->args.M != 0);
            if(gbmvExecute)
            {
                if(kargs->order == clblasColumnMajor) //GBMV Swaps it back while assigning
                {
                    temp = gbmv->args.N;
                    gbmv->args.N = gbmv->args.M;
                    gbmv->args.M = temp;
                    temp = gbmv->args.KU;
                    gbmv->args.KU = gbmv->args.KL;
                    gbmv->args.KL = temp;
                }
                err = executeSolutionSeq(gbmvSeq);
            }

            if (err != CL_SUCCESS)
            {
                printf("TBSV: WARNING: GBMV LOOP: Breaking after %d iterations  !!!\n", (int)i);
                break;
            }

            #ifdef DEBUG_TBSV
                printf("Calling TBSV\n");
            #endif
            if (getUpLo(kargs) == clblasUpper)
            {
                trtri->args.startRow = (cl_int)(((int)trtri->args.startRow - (int)TARGET_ROWS) >= 0) ? (trtri->args.startRow - TARGET_ROWS) : 0;
                trtri->args.endRow = (cl_int)(gbmv->args.endRow);
            } else {
                trtri->args.startRow = gbmv->args.startRow;
                trtri->args.endRow = (cl_int)(((gbmv->args.startRow + TARGET_ROWS-1) < kargs->N) ? (gbmv->args.startRow + TARGET_ROWS-1) : kargs->N-1);
            }
            #ifdef DEBUG_TBSV
            printf("TRSV ITER %d, startRow %d , endRow %d\n", i, trtri->args.startRow, trtri->args.endRow);
            #endif
            trtri->event = &triangleEventArray[i];
            if (i == (nLoops-1))
            {
                //
                // TRTRI's last iteration must be tied to the "event" that the API
                // user will choose to wait on.
                //
                trtri->event = events;
            }
            //
            // For first iteration, TRTRI waits on what the API user has specified.
            // Subsequent iterations will wait on the previous iteration's rectangle
            // counterpart
            //

            trtri->numEventsInWaitList =1;
            if(gbmvExecute)
            {
                trtri->eventWaitList = &rectangleEventArray[i-1];
            }
            else //GBMV is not executed when KL or KU of the band in TBSV is 0.
            {
                trtri->eventWaitList = &triangleEventArray[i-1];
            }

            err = executeSolutionSeq(trtriSeq);
            if (err != CL_SUCCESS)
            {
                printf("TBSV: WARNING: TRSV LOOP: Breaking after %d iterations  !!!\n", (int)i);
                break;
            }

        }
    }

    free(triangleEventArray);
    free(rectangleEventArray);
    return err;
}

static clblasStatus
orchestrateTransposeTBSV(CLBlasKargs *kargs, ListHead *trtriSeq, ListHead *gbmvSeq, cl_uint numEventsInWaitList,
                const cl_event *eventWaitList, cl_event *events)
{
    clblasStatus err;
    SolutionStep *trtri, *gbmv;
    size_t nLoops, i;
    cl_event *triangleEventArray, *rectangleEventArray;
    size_t TARGET_ROWS;
    bool gbmvExecute;
    size_t temp;
    int TR_ER, N_SR, SD_ER;

    ListNode *f = listNodeFirst(trtriSeq);
    trtri = container_of(f, node, SolutionStep);
    f = listNodeFirst(gbmvSeq);
    gbmv = container_of(f, node, SolutionStep);

    TARGET_ROWS = trtri->subdims->y;
    TARGET_ROWS = (TARGET_ROWS > kargs->K) ? kargs->K : TARGET_ROWS;
    TARGET_ROWS = (TARGET_ROWS == 0) ? 1 : TARGET_ROWS;

    trtri->numEventsInWaitList = numEventsInWaitList;
    trtri->eventWaitList = eventWaitList;

    if (kargs->N <= TARGET_ROWS)
    {
        trtri->event = events;
        trtri->args.startRow = 0;
        trtri->args.endRow = (cl_int)((kargs->N));
        err = executeSolutionSeq(trtriSeq);
        return err;
    }

    //
    // Allocate Event Chain
    //
    nLoops = ((kargs->N) / TARGET_ROWS);
    if ((kargs->N % TARGET_ROWS))
    {
        nLoops++;
    }
    //
    // Allocate Event Arrays to order the orchestration
    //
    triangleEventArray = malloc(nLoops*sizeof(cl_event));
    rectangleEventArray = malloc(nLoops*sizeof(cl_event));
    if ((triangleEventArray == NULL) || (rectangleEventArray == NULL))
    {
        if (triangleEventArray)
        {
            free (triangleEventArray);
        }
        if (rectangleEventArray)
        {
            free (rectangleEventArray);
        }
        return clblasOutOfHostMemory;
    }
    //
    //  Solve 1 Triangle using Triangle Kernel Followed by Rectangle Kernels
    //
    trtri->event = &triangleEventArray[0];
    if (getUpLo(kargs) == clblasUpper)
    {
        trtri->args.startRow = 0;
        trtri->args.endRow = (cl_int)(TARGET_ROWS);
    } else {
        trtri->args.startRow = (cl_int)((kargs->N) - TARGET_ROWS);
        trtri->args.endRow = (cl_int)((kargs->N));
    }
    err = executeSolutionSeq(trtriSeq);

/*#define GET_OFFA(offa, lda, r, c, k)\
if(r < k) \
offa = r * lda + col + k - r;\
else if (r == k) \
offa = r * lda + col;\
else\
offa = r * lda + col - (r - k);
*/
#define GET_OFFA_LOWER(offa, lda, row, col, kl) (offa) = ((row) * (lda)) + (col) + (kl) - (row);
#define GET_OFFA_UPPER(offa, lda, row, col) (offa) = ((row) * (lda)) + (col) - (row);

    if (err == CL_SUCCESS)
    {
        //
        // Solve the Rectangles one by one
        //
        //nLoops = 1;
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))


        for(i=1; i<nLoops; i++)
        {
            #ifdef DEBUG_TBSV
                printf("Calling gbmv-");
            #endif
            gbmv->numEventsInWaitList = 1;
            gbmv->eventWaitList = &triangleEventArray[i-1];
            gbmv->event = &rectangleEventArray[i-1];

            if (getUpLo(kargs) == clblasUpper)
            {
                TR_ER = trtri->args.endRow - 1;
                gbmv->args.N = max(0, min(((int)kargs->K), ((int)kargs->N - 1 - TR_ER)));
                gbmv->args.M = TARGET_ROWS;
                gbmv->args.startRow = (trtri->args.startRow);
                gbmv->args.endRow = trtri->args.endRow;
                N_SR = max(0, min(((int)kargs->K), ((int)kargs->N - 1 - (int)trtri->args.startRow)));
                gbmv->args.KU = N_SR - TARGET_ROWS;
                gbmv->args.KL = gbmv->args.M - 1;

                GET_OFFA_UPPER(gbmv->args.offA, kargs->lda.matrix, gbmv->args.startRow, (gbmv->args.endRow));
                gbmv->args.offA -= gbmv->args.KL;
                gbmv->args.offA += kargs->offA;
                gbmv->args.offa = gbmv->args.offA;

                if(kargs->ldb.Vector < 0)
                {
                    gbmv->args.offBX = kargs->offBX + (kargs->N - (gbmv->args.endRow)) * abs(kargs->ldb.Vector);
                    gbmv->args.offCY = kargs->offBX + (kargs->N - (gbmv->args.endRow + gbmv->args.N) ) * abs(kargs->ldb.Vector);
                }
                else
                {
                    gbmv->args.offBX = kargs->offBX + (gbmv->args.startRow) * kargs->ldb.Vector;
                    gbmv->args.offCY = kargs->offBX + (gbmv->args.endRow) * kargs->ldb.Vector;
                }


            } else {

#define SUBDIAGS(r, k) ((r) <= (k)) ? (r) : (k);
                gbmv->args.startRow = trtri->args.startRow;
                gbmv->args.endRow   = trtri->args.endRow;

                gbmv->args.N = SUBDIAGS((int)trtri->args.startRow, (int)kargs->K);
                gbmv->args.M = TARGET_ROWS;
                gbmv->args.KU = gbmv->args.N - 1;
                SD_ER = SUBDIAGS((int)(trtri->args.endRow - 1), (int)kargs->K);
                gbmv->args.KL = SD_ER - gbmv->args.N;

                GET_OFFA_LOWER(gbmv->args.offA, kargs->lda.matrix, gbmv->args.startRow, (gbmv->args.startRow - gbmv->args.N), kargs->K);
                gbmv->args.offA -= gbmv->args.KL;
                gbmv->args.offA += kargs->offA;
                gbmv->args.offa = gbmv->args.offA;
                if(kargs->ldb.Vector < 0)
                {
                    gbmv->args.offBX = kargs->offBX + (kargs->N - gbmv->args.endRow) * abs(kargs->ldb.Vector);
                    gbmv->args.offCY = kargs->offBX + (kargs->N - (gbmv->args.startRow) ) * abs(kargs->ldb.Vector);
                }
                else
                {
                    gbmv->args.offBX = kargs->offBX + (gbmv->args.startRow) * kargs->ldb.Vector;
                    gbmv->args.offCY = kargs->offBX + (gbmv->args.startRow - gbmv->args.N) * kargs->ldb.Vector;
                }

            }
            #ifdef DEBUG_TBSV
            printf("GBMV ITER %d, startRow %d, endRow %d, N %d, M %d , KU %d, KL %d, offBX %d, offA %d, offCY %d\n", i-1, gbmv->args.startRow, gbmv->args.endRow, \
                                            gbmv->args.N, gbmv->args.M, gbmv->args.KU, gbmv->args.KL, gbmv->args.offBX, gbmv->args.offA, gbmv->args.offCY);
            #endif
            // This is required when KL or KU is 0 for TBSV.
            gbmvExecute = (gbmv->args.N != 0);
            if(gbmvExecute)
            {
                if(kargs->order == clblasColumnMajor) //GBMV Swaps it back while assigning
                {
                    temp = gbmv->args.N;
                    gbmv->args.N = gbmv->args.M;
                    gbmv->args.M = temp;
                    temp = gbmv->args.KU;
                    gbmv->args.KU = gbmv->args.KL;
                    gbmv->args.KL = temp;
                }
                err = executeSolutionSeq(gbmvSeq);
            }

            if (err != CL_SUCCESS)
            {
                printf("TBSV: WARNING: GBMV LOOP: Breaking after %d iterations  !!!\n", (int)i);
                break;
            }

            #ifdef DEBUG_TBSV
                printf("Calling TBSV\n");
            #endif
            if (getUpLo(kargs) == clblasUpper)
            {
                trtri->args.startRow = (cl_int)(trtri->args.endRow);
                trtri->args.endRow = (cl_int)(((int)trtri->args.endRow + (int)TARGET_ROWS) <= (int)kargs->N) ? (trtri->args.endRow + TARGET_ROWS) : kargs->N;
            } else {
                trtri->args.endRow = trtri->args.startRow;
                trtri->args.startRow = (cl_int)((((int)trtri->args.startRow - (int)TARGET_ROWS) > 0) ? (trtri->args.startRow - TARGET_ROWS) : 0);
            }
            #ifdef DEBUG_TBSV
            printf("TRSV ITER %d, startRow %d , endRow %d\n", i, trtri->args.startRow, trtri->args.endRow);
            #endif
            trtri->event = &triangleEventArray[i];
            if (i == (nLoops-1))
            {
                //
                // TRTRI's last iteration must be tied to the "event" that the API
                // user will choose to wait on.
                //
                trtri->event = events;
            }
            //
            // For first iteration, TRTRI waits on what the API user has specified.
            // Subsequent iterations will wait on the previous iteration's rectangle
            // counterpart
            //

            trtri->numEventsInWaitList =1;
            if(gbmvExecute)
            {
                trtri->eventWaitList = &rectangleEventArray[i-1];
            }
            else //GBMV is not executed when KL or KU of the band in TBSV is 0.
            {
                trtri->eventWaitList = &triangleEventArray[i-1];
            }

            err = executeSolutionSeq(trtriSeq);
            if (err != CL_SUCCESS)
            {
                printf("TBSV: WARNING: TRSV LOOP: Breaking after %d iterations  !!!\n", (int)i);
                break;
            }

        }
    }

    free(triangleEventArray);
    free(rectangleEventArray);
    return err;
}

static clblasStatus
orchestrateTBSV(CLBlasKargs *kargs, ListHead *trtriSeq, ListHead *gbmvSeq, cl_uint numEventsInWaitList,
                const cl_event *eventWaitList, cl_event *events)
{
    clblasStatus err = clblasNotImplemented;

    if  (   ((kargs->order == clblasRowMajor) && (kargs->transA == clblasNoTrans)) ||
            ((kargs->order == clblasColumnMajor) && (kargs->transA != clblasNoTrans))
        )
    {
        #ifdef DEBUG_TBSV
        printf("Orchestrating the NO-Transpose case..\n");
        #endif
        err = orchestrateNonTransposeTBSV(kargs, trtriSeq, gbmvSeq, numEventsInWaitList, eventWaitList, events);
    } else {
        #ifdef DEBUG_TRSV
        printf("Orchestrating the Transpose case..\n");
        #endif
        err = orchestrateTransposeTBSV(kargs, trtriSeq, gbmvSeq, numEventsInWaitList, eventWaitList, events);
    }

    return err;
}


clblasStatus
doTbsv(
	CLBlasKargs *kargs,
    clblasOrder order,
    clblasUplo uplo,
    clblasTranspose trans,
    clblasDiag diag,
    size_t N,
    size_t K,
    const cl_mem A,
    size_t offa,
    size_t lda,
    cl_mem x,
    size_t offx,
    int incx,
    cl_uint numCommandQueues,
    cl_command_queue *commandQueues,
    cl_uint numEventsInWaitList,
    const cl_event *eventWaitList,
    cl_event *events)
{
    cl_int err = clblasNotImplemented;
    ListHead seq;
	CLBlasKargs gbmvKargs;
	ListHead gbmvSeq;
	//cl_context c;
    clblasStatus retCode = clblasSuccess;

    if (!clblasInitialized) {
        return clblasNotInitialized;
    }

    /* Validate arguments */

    retCode = checkMemObjects(A, x, (cl_mem) NULL, false, A_MAT_ERRSET, X_VEC_ERRSET, END_ERRSET);
    if (retCode != clblasSuccess) {
		#ifdef DEBUG_TBSV
		printf("Invalid mem object..\n");
		#endif
        return retCode;
    }

	/*
	 * PENDING:
 	 * checkMatrixSizes() does not account for "offa" argument.
 	 * Need to pass "offa" when "checkMatrixSizes()" is changed.
	 */
    retCode = checkBandedMatrixSizes(kargs->dtype, order, trans, N, N, K, 0, A, offa, lda, A_MAT_ERRSET );
    if (retCode != clblasSuccess) {
		#ifdef DEBUG_TBSV
		printf("Invalid Size for A\n");
		#endif
        return retCode;
    }
    retCode = checkVectorSizes(kargs->dtype, N, x, offx, incx, X_VEC_ERRSET );
    if (retCode != clblasSuccess) {
		#ifdef DEBUG_TBSV
		printf("Invalid Size for X\n");
		#endif
        return retCode;
    }

	#ifdef DEBUG_TBSV
	printf("DoTbsv being called...\n");
	#endif

	if ((commandQueues == NULL) || (numCommandQueues == 0))
	{
		return clblasInvalidValue;
	}

	if ((numEventsInWaitList !=0) && (eventWaitList == NULL))
	{
		return clblasInvalidEventWaitList;
	}

    if (commandQueues[0] == NULL)
	{
		return clblasInvalidCommandQueue;
	}


	numCommandQueues = 1; // NOTE: Hard-coding the number of command queues to 1i
    kargs->order = order;
    kargs->uplo = uplo;
    kargs->transA = trans;
	kargs->diag = diag;
    kargs->M = N; // store Original N
    kargs->N = N;
    kargs->K = K;
    kargs->A = A;
    kargs->lda.matrix = lda;
    kargs->B = x;
    kargs->ldb.Vector = incx;
    kargs->offBX = offx;
	kargs->offa = offa;
	kargs->offA = offa;
    kargs->C = x;
    kargs->offCY = offx;
    kargs->ldc.Vector = incx;
    kargs->startRow = 0;

    if(trans == clblasNoTrans)
    {
        kargs->endRow = (order == clblasRowMajor) ?  N-1 : N;
    }
    else
    {
        kargs->endRow = (order == clblasRowMajor) ?  N : N-1;
    }

    memcpy(&gbmvKargs, kargs, sizeof(CLBlasKargs));
    gbmvKargs.pigFuncID = CLBLAS_GBMV;

    listInitHead(&seq);
    listInitHead(&gbmvSeq);

    err = makeSolutionSeq(CLBLAS_TRSV, kargs, numCommandQueues, commandQueues,
                          numEventsInWaitList, eventWaitList, events, &seq);

    if (err == CL_SUCCESS) {

        err = makeSolutionSeq(CLBLAS_GBMV, &gbmvKargs, numCommandQueues, commandQueues,
                                0, NULL, NULL, &gbmvSeq);
        if (err == CL_SUCCESS)
        {
            err = orchestrateTBSV(kargs, &seq, &gbmvSeq, numEventsInWaitList, eventWaitList, events);
        }
    }

    freeSolutionSeq(&seq);
    freeSolutionSeq(&gbmvSeq);
    return (clblasStatus)err;
}

clblasStatus
clblasStbsv(
clblasOrder order,
    clblasUplo uplo,
    clblasTranspose trans,
    clblasDiag diag,
    size_t N,
    size_t K,
    const cl_mem A,
    size_t offa,
    size_t lda,
    cl_mem X,
    size_t offx,
    int incx,
    cl_uint numCommandQueues,
    cl_command_queue *commandQueues,
    cl_uint numEventsInWaitList,
    const cl_event *eventWaitList,
    cl_event *events)
{
    CLBlasKargs kargs;
    #ifdef DEBUG_TBSV
    printf("STBSV Called\n");
    #endif

    memset(&kargs, 0, sizeof(kargs));
    kargs.dtype = TYPE_FLOAT;
    kargs.pigFuncID = CLBLAS_TBSV;
    kargs.alpha.argFloat = -1.0;
    kargs.beta.argFloat = 1.0;

    return doTbsv(&kargs, order, uplo, trans, diag, N, K, A, offa, lda, X, offx, incx, numCommandQueues, commandQueues,
                   numEventsInWaitList, eventWaitList, events);
}

clblasStatus
clblasDtbsv(
    clblasOrder order,
    clblasUplo uplo,
    clblasTranspose trans,
    clblasDiag diag,
    size_t N,
    size_t K,
    const cl_mem A,
    size_t offa,
    size_t lda,
    cl_mem X,
    size_t offx,
    int incx,
    cl_uint numCommandQueues,
    cl_command_queue *commandQueues,
    cl_uint numEventsInWaitList,
    const cl_event *eventWaitList,
    cl_event *events)
{
    CLBlasKargs kargs;
    #ifdef DEBUG_TBSV
    printf("DTBSV Called\n");
    #endif

    memset(&kargs, 0, sizeof(kargs));
    kargs.dtype = TYPE_DOUBLE;
    kargs.pigFuncID = CLBLAS_TBSV;
    kargs.alpha.argDouble = -1.0;
    kargs.beta.argDouble = 1.0;

    return doTbsv(&kargs, order, uplo, trans, diag, N, K, A, offa, lda, X, offx, incx, numCommandQueues, commandQueues,
                   numEventsInWaitList, eventWaitList, events);
}

clblasStatus
clblasCtbsv(
    clblasOrder order,
    clblasUplo uplo,
    clblasTranspose trans,
    clblasDiag diag,
    size_t N,
    size_t K,
    const cl_mem A,
    size_t offa,
    size_t lda,
    cl_mem X,
    size_t offx,
    int incx,
    cl_uint numCommandQueues,
    cl_command_queue *commandQueues,
    cl_uint numEventsInWaitList,
    const cl_event *eventWaitList,
    cl_event *events)
{
    CLBlasKargs kargs;
    FloatComplex alpha, beta;
    #ifdef DEBUG_TBSV
    printf("CTBSV Called\n");
    #endif

    memset(&kargs, 0, sizeof(kargs));
    kargs.dtype = TYPE_COMPLEX_FLOAT;
    kargs.pigFuncID = CLBLAS_TBSV;

    CREAL(alpha) = -1.0;
    CIMAG(alpha) = 0.0;
    CREAL(beta) = 1.0;
    CIMAG(beta) = 0.0;

    kargs.alpha.argFloatComplex = alpha;
    kargs.beta.argFloatComplex = beta;

    return doTbsv(&kargs, order, uplo, trans, diag, N, K, A, offa, lda, X, offx, incx, numCommandQueues, commandQueues,
                   numEventsInWaitList, eventWaitList, events);
}

clblasStatus
clblasZtbsv(
    clblasOrder order,
    clblasUplo uplo,
    clblasTranspose trans,
    clblasDiag diag,
    size_t N,
    size_t K,
    const cl_mem A,
    size_t offa,
    size_t lda,
    cl_mem X,
    size_t offx,
    int incx,
    cl_uint numCommandQueues,
    cl_command_queue *commandQueues,
    cl_uint numEventsInWaitList,
    const cl_event *eventWaitList,
    cl_event *events)
{
    CLBlasKargs kargs;
    DoubleComplex alpha, beta;
    #ifdef DEBUG_TBSV
    printf("ZTBSV Called\n");
    #endif

    memset(&kargs, 0, sizeof(kargs));
    kargs.dtype = TYPE_COMPLEX_DOUBLE;
    kargs.pigFuncID = CLBLAS_TBSV;

    CREAL(alpha) = -1.0;
    CIMAG(alpha) = 0.0;
    CREAL(beta) = 1.0;
    CIMAG(beta) = 0.0;

    kargs.alpha.argDoubleComplex = alpha;
    kargs.beta.argDoubleComplex = beta;

    return doTbsv(&kargs, order, uplo, trans, diag, N, K, A, offa, lda, X, offx, incx, numCommandQueues, commandQueues,
                   numEventsInWaitList, eventWaitList, events);
}

