/*
 * Copyright (C) 2010-2021 Arm Limited or its affiliates. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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
 *
 * 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.
 */

/* ----------------------------------------------------------------------
 * Project:      Arm-2D Library
 * Title:        arm-2d.c
 * Description:  Basic Tile operations
 *
 * $Date:        01. December 2020
 * $Revision:    V.0.5.0
 *
 * Target Processor:  Cortex-M cores
 *
 * -------------------------------------------------------------------- */


/*============================ INCLUDES ======================================*/
#define __ARM_2D_IMPL__

#include "arm_2d.h"
#include "__arm_2d_impl.h"

#ifdef   __cplusplus
extern "C" {
#endif

#if defined(__clang__)
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
#   pragma clang diagnostic ignored "-Wcast-qual"
#   pragma clang diagnostic ignored "-Wcast-align"
#   pragma clang diagnostic ignored "-Wextra-semi-stmt"
#   pragma clang diagnostic ignored "-Wsign-conversion"
#   pragma clang diagnostic ignored "-Wunused-function"
#   pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"
#   pragma clang diagnostic ignored "-Wdouble-promotion"
#   pragma clang diagnostic ignored "-Wunused-parameter"
#   pragma clang diagnostic ignored "-Wimplicit-float-conversion"
#   pragma clang diagnostic ignored "-Wimplicit-int-conversion"
#   pragma clang diagnostic ignored "-Wtautological-pointer-compare"
#   pragma clang diagnostic ignored "-Wsign-compare"
#   pragma clang diagnostic ignored "-Wmissing-prototypes"
#   pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#   pragma clang diagnostic ignored "-Wgnu-statement-expression"
#   pragma clang diagnostic ignored "-Wswitch-enum"
#   pragma clang diagnostic ignored "-Wswitch"
#elif defined(__IS_COMPILER_ARM_COMPILER_5__)
#   pragma diag_suppress 174,177,188,68,513
#elif defined(__IS_COMPILER_GCC__)
#   pragma GCC diagnostic push
#   pragma GCC diagnostic ignored "-Wenum-compare"
#   pragma GCC diagnostic ignored "-Wpedantic"
#   pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif

/*============================ MACROS ========================================*/
#ifndef __ARM_2D_DEFAULT_SUB_TASK_POOL_SIZE
#   define __ARM_2D_DEFAULT_SUB_TASK_POOL_SIZE        3
#endif

/*============================ MACROFIED FUNCTIONS ===========================*/
/*============================ TYPES =========================================*/
/*============================ GLOBAL VARIABLES ==============================*/

__WEAK
arm_2d_runtime_feature_t ARM_2D_RUNTIME_FEATURE = {
    .TREAT_OUT_OF_RANGE_AS_COMPLETE         = 1,
    .HAS_DEDICATED_THREAD_FOR_2D_TASK       = 0,
};
/*============================ PROTOTYPES ====================================*/
/*============================ LOCAL VARIABLES ===============================*/
/*============================ IMPLEMENTATION ================================*/

ARM_NOINIT
struct __arm_2d_op_control ARM_2D_CTRL;

#define __arm_2d_get_address_and_region_from_tile(  __TILE_PTR,                 \
                                                    __VALID_REGION_NAME,        \
                                                    __LOCATION_OFFSET_PTR,      \
                                                    __TYPE,                     \
                                                    __BUF_PTR_NAME)             \
    arm_2d_region_t __VALID_REGION_NAME;                                        \
    assert(NULL != (__TILE_PTR));                                               \
    __TYPE *(__BUF_PTR_NAME) = NULL;                                            \
                                                                                \
    (__TILE_PTR) = arm_2d_tile_get_root((__TILE_PTR),                           \
                                        &__VALID_REGION_NAME,                   \
                                         __LOCATION_OFFSET_PTR);                \
                                                                                \
    if (NULL != (__TILE_PTR)) {                                                 \
        (__BUF_PTR_NAME) = ((__TYPE *)((__TILE_PTR)->pchBuffer))                \
                        + (__TILE_PTR)->tRegion.tSize.iWidth *                  \
                                __VALID_REGION_NAME.tLocation.iY                \
                        + __VALID_REGION_NAME.tLocation.iX;                     \
    }


/*----------------------------------------------------------------------------*
 * Region Calculation                                                         *
 *----------------------------------------------------------------------------*/

__WEAK
arm_fsm_rt_t __arm_2d_issue_sub_task_tile_process(  
                                        arm_2d_op_t *ptThis,
                                        __arm_2d_tile_param_t *ptParam)
{
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
    __arm_2d_sub_task_t *ptTask = &(__arm_2d_sub_task_t){
        .ptOP = (arm_2d_op_core_t *)ptThis,
        .Param.tTileProcess = *ptParam,
    };

    /* todo: update the tile process interface */

    /* call default software implementation */
    ARM_2D_RUN_DEFAULT( 0, __arm_2d_io_func_t);
    return tResult;
}


__WEAK
arm_fsm_rt_t __arm_2d_issue_sub_task_fill(
                                    arm_2d_op_cp_t *ptThis,
                                    __arm_2d_tile_param_t *ptSource,
                                    __arm_2d_tile_param_t *ptTarget)
{
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
    
    
    __arm_2d_sub_task_t *ptTask = &(__arm_2d_sub_task_t){
        .ptOP = (arm_2d_op_core_t *)ptThis,
        .Param.tFill = {
            .tSource        = *ptSource,
            .tTarget        = *ptTarget,
        },
    };

    /* call default software implementation */
    ARM_2D_RUN_DEFAULT( 1,__arm_2d_io_func_t );

    return tResult;
}

__WEAK
arm_fsm_rt_t __arm_2d_issue_sub_task_copy(arm_2d_op_cp_t *ptThis,
                                    __arm_2d_tile_param_t *ptSource,
                                    __arm_2d_tile_param_t *ptTarget,
                                    arm_2d_size_t * __RESTRICT ptCopySize)
{
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
    __arm_2d_sub_task_t *ptTask = &(__arm_2d_sub_task_t){
        .ptOP = (arm_2d_op_core_t *)ptThis,
        .Param.tCopy = {
            .tSource        = *ptSource,
            .tTarget        = *ptTarget,
            .tCopySize      = *ptCopySize,
        },
    };
    
    /* call default software implementation */
    ARM_2D_RUN_DEFAULT(0,__arm_2d_io_func_t );
    
    return tResult;
}

__WEAK
arm_fsm_rt_t __arm_2d_issue_sub_task_fill_origin(
                                    arm_2d_op_cp_t *ptThis,
                                    __arm_2d_tile_param_t *ptSource,
                                    __arm_2d_tile_param_t *ptOrigin,
                                    __arm_2d_tile_param_t *ptTarget)
{
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
    
    
    __arm_2d_sub_task_t *ptTask = &(__arm_2d_sub_task_t){
        .ptOP = (arm_2d_op_core_t *)ptThis,
        .Param.tFillOrig = {
            .use_as____arm_2d_param_fill_t = {
                .tSource        = *ptSource,
                .tTarget        = *ptTarget,
            },
            .tOrigin        = *ptOrigin,
        },
    };

    /* call default software implementation */
    ARM_2D_RUN_DEFAULT( 1,__arm_2d_io_func_t );

    return tResult;
}

__WEAK
arm_fsm_rt_t __arm_2d_issue_sub_task_copy_origin(
                                    arm_2d_op_cp_t *ptThis,
                                    __arm_2d_tile_param_t *ptSource,
                                    __arm_2d_tile_param_t *ptOrigin,
                                    __arm_2d_tile_param_t *ptTarget,
                                    arm_2d_size_t * __RESTRICT ptCopySize)
{
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
    __arm_2d_sub_task_t *ptTask = &(__arm_2d_sub_task_t){
        .ptOP = (arm_2d_op_core_t *)ptThis,
        .Param.tCopyOrig = {
            .use_as____arm_2d_param_copy_t = {
                .tSource        = *ptSource,
                .tTarget        = *ptTarget,
                .tCopySize      = *ptCopySize,
            },
            .tOrigin        = *ptOrigin,
        },
    };
    
    /* call default software implementation */
    ARM_2D_RUN_DEFAULT(0,__arm_2d_io_func_t );
    
    return tResult;
}



ARM_NONNULL(1,2)
static const arm_2d_tile_t * __arm_2d_tile_region_caculator( 
                                            const arm_2d_tile_t *ptTile,
                                            __arm_2d_tile_param_t *ptOut,
                                            uint_fast8_t *pchPixelLenInBit,
                                            bool bAllowEnforcedColour,
                                            uint32_t wMode)
{
    arm_2d_region_t tValidRegion;                                                     
    int32_t nOffset = 0;
    uint8_t *pchBuffer = NULL;                                            
                                    
    assert(NULL != ptTile);
    assert(NULL != ptOut);
    
    ptTile = arm_2d_tile_get_root(  ptTile,                           
                                    &tValidRegion,                   
                                    NULL);                
             
    if (NULL != (ptTile)) {    

        //! check if enforced colour is allowed
        if (bAllowEnforcedColour) {
            if (ptTile->tInfo.bHasEnforcedColour) {
                *pchPixelLenInBit = _BV(ptTile->tInfo.tColourInfo.u3ColourSZ);
            }
        }
        
        uint_fast8_t chPixelLenInBit = *pchPixelLenInBit;
    
        arm_2d_location_t tOffset = tValidRegion.tLocation;              
        if ((wMode) & ARM_2D_CP_MODE_X_MIRROR) {                             
            tOffset.iX = 0;                                                     
        }                                                                       
        if ((wMode) & ARM_2D_CP_MODE_Y_MIRROR) {                             
            tOffset.iY = 0;                                                     
        }                          

        if (chPixelLenInBit >= 8) {
            nOffset = ptTile->tRegion.tSize.iWidth * tOffset.iY + tOffset.iX;
            pchBuffer = ptTile->pchBuffer  
                      + (nOffset * chPixelLenInBit >> 3);            
        } else {
        
            nOffset = tOffset.iX;
        
            pchBuffer = ptTile->pchBuffer 
                      + ((ptTile->tRegion.tSize.iWidth 
                         *  chPixelLenInBit + 7) >> 3) * tOffset.iY
                      + ((tOffset.iX * chPixelLenInBit) >> 3);
        }
                                   
    }
    
    ptOut->iStride = ptTile->tRegion.tSize.iWidth;
    ptOut->nOffset = nOffset;
    ptOut->pBuffer = pchBuffer;
    ptOut->tValidRegion = tValidRegion;
        
    return ptTile;
}


ARM_NONNULL(1,2)
static
arm_fsm_rt_t __arm_2d_tile_process( arm_2d_op_t *ptThis,
                                    const arm_2d_tile_t *ptTarget)
{
    assert(NULL != ptTarget);
    assert(OP_CORE.ptOp->Info.Colour.u3ColourSZ >= ARM_2D_COLOUR_SZ_8BIT);
    
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;

    uint_fast8_t chPixelLenInBit = _BV(OP_CORE.ptOp->Info.Colour.u3ColourSZ);

    __arm_2d_tile_param_t tTileParam;
    __arm_2d_tile_region_caculator( 
                                ptTarget, 
                                &tTileParam,
                                &chPixelLenInBit,
                                OP_CORE.ptOp->Info.Param.bAllowEnforcedColour,
                                0);
                                    
    tResult = __arm_2d_issue_sub_task_tile_process( ptThis, &tTileParam); 
    
    return tResult;
}

ARM_NONNULL(1,2)
static
arm_fsm_rt_t __arm_2d_big_pixel_tile_pave(  arm_2d_op_cp_t *ptThis,
                                            const arm_2d_tile_t *ptSource,
                                            const arm_2d_tile_t *ptTarget,
                                            uint32_t wMode)
{
    assert(NULL != ptSource);
    assert(NULL != ptTarget);
    //assert(OP_CORE.ptOp->Info.Colour.u3ColourSZ >= ARM_2D_COLOUR_SZ_8BIT);
    
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
    uint_fast8_t chSourcePixelLenInBit = _BV(OP_CORE.ptOp->Info.Colour.u3ColourSZ);
    uint_fast8_t chTargetPixelLenInBit = chSourcePixelLenInBit;
    
    __arm_2d_tile_param_t tSourceTileParam;
    __arm_2d_tile_param_t tOriginTileParam;
    
    __arm_2d_tile_param_t tTargetTileParam;
    
    const arm_2d_tile_t *ptOrigin = NULL;
    
    if (OP_CORE.ptOp->Info.Param.bHasOrigin) {
        arm_2d_op_src_orig_t *ptOP = (arm_2d_op_src_orig_t *)ptThis;
        uint_fast8_t chOriginPixelLenInBit = chSourcePixelLenInBit;
        ptOrigin = __arm_2d_tile_region_caculator( 
                                ptOP->Origin.ptTile, 
                                &tOriginTileParam,
                                &chOriginPixelLenInBit,
                                OP_CORE.ptOp->Info.Param.bAllowEnforcedColour,
                                wMode); 
                                
        if (NULL == ptOrigin) {
            return (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
        }
    }

    ptSource = __arm_2d_tile_region_caculator( 
                                ptSource, 
                                &tSourceTileParam,
                                &chSourcePixelLenInBit,
                                OP_CORE.ptOp->Info.Param.bAllowEnforcedColour,
                                wMode); 
                                
     
    
    ptTarget = __arm_2d_tile_region_caculator( 
                                ptTarget, 
                                &tTargetTileParam,
                                &chTargetPixelLenInBit,
                                OP_CORE.ptOp->Info.Param.bAllowEnforcedColour,
                                0);

    if (NULL == ptSource || NULL == ptTarget) {
        return (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
    }

    if (wMode & ARM_2D_CP_MODE_FILL) {
    
        if (OP_CORE.ptOp->Info.Param.bHasOrigin) {
            tResult = __arm_2d_issue_sub_task_fill_origin(
                                                    ptThis,
                                                    &tSourceTileParam,
                                                    &tOriginTileParam,
                                                    &tTargetTileParam);
        } else {
            tResult = __arm_2d_issue_sub_task_fill( ptThis, 
                                                    &tSourceTileParam,
                                                    &tTargetTileParam);
        }
    } else {
        arm_2d_size_t tActualSize = {
            .iWidth = MIN(  tSourceTileParam.tValidRegion.tSize.iWidth, 
                            tTargetTileParam.tValidRegion.tSize.iWidth),
            .iHeight = MIN( tSourceTileParam.tValidRegion.tSize.iHeight, 
                            tTargetTileParam.tValidRegion.tSize.iHeight),
        };
        
        //! right and/or bottom alignment
        do {
            arm_2d_size_t tOffset = {0};
            
            //! right alignment 
            if (wMode & ARM_2D_CP_MODE_X_MIRROR) {
                tOffset.iWidth  = tSourceTileParam.tValidRegion.tSize.iWidth 
                                - tActualSize.iWidth;
            }
            
            //! bottom alignment 
            if (wMode & ARM_2D_CP_MODE_Y_MIRROR) {
                tOffset.iHeight = tSourceTileParam.tValidRegion.tSize.iHeight 
                                - tActualSize.iHeight;
            }
                        
                      
            if (chSourcePixelLenInBit >= 8) {
                
                tSourceTileParam.nOffset += ( tOffset.iHeight * ptSource->tRegion.tSize.iWidth 
                            + tOffset.iWidth);
                
                tSourceTileParam.pBuffer = ptSource->pchBuffer 
                          + (tSourceTileParam.nOffset * chSourcePixelLenInBit >> 3);
            } else {
                tSourceTileParam.nOffset += tOffset.iWidth;
            
                (*(uintptr_t *)&tSourceTileParam.pBuffer) += 
                          + ((ptSource->tRegion.tSize.iWidth 
                             *  chSourcePixelLenInBit + 7) >> 3) * tOffset.iHeight
                          + ((tOffset.iWidth * chSourcePixelLenInBit) >> 3);
            }
          
        } while(0);
    
        if (OP_CORE.ptOp->Info.Param.bHasOrigin) {
            tResult = __arm_2d_issue_sub_task_copy_origin( ptThis, 
                                                    &tSourceTileParam,
                                                    &tOriginTileParam,
                                                    &tTargetTileParam,
                                                    &tActualSize);
        } else {
            tResult = __arm_2d_issue_sub_task_copy( ptThis, 
                                                    &tSourceTileParam,
                                                    &tTargetTileParam,
                                                    &tActualSize);
        }
    }

    return tResult;
}


static
arm_fsm_rt_t __tile_clipped_pave(
                            arm_2d_op_cp_t *ptThis,
                            const arm_2d_region_t *ptRegion,
                            arm_2d_region_t *ptClippedRegion,
                            uint32_t wMode)
{
    arm_2d_region_t tempRegion = this.Source.ptTile->tRegion;
    arm_2d_tile_t tClippedTile = {0};
    arm_2d_tile_t tTargetTile = {0};
    tempRegion.tLocation.iX = -ptRegion->tLocation.iX;
    tempRegion.tLocation.iY = -ptRegion->tLocation.iY;
    tempRegion.tSize.iWidth += ptRegion->tLocation.iX;
    tempRegion.tSize.iHeight += ptRegion->tLocation.iY;

    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
    do {
        if (NULL == arm_2d_tile_generate_child( this.Target.ptTile, 
                                                ptRegion, 
                                                &tTargetTile, 
                                                true)) {
            break;
        }

        if (NULL == arm_2d_tile_generate_child( this.Source.ptTile, 
                                                &tempRegion, 
                                                &tClippedTile, 
                                                true)) {
            break;
        }
        
        if (NULL != ptClippedRegion) {
            *ptClippedRegion = tClippedTile.tRegion;
        }
    

    #if 0
        switch (OP_CORE.ptOp->Info.Colour.u3ColourSZ) {
            case ARM_2D_COLOUR_SZ_8BIT:
            case ARM_2D_COLOUR_SZ_16BIT:
            case ARM_2D_COLOUR_SZ_32BIT:
                tResult = __arm_2d_big_pixel_tile_pave( ptThis, 
                                                    &tClippedTile, 
                                                    &tTargetTile, 
                                                    wMode);
                break;
            default:
                tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
                break;
        }
    #else
        if (OP_CORE.ptOp->Info.Colour.u3ColourSZ >= ARM_2D_COLOUR_SZ_8BIT) {
            tResult = __arm_2d_big_pixel_tile_pave( ptThis, 
                                    &tClippedTile, 
                                    &tTargetTile, 
                                    wMode);
        } else {
            tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
        }
    #endif
    } while(0);

    return tResult;
}

static arm_fsm_rt_t __tile_non_negtive_location_pave(
                                                arm_2d_op_cp_t *ptThis,
                                                const arm_2d_tile_t *ptSource,
                                                const arm_2d_tile_t *ptTarget,
                                                const arm_2d_region_t *ptRegion,
                                                uint32_t wMode)
{
    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
    do {
        arm_2d_tile_t tTile = {0};
        if (NULL == arm_2d_tile_generate_child(ptTarget, ptRegion, &tTile, true)) {
            break;
        }
    #if 0
        switch (OP_CORE.ptOp->Info.Colour.u3ColourSZ) {
            case ARM_2D_COLOUR_SZ_8BIT:
            case ARM_2D_COLOUR_SZ_16BIT:
            case ARM_2D_COLOUR_SZ_32BIT:
                tResult = __arm_2d_big_pixel_tile_pave(ptThis, ptSource, &tTile, wMode);
                break;
            default:
                tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
                break;
        }
    #else
        if (OP_CORE.ptOp->Info.Colour.u3ColourSZ >= ARM_2D_COLOUR_SZ_8BIT) {
            tResult = __arm_2d_big_pixel_tile_pave( ptThis, 
                                                    ptSource, 
                                                    &tTile, 
                                                    wMode);
        } else {
            tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
        }
    #endif
    } while(0);

    return tResult;
}


__WEAK 
bool __arm_2d_op_ensure_resource(   arm_2d_op_core_t *ptOP, 
                                    uint_fast16_t hwRequired)
{
    ARM_2D_UNUSED(hwRequired);
    ARM_2D_UNUSED(ptOP);
    return true;
}

static
arm_fsm_rt_t __arm_2d_op_frontend_region_process( arm_2d_op_core_t *ptOP)
{
   ARM_2D_IMPL(arm_2d_op_t, ptOP)

    arm_fsm_rt_t tResult = (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;;
    arm_2d_tile_t tTile = {0};
    arm_2d_region_t tDrawRegion = {0};
    arm_2d_region_t tTargetRegion = {0};

    if (!__arm_2d_op_ensure_resource(ptOP, 1)) {
        //! insufficient resources, ask users to try again
        return arm_fsm_rt_wait_for_res;
    }
    
    
    if (NULL == this.Target.ptRegion) {
        //! use target region instead
        this.Target.ptRegion = &(this.Target.ptTile->tRegion);
        
        tTargetRegion.tSize = (*this.Target.ptRegion).tSize;
    } else {

        tTargetRegion = *this.Target.ptRegion;
    }

    //! handle the offset of the target tile
    do {
        //arm_2d_tile_t tValidTargetTile;
        arm_2d_region_t tValidRegion;
        arm_2d_location_t tOffset;
        if (NULL == arm_2d_tile_get_root(this.Target.ptTile, &tValidRegion, &tOffset)) {
            //! nothing to draw, actually
            return (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
        }

        //! calculate the valid region in the view of the target tile
        tValidRegion.tLocation = tOffset;
        this.Target.ptTile = arm_2d_tile_generate_child(  this.Target.ptTile,
                                                &tValidRegion,
                                                &tTile,
                                                true);
        assert(NULL != this.Target.ptTile);
        tTargetRegion.tLocation.iX -= tOffset.iX;
        tTargetRegion.tLocation.iY -= tOffset.iY;

    } while(0);

    tDrawRegion.tSize = this.Target.ptTile->tRegion.tSize;
    if (!arm_2d_region_intersect(   &tDrawRegion, 
                                    &tTargetRegion, 
                                    &tDrawRegion)) {
        return (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
    }


    //! drawing
    do {
        arm_2d_tile_t tTempTile = {0};
        if (NULL == arm_2d_tile_generate_child( this.Target.ptTile, 
                                                &tTargetRegion, 
                                                &tTempTile, true)) {
            break;
        }
    #if 0
        switch (OP_CORE.ptOp->Info.Colour.u3ColourSZ) {
            case ARM_2D_COLOUR_SZ_8BIT:
            case ARM_2D_COLOUR_SZ_16BIT:
            case ARM_2D_COLOUR_SZ_32BIT:
                tResult = __arm_2d_big_pixel_tile_pave(ptThis, ptSource, &tTile, wMode);
                break;
            default:
                tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
                break;
        }
    #else
        if (OP_CORE.ptOp->Info.Colour.u3ColourSZ >= ARM_2D_COLOUR_SZ_8BIT) {
            tResult = __arm_2d_tile_process( ptThis,
                                            &tTempTile);
        } else {
            tResult = (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
        }
    #endif
    } while(0);

    return tResult;
}

static
arm_fsm_rt_t __arm_2d_op_frontend_control( arm_2d_op_core_t *ptOP)
{
    /*TODO: add support for control operations*/
    return (arm_fsm_rt_t)ARM_2D_ERR_NOT_SUPPORT;
}

static
arm_fsm_rt_t __arm_2d_op_frontend_region_process_with_src( arm_2d_op_core_t *ptOP)
{
    ARM_2D_IMPL(arm_2d_op_src_t, ptOP)

    arm_fsm_rt_t tResult;
    arm_2d_tile_t tTile = {0};
    arm_2d_region_t tDrawRegion = {0};
    arm_2d_region_t tTargetRegion = {0};

    if (!__arm_2d_op_ensure_resource(ptOP, 4)) {
        //! insufficient resources, ask users to try again
        return arm_fsm_rt_wait_for_res;
    }
    
    
    if (NULL == this.Target.ptRegion) {
        //! use target region instead
        this.Target.ptRegion = &(this.Target.ptTile->tRegion);
        
        tTargetRegion.tSize = (*this.Target.ptRegion).tSize;
    } else {

        tTargetRegion = *this.Target.ptRegion;
    }

    //! handle the offset of the target tile
    do {
        //arm_2d_tile_t tValidTargetTile;
        
        arm_2d_region_t tValidRegion;
        arm_2d_location_t tOffset;
        if (NULL == arm_2d_tile_get_root(this.Target.ptTile, &tValidRegion, &tOffset)) {
            //! nothing to draw, actually
            return (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
        }

        //! calculate the valid region in the view of the target tile
        tValidRegion.tLocation = tOffset;
        //if (tOffset.iX != 0 || tOffset.iY != 0) {
            this.Target.ptTile = arm_2d_tile_generate_child(  this.Target.ptTile,
                                                    &tValidRegion,
                                                    &tTile,
                                                    true);
            assert(NULL != this.Target.ptTile);
            tTargetRegion.tLocation.iX -= tOffset.iX;
            tTargetRegion.tLocation.iY -= tOffset.iY;
        //}

    } while(false);



    if ( this.wMode & ARM_2D_CP_MODE_FILL) {
        /* quickly ignore non visiable area, only for FILL mode */
        if (tTargetRegion.tLocation.iX < 0) {
            int_fast16_t iX = tTargetRegion.tLocation.iX;
            tTargetRegion.tLocation.iX %= this.Source.ptTile->tRegion.tSize.iWidth;

            //! calculate the delta
            iX = tTargetRegion.tLocation.iX - iX;

            //! update the width to make sure the range won't be expanded mistakenly
            tTargetRegion.tSize.iWidth -= iX;
        }

        if (tTargetRegion.tLocation.iY < 0) {
            int_fast16_t iY = tTargetRegion.tLocation.iY;
            tTargetRegion.tLocation.iY %= this.Source.ptTile->tRegion.tSize.iHeight;

            //! calculate the delta
            iY = tTargetRegion.tLocation.iY - iY;

            //! update the width to make sure the range won't be expanded mistakenly
            tTargetRegion.tSize.iHeight -= iY;
        }
    }

    tDrawRegion.tSize = this.Target.ptTile->tRegion.tSize;
    if (!arm_2d_region_intersect(   &tDrawRegion, 
                                    &tTargetRegion, 
                                    &tDrawRegion)) {
        //! no overlapping
        return (arm_fsm_rt_t)ARM_2D_ERR_OUT_OF_REGION;
    }

    if (    (tTargetRegion.tLocation.iX < 0) 
        ||  (tTargetRegion.tLocation.iY < 0)) {
        /*! draw the top left corner */
        /*
          HOW IT WORKS:

           source tile (with negative relative location)
          +------------------------------------+
          |                                    |
          |     +------------------------------+-- ... --+
          |     |//////////////////////////////|         |
          |     |//ACTUAL DRAWING PART/////////|         |
          |     |//////////////////////////////|         |
          +-----+------------------------------+-- ... --|
                |                              |         |
               ...                            ...       ...
                |                              |         |
                +------------------------------+-- ... --+
         */
        arm_2d_region_t tClippdRegion;
        tResult = __tile_clipped_pave(  &this,
                                        &tTargetRegion,
                                        &tClippdRegion,
                                        this.wMode & ~ARM_2D_CP_MODE_FILL);
                                        
        if (tResult < 0) {
            return tResult;
        }

        if (this.wMode & ARM_2D_CP_MODE_FILL) {                               /*!< fill mode */
            /*! draw the partial header */
            /*
            HOW IT WORKS:

            source tile (with negative relative location)
            +------------------+------------------------------------+-- ... --+
            |                  |    IGNORED HEADER PART             | repeat  |
            |  +---------------+------------------------------------+-- ... --+
            |  |               |////////////////////////////////////|/////////|
            |  |Clipped Region |////ACTUAL DRAWING PART/////////////|/repeat//|
            |  |               |////////////////////////////////////|/////////|
            +--+---------------+------------------------------------+-- ... --+
            |                                                                 |
            |                                                                 |
            +---------------------------------------------------------- ... --+
            */

            if  (tClippdRegion.tSize.iWidth < tDrawRegion.tSize.iWidth) {
                //! something left to draw

                arm_2d_region_t tHeaderRegion = tDrawRegion;
                tHeaderRegion.tSize.iWidth  = tDrawRegion.tSize.iWidth 
                                            - tClippdRegion.tSize.iWidth;
                tHeaderRegion.tLocation.iX += tClippdRegion.tSize.iWidth;
                tHeaderRegion.tSize.iHeight = tClippdRegion.tSize.iHeight 
                                            - tTargetRegion.tLocation.iY;
                tHeaderRegion.tLocation.iY = tTargetRegion.tLocation.iY;

                tResult = __tile_clipped_pave(  &this,
                                                &tHeaderRegion,
                                                NULL,
                                                this.wMode);
                                    
                //arm_2d_region_t tClippedRegion;
                if (tResult < 0) {
                    return tResult;
                }
            }



            /*! draw the partial column */
            /*
            HOW IT WORKS:

            source tile (with negative relative location)
            +------------------------------------+
            |                                    |
            |     +------------------------------+-- ... --+
            |     |                              |         |
            |     |                              |         |
            |     |                              |         |
            +-----+------------------------------+         |
            |     |//////////////////////////////|         |
            |     |//////////////////////////////|         |
            |     |////ACTUAL DRAWING COLUMN/////|         |
            |     |//////////////////////////////|         |
            |     |//////////////////////////////|         |
            +-----+------------------------------+         |
            |     |//////////////////////////////|         |
           ...   ...///repeat////////////////////|         |
            |     |//////////////////////////////|         |
            +-----+------------------------------+-- ... --+
            */

            if  (tClippdRegion.tSize.iHeight < tDrawRegion.tSize.iHeight) {
                //! something left to draw

                arm_2d_region_t tFirstColumnRegion = tDrawRegion;
                tFirstColumnRegion.tSize.iHeight = tDrawRegion.tSize.iHeight - tClippdRegion.tSize.iHeight;
                tFirstColumnRegion.tLocation.iY += tClippdRegion.tSize.iHeight;
                tFirstColumnRegion.tSize.iWidth = tClippdRegion.tSize.iWidth 
                                                - tTargetRegion.tLocation.iX;
                tFirstColumnRegion.tLocation.iX = tTargetRegion.tLocation.iX;

                tResult = __tile_clipped_pave(  &this,
                                                &tFirstColumnRegion,
                                                NULL,
                                                this.wMode);
                                    
                if (tResult < 0) {
                    return tResult;
                }
            }


            /*! draw the normal non-negitive part */
            /*
            HOW IT WORKS:

            source tile (with negative relative location)
            +------------------------------------+
            |                                    |
            |     +------------------------------+-- ... --+
            |     |                              |         |
            |     |                              |         |
            |     |                              |         |
            +-----+------------------------------+-- ... --|
                  |                              |/////////|
                  |                              |/////////|
                 ...                            ...///////...
                  |                              |/////////|
                  |                              |/////////|
                  +------------------------------+-- ... --+
            */

            if (    (tClippdRegion.tSize.iWidth < tDrawRegion.tSize.iWidth)
                &&  (tClippdRegion.tSize.iHeight < tDrawRegion.tSize.iHeight)) {

                arm_2d_region_t tNonNegRegion = tDrawRegion;
                tNonNegRegion.tSize.iWidth = tDrawRegion.tSize.iWidth - tClippdRegion.tSize.iWidth;
                tNonNegRegion.tSize.iHeight = tDrawRegion.tSize.iHeight - tClippdRegion.tSize.iHeight;
                tNonNegRegion.tLocation.iX += tClippdRegion.tSize.iWidth;
                tNonNegRegion.tLocation.iY += tClippdRegion.tSize.iHeight;

                tResult = __tile_non_negtive_location_pave( &this,
                                                            this.Source.ptTile,
                                                            this.Target.ptTile,
                                                            &tNonNegRegion,
                                                            this.wMode);
                if (tResult < 0) {
                    return tResult;
                }
            }

        }
    } else {
        tResult = __tile_non_negtive_location_pave( &this,
                                                    this.Source.ptTile,
                                                    this.Target.ptTile,
                                                    &tTargetRegion,
                                                    this.wMode);
    }

    return tResult;
}


/*----------------------------------------------------------------------------*
 * Frontend                                                                   *
 *----------------------------------------------------------------------------*/

__WEAK
arm_fsm_rt_t __arm_2d_op_frontend_on_leave( arm_2d_op_core_t *ptThis, 
                                            arm_fsm_rt_t tResult)
{
    this.Status.bIsBusy = 0;
    this.Status.bOpCpl = true;
    this.Status.bIOError = (tResult < 0);
    this.tResult = tResult;
    
    return tResult;
}

static void __arm_2d_op_use_default_frame_buffer(arm_2d_op_core_t *ptOP)
{
    ARM_2D_IMPL(arm_2d_op_t, ptOP);
    
    if ((intptr_t)-1 == (intptr_t)this.Target.ptTile) {
        this.Target.ptTile = ARM_2D_CTRL.ptDefaultFrameBuffer;
    }
}

 
arm_fsm_rt_t __arm_2d_op_frontend_op_decoder(arm_2d_op_core_t *ptThis)
{
    arm_fsm_rt_t tResult;
    
    //! decode operation
    switch (this.ptOp->Info.Param.chValue & 
                (   ARM_2D_OP_INFO_PARAM_HAS_SOURCE 
                //|   ARM_2D_OP_INFO_PARAM_HAS_ORIGIN
                |   ARM_2D_OP_INFO_PARAM_HAS_TARGET
                |   ARM_2D_OP_INFO_PARAM_HAS_ALPHA_MASK)) {
                
    case (   ARM_2D_OP_INFO_PARAM_HAS_SOURCE 
         |   ARM_2D_OP_INFO_PARAM_HAS_TARGET):
        __arm_2d_op_use_default_frame_buffer(ptThis);
        /* operaion process target tile based on source tile */
        tResult = __arm_2d_op_frontend_region_process_with_src(ptThis);
        break;
        
    case ARM_2D_OP_INFO_PARAM_HAS_TARGET:
        __arm_2d_op_use_default_frame_buffer(ptThis);
        /* operation which only processes target tile */
        tResult = __arm_2d_op_frontend_region_process(ptThis);
        break;
        
    default:
        /* control operation */
        tResult = __arm_2d_op_frontend_control(ptThis);
    }
    
    if (ARM_2D_ERR_OUT_OF_REGION == tResult) {                                 
        if (ARM_2D_RUNTIME_FEATURE.TREAT_OUT_OF_RANGE_AS_COMPLETE) {
            //! nothing to draw
            tResult = arm_fsm_rt_cpl;
        } 
    }
    
    return tResult;
}


__WEAK
/*! \brief sync up with operation 
 *! \retval true sync up with operation
 *! \retval false operation is busy
 */
bool arm_2d_op_wait_async(arm_2d_op_core_t *ptOP)
{
    return true;
}


__WEAK
/*! \brief sync up with operation 
 *! \retval true operation is busy
 *! \retval false operation isn't busy
 */
bool __arm_2d_op_acquire(arm_2d_op_core_t *ptOP)
{
    ARM_2D_IMPL(arm_2d_op_core_t, ptOP)
    bool bResult = !this.Status.bIsBusy;
    
    arm_irq_safe {
        if (bResult) {
            //! initialize operation
            do {
                this.tResult = arm_fsm_rt_async;
                this.Status.tValue = 0;                                                 //! reset status
                this.Status.bIsBusy = true;                                             //! set busy flag
            } while(0);
        }
    }

    return bResult;
}

__WEAK
arm_fsm_rt_t __arm_2d_op_frontend(arm_2d_op_core_t *ptThis)
{
    arm_fsm_rt_t tResult;
    
    tResult = __arm_2d_op_frontend_op_decoder(ptThis);
    
    return  __arm_2d_op_frontend_on_leave(ptThis, tResult);
}

arm_fsm_rt_t __arm_2d_op_invoke(arm_2d_op_core_t *ptOP)
{
    ARM_2D_IMPL(arm_2d_op_core_t, ptOP)
    
#if 0
    if (this.Status.bIsBusy) {
        return arm_fsm_rt_on_going;
    }

    //! initialize operation
    do {
        this.tResult = arm_fsm_rt_async;
        this.Status.tValue = 0;                                                 //! reset status
        this.Status.bIsBusy = true;                                             //! set busy flag
    } while(0);
#endif

    return  __arm_2d_op_frontend(ptThis);
}


/*! \brief initialise the whole arm-2d service
 *! \param none
 *! \return none
 */

void __arm_2d_init(void)
{
    /*! initialise arm-2d control block */
    memset(&ARM_2D_CTRL, 0, sizeof(struct __arm_2d_op_control));
}

/*! \brief set the default frame buffer
 *! \param ptFramebuffer the new frame buffer, if NULL is given, no default
 *!       frame buffer will be used
 *! \return the address of the old frame buffer
 */
arm_2d_tile_t *arm_2d_set_default_frame_buffer(const arm_2d_tile_t *ptFrameBuffer)
{
    arm_2d_tile_t *ptOldBuffer = NULL;
    
    arm_irq_safe {
        ptOldBuffer = ARM_2D_CTRL.ptDefaultFrameBuffer;
        ARM_2D_CTRL.ptDefaultFrameBuffer = (arm_2d_tile_t *)ptFrameBuffer;
    }
    
    return ptOldBuffer;
}

/*! \brief get the default frame buffer
 *! \return the address of the default frame buffer
 */
arm_2d_tile_t *arm_2d_get_default_frame_buffer(void)
{
    return ARM_2D_CTRL.ptDefaultFrameBuffer;
}

/*! \brief attach a user param (which could be a pointer) to specified OP
 *! \param ptOP the address of the target OP (NULL means using the default OP)
 *! \param pUserParam a user param (it can be used as a pointer)
 */
void arm_2d_set_user_param(arm_2d_op_core_t *ptOP, uintptr_t pUserParam)
{
    ARM_2D_IMPL(arm_2d_op_core_t, ptOP);
    
    this.pUserParam = pUserParam;
}

/*! \brief get the status of a specified OP
 *! \param ptOP the address of the target OP (NULL means using the default OP)
 *! \return the status
 */
arm_2d_op_status_t arm_2d_get_op_status(arm_2d_op_core_t *ptOP)
{
    ARM_2D_IMPL(arm_2d_op_core_t, ptOP)

    volatile arm_2d_op_status_t *ptStatus 
        = (volatile arm_2d_op_status_t *)&(this.Status);
        
    return *ptStatus;
}


__WEAK
 /*! \brief arm-2d pixel pipeline task entry
  *! \note  This function is *TRHEAD-SAFE*
  *! \param none
  *! \retval arm_fsm_rt_cpl The sub-task FIFO is empty, the caller, i.e. the host
  *!            RTOS thread can block itself by waiting for a semaphore which is
  *!            set by arm_2d_notif_sub_task_fifo_task_arrive()
  *! \retval arm_fsm_rt_on_going The arm_2d_task issued one sub-task without 
  *!            problem and it yields. 
  *! \retval arm_fsm_rt_async You shouldn't see this value
  *! \retval arm_fsm_rt_wait_for_obj some algorithm or hardware accelerator wants
  *!            to sync-up with applications.
  *! \retval (<0) Serious error is detected.
  */
arm_fsm_rt_t arm_2d_task(arm_2d_task_t *ptTask)
{
    ARM_2D_UNUSED(ptTask);
    return arm_fsm_rt_cpl;
}


#if defined(__clang__)
#   pragma clang diagnostic pop
#elif defined(__IS_COMPILER_ARM_COMPILER_5__)
#   pragma diag_warning 174,177,188,68,513,144
#elif defined(__IS_COMPILER_GCC__)
#   pragma GCC diagnostic pop
#endif

#ifdef   __cplusplus
}
#endif
