 /*
 * Copyright (c) 2013 - 2016, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "../source/system/nt_system_prv.h"
#include "../source/system/nt_system_mem_prv.h"
#include "nt_controls.h"
#include "../source/controls/nt_controls_prv.h"
#include "nt_control_proxi.h"
#include "nt_control_proxi_prv.h"
#include "../source/electrodes/nt_electrodes_prv.h"
#include "../source/filters/nt_filters_prv.h"

/* Invoke callback based on the even has occurred */
static void _nt_control_proxi_invoke_callback(struct nt_control_data *control,
                                              enum nt_control_proxi_event event,
                                              uint32_t index_or_gesture, 
                                              uint32_t proximity);

/* The proximity control initialization function. */
static int32_t _nt_control_proxi_init(struct nt_control_data *control);
/* The proximity control process function. */
static int32_t _nt_control_proxi_process(struct nt_control_data *control);

/* The proximity control interface. */
const struct nt_control_interface nt_control_proxi_interface = {
    .init    = _nt_control_proxi_init,
    .process = _nt_control_proxi_process,
    .name    = "nt_control_proxi_interface",
};

void nt_control_proxi_register_callback(const struct nt_control *control,
                                        nt_control_proxi_callback callback)
{
    NT_ASSERT(control != NULL);
    NT_ASSERT(control->interface == &nt_control_proxi_interface);
    
    struct nt_control_data *control_data = _nt_control_get_data(control);
    NT_ASSERT(control_data != NULL);
    
    struct nt_control_proxi_data *ram = control_data->data.proxi;
    NT_ASSERT(ram != NULL);
    
    ram->callback = callback;
}

static void _nt_control_proxi_invoke_callback(struct nt_control_data *control,
                                              enum nt_control_proxi_event event, 
                                              uint32_t index_or_gesture,
                                              uint32_t proximity)
{
    struct nt_control_proxi_data *ram = control->data.proxi;
    NT_ASSERT(ram != NULL);
    
    if (ram->callback != NULL) 
    {
        ram->callback(control->rom, event, index_or_gesture, proximity);
    }
}

static int32_t _nt_control_proxi_init(struct nt_control_data *control)
{
    NT_ASSERT(control != NULL);
    NT_ASSERT(control->rom->interface == &nt_control_proxi_interface);
    NT_ASSERT(control->rom->control_params.proxi != NULL); 
   
    const struct nt_control_proxi *proxi = control->rom->control_params.proxi;
    
    if(proxi->range == 0)
    {
        return NT_FAILURE;
    }
    
    if(proxi->threshold == 0)
    {
        return NT_FAILURE;
    }
    
    control->data.proxi = _nt_mem_alloc(sizeof(struct nt_control_proxi_data));
    
    if(control->data.proxi == NULL)
    {
        return NT_OUT_OF_MEMORY;
    }
    
    if (_nt_control_check_data(control) != NT_SUCCESS) {
        return NT_FAILURE;
    }  
    
    NT_ASSERT(control->data.proxi != NULL);
    struct nt_control_proxi_data *ram = control->data.proxi;
    /* Initialization of maximum proximity for all proximity electrodes from proxi->range */
    uint32_t elec_counter = control->electrodes_size;
    while (elec_counter--) 
    {   /* Assign some proxi_max value to make proximity working when max proximity value is not obtained. */      
        ram->proxi_max[elec_counter] = proxi->range; 
    }   
           
    #if NT_PROXI_GESTURES > 0    
    ram->recognized_gesture = 0;  /* Initialization recognized gesture variable */
    ram->selected_gesture = 0;    /* Initialization selected gesture to store variable */
    ram->gesture_memory = 0;      /* Initialization of stored gesture memory used. Default is ROM, RAM selected after recognized_gesture > 1 */ 
    
    /* Section for gesture storing */
    uint8_t n;
    for (n=0; n<NT_PROXI_GESTURES; n++)
    {   ram->gesture[n] = proxi->gesture[n];   /* Copy gestures from flash to ram */
    }
    
    #endif /* NT_PROXI_GESTURES > 0 */ 
    return NT_SUCCESS;
}

static int32_t _nt_control_proxi_process(struct nt_control_data *control)
{
    NT_ASSERT(control != NULL);
    NT_ASSERT(control->rom != NULL);  
    NT_ASSERT(control->rom->interface == &nt_control_proxi_interface); 
    
    uint32_t elec_counter = control->electrodes_size; 
    int32_t current_prox;
    int32_t proxi[NT_PROXI_ELECTRODE];
        
    struct nt_control_proxi_data *ram = control->data.proxi;
    const struct nt_control_proxi *rom = control->rom->control_params.proxi;
      
    NT_ASSERT(ram != NULL);
    NT_ASSERT(rom != NULL);  
    
    if (!_nt_control_get_flag(control, NT_CONTROL_EN_FLAG) ||
        !_nt_control_get_flag(control, NT_CONTROL_NEW_DATA_FLAG)) {
            return NT_FAILURE; /* control disabled or data not ready */
        }
       
    /* Proximity process */
    while (elec_counter--) 
    {         
        /* Get electrodes delta values */
        current_prox = _nt_electrode_get_delta(control->electrodes[elec_counter]);                  
        current_prox = _nt_abs_int32 (current_prox);
        
        /* Finding maximum delta value to calculate proximity */
        if (ram->proxi_max[elec_counter] < current_prox)
        {   
            ram->proxi_max[elec_counter] = current_prox;
        } 
        
        /* denominator in not zero (checked in init) so calculate scaled proximity */
        current_prox = (uint32_t)((uint16_t) current_prox * (uint16_t)rom->scale);
        current_prox = (uint32_t)((uint32_t) current_prox / (uint32_t)ram->proxi_max[elec_counter]); 

        /* Store proximity to array for gesture processing */       
        proxi[elec_counter] = current_prox;
        
        /* If touch, check change and direction */
        if(_nt_control_proxi_get_flag(&ram->proxi_flags[elec_counter], NT_PROXI_TOUCH_FLAG))
        {   /* Check insensitivity for movement neglecting. */
            if(_nt_abs_int32(ram->proxi_k_1[elec_counter] - current_prox) > rom->insensitivity)
            {   /* Observe direction and movement process */
                if (ram->proxi_k_1[elec_counter] < current_prox) 
                {
                    _nt_control_proxi_set_flag(&ram->proxi_flags[elec_counter], NT_PROXI_DIRECTION_FLAG);
                    _nt_control_set_flag(control, NT_PROXI_DIRECTION_FLAG);
                } 
                else 
                {
                    _nt_control_proxi_clear_flag(&ram->proxi_flags[elec_counter], NT_PROXI_DIRECTION_FLAG);
                    _nt_control_clear_flag(control, NT_PROXI_DIRECTION_FLAG);
                }
                /* flags and callbacks when moving (proximity changing) */
                _nt_control_proxi_set_flag(&ram->proxi_flags[elec_counter], NT_PROXI_MOVEMENT_FLAG);
                _nt_control_clear_flag(control, NT_PROXI_DIRECTION_FLAG);
                _nt_control_set_flag(control, NT_PROXI_MOVEMENT_FLAG);
                _nt_control_proxi_invoke_callback(control, NT_PROXI_MOVEMENT, elec_counter,current_prox);
            }  
            else  /* Too small proximity change - will be neglected */
            {
                _nt_control_proxi_clear_flag(&ram->proxi_flags[elec_counter], NT_PROXI_MOVEMENT_FLAG);    
                _nt_control_clear_flag(control, NT_PROXI_MOVEMENT_FLAG);    
            }
        }    
        ram->proxi_k_1[elec_counter] = current_prox; /* store proximity value for next step */
       
        /* Touch recognition */
        if (current_prox > rom->threshold)  /* Check Touch threshold */
        {   /* Skip if touch was detected previously, only one touch event call */ 
            if (!_nt_control_proxi_get_flag((&ram->proxi_flags[elec_counter]), NT_PROXI_TOUCH_FLAG)) 
            {
                _nt_control_set_flag(control, NT_PROXI_TOUCH_FLAG);
                _nt_control_proxi_set_flag(&ram->proxi_flags[elec_counter], NT_PROXI_TOUCH_FLAG);
                _nt_control_proxi_clear_flag(&ram->proxi_flags[elec_counter], NT_PROXI_RELEASE_FLAG);
                _nt_control_proxi_invoke_callback(control, NT_PROXI_TOUCH, elec_counter, current_prox);
            }  
        } 
        else  /* Release event */
        {   _nt_control_clear_flag(control, NT_PROXI_TOUCH_FLAG);   /* released */
            _nt_control_clear_flag(control, NT_PROXI_MOVEMENT_FLAG);/* clear flag */ 
            _nt_control_proxi_clear_flag(&ram->proxi_flags[elec_counter], NT_PROXI_TOUCH_FLAG);   /* released */
            _nt_control_proxi_clear_flag(&ram->proxi_flags[elec_counter], NT_PROXI_MOVEMENT_FLAG);/* clear flag */ 
            /* Call just once, if already released, no more calls*/
            if (!_nt_control_proxi_get_flag(&ram->proxi_flags[elec_counter], NT_PROXI_RELEASE_FLAG))
            { 
                _nt_control_proxi_invoke_callback(control, NT_PROXI_RELEASE,elec_counter,current_prox);    
                _nt_control_proxi_set_flag(&ram->proxi_flags[elec_counter], NT_PROXI_RELEASE_FLAG);
                _nt_control_set_flag(control, NT_PROXI_RELEASE_FLAG);
            }     
        }
    } 
    /* GESTURE RECOGNITION */
    /* index (position) recognition */ 
    elec_counter = control->electrodes_size;   
    if (elec_counter > 1)
    {   ram->proximity = 0;      /* clear proximity max. */        
        while (elec_counter--)   /* Find proximity max and its electrode */
        {    
            if (proxi[elec_counter] > ram->proximity)     /* Compare electrode proximity with max values */
            {   ram->proximity = proxi[elec_counter];     /* Store current in case of higher value was found */
                if (_nt_control_proxi_get_flag(&ram->proxi_flags[elec_counter], NT_PROXI_TOUCH_FLAG))
                {   ram->index = elec_counter;            /* Store index of the touched electrode with max proximity */
                }    
            }      
        } 
        #if NT_PROXI_GESTURES > 0                         /* Avoid the code execution if PROXI_GESTURES not required. */
        if (ram->index != ram->index_k_1)                 /* Max proximity electrode was changed */
        {   /* Storing max electrode to buffer */ 
            ram->buffer_index &= NT_PROXI_ELECTRODE-1;            /* Clean unused part of buffer index to overflow the index */
            ram->buffer &= ~(0xf<<(ram->buffer_index<<2));        /* Clean buffer part to store max. electrode index */
            ram->buffer |= (ram->index<<(ram->buffer_index<<2));  /* Store max. electrode index to buffer*/ 
            ram->buffer_index++;                                  /* Increment buffer index */
            ram->index_k_1 = ram->index;                          /* Store electrode index for next step */
              
            uint8_t n;
            /* guesture recognition */
            if (ram->buffer_index > NT_PROXI_ELECTRODE-1)   
            {   
                if (ram->selected_gesture > 0)                    /* Store gesture captured in buffer (gesture 1 has array index 0)*/
                {
                    ram->gesture[ram->selected_gesture-1] = ram->buffer;  /* Store buffered value to gesture array */
                    _nt_control_proxi_invoke_callback(control, NT_PROXI_SET_GESTURE, ram->selected_gesture, current_prox);                    
                    ram->selected_gesture = 0;                    /* Clean selected gesture quantity for next gesture storing */
                    ram->gesture_memory = 1;                      /* After first gesture storing the Ram memory will be used */
                }
                else 
                {    
                    ram->recognized_gesture = 0;            /* Clear recognized gesture value */
                    uint32_t temp_gesture;
                    for (n=0; n<NT_PROXI_GESTURES; n++)  
                    {   
                        if (ram->gesture_memory == 0)       /* Default is to use Rom, after first gesture stored the Ram will be used */
                            temp_gesture = rom->gesture[n]; /* Read from Rom */
                        else
                            temp_gesture = ram->gesture[n]; /* Otherwise read from Ram */
                        if (temp_gesture == ram->buffer)    /* Compare buffered gesture with stored one */ 
                            ram->recognized_gesture = n+1;  /* In case of sameness store the gesture number */

                    }                      
                    if (ram->recognized_gesture != 0)
                        _nt_control_proxi_invoke_callback(control, NT_PROXI_GET_GESTURE, ram->recognized_gesture, current_prox);   
                }
            }                    
        }
        #endif /* #if NT_PROXI_GESTURES > 0 */        
    }
    else 
    {
        ram->index = 0; /* Set key index to zero because only one electrode assigned */ 
    }    
    _nt_control_clear_flag(control, NT_CONTROL_NEW_DATA_FLAG); /* data processed */  
    
    return NT_SUCCESS;
}
