/* ******************************************************************************
 *                        LCDMenuLib2 (LCDML)
 * ******************************************************************************
 *
 * MIT License
 *
 * Copyright (c) [2020] [Nils Feldkämper]
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * ******************************************************************************
 *
 * BUG / ISSUES REPORTING
 *    https://github.com/Jomelo/LCDMenuLib2/issues
 *
 * ARDUINO FORUM
 *    http://forum.arduino.cc/index.php?topic=73816.0
 *
 * ******************************************************************************
 */

// File header
#ifndef LCDMenuLib2_h
    #define LCDMenuLib2_h

    // ####################### //
    // This following defines can be changed
    // ####################### //

    // you can change this parameters
    #define _LCDML_cfg_use_ram                   // enable this line when you want to use the ram mode 

    // set the number of custom events (this could be a button ore something else)    
    #define _LCDML_CE_cb_function_cnt           4   // this is the number of custom event callback functions which are supported
                                                    // 4 = default 

    // enable debug strings (remove comments from this line)
    //#define LCDML_DBG                          1

    // debug special method groups          // enable a flag to control the function call order
    #define LCDML_DBG_function_name_LOOP        0
    #define LCDML_DBG_function_name_MENU        0
    #define LCDML_DBG_function_name_FUNC        0
    #define LCDML_DBG_function_name_BT          0
    #define LCDML_DBG_function_name_CE          0
    #define LCDML_DBG_function_name_OTHER       0
    #define LCDML_DBG_function_name_DISP        0
    #define LCDML_DBG_function_name_TIMER       0
    #define LCDML_DBG_function_name_SCREEN      0

    // ####################### //
    // Do nothing change here
    // ####################### //

    // create DBG_print makro when debugging is enabled
    #ifdef LCDML_DBG
        #define DBG_print(enable, str)                  if(enable == 1) { Serial.print(str); }
        #define DBG_println(enable, str)                if(enable == 1) { Serial.println(str); }
        #define DBG_printstrln(enable, str, val, form)  if(enable == 1) { Serial.print(str); Serial.println(val, form);}
    #else
        #define DBG_print(enable, str)
        #define DBG_println(enable, str)
        #define DBG_printstrln(enable, str, val, form)
    #endif

    // Version
    #define _LCDML_VERSION                       "LCDML2 v2.2.6"

    // this makro is for unused variables which exists for compatibility tings ...
    #define LCDML_UNUSED(expr) do { (void)(expr); } while (0)

    // Include Arduino ios
    #include "Arduino.h"

    // Arduino specific settings
    #if ARDUINO >= 160
        #define _LCDMenuLib2_arduino_version         1    // for new Arduino version like 1.6.0 or higher
    #else
        #define _LCDMenuLib2_arduino_version         0    // for old Arduino version like 1.0.6 or 1.0.5
    #endif

    // ESP specific settings
    #if defined( ESP8266 ) || defined( ESP32 )
        #ifndef _LCDML_cfg_use_ram
            #define _LCDML_cfg_use_ram           
        #endif
    #endif

    // 64 this is the number of events which are supported
    #define _LCDML_CE_events_cnt                            64  

    // No function constant
    #define _LCDML_NO_FUNC                                  255
    #define _LCDML_FIRST_ELEMENT_ID                         255

    // Bit pos control flags
    #define _LCDML_REG_control_dynMenuDisplayed             7
    #define _LCDML_REG_control_ce_init_setup                6
    #define _LCDML_REG_control_free_5                       5
    #define _LCDML_REG_control_bt_init_setup                4
    #define _LCDML_REG_control_update_direct                3
    #define _LCDML_REG_control_free_2                       2
    #define _LCDML_REG_control_rollover                     1
    #define _LCDML_REG_control_free_0                       0

    // screensaver, jump to function, go Root, ...
    #define _LCDML_REG_special_setCursorTo                  7
    #define _LCDML_REG_special_OTHER_function_active        6 
    #define _LCDML_REG_special_free_5                       5 
    #define _LCDML_REG_special_free_4                       4 
    #define _LCDML_REG_special_free_3                       3 
    #define _LCDML_REG_special_goRoot                       2 
    #define _LCDML_REG_special_disable_scroll               1 
    #define _LCDML_REG_special_disable_screensaver          0   

    // menu function flags
    #define _LCDML_REG_MenuFunction_called_from_menu        7
    #define _LCDML_REG_MenuFunction_free_6                  6
    #define _LCDML_REG_MenuFunction_free_5                  5
    #define _LCDML_REG_MenuFunction_goBackToFuncID          4
    #define _LCDML_REG_MenuFunction_setCursorToID           3
    #define _LCDML_REG_MenuFunction_close_active            2
    #define _LCDML_REG_MenuFunction_setup                   1
    #define _LCDML_REG_MenuFunction_end                     0

    // button flags
    #define _LCDML_REG_button_custom_2                      7
    #define _LCDML_REG_button_custom_1                      6
    #define _LCDML_REG_button_quit                          5
    #define _LCDML_REG_button_right                         4
    #define _LCDML_REG_button_left                          3
    #define _LCDML_REG_button_down                          2
    #define _LCDML_REG_button_up                            1
    #define _LCDML_REG_button_enter                         0

    // display update handling
    #define _LCDML_REG_update_content                       7
    #define _LCDML_REG_update_cursor                        6
    #define _LCDML_REG_update_menu                          5
    #define _LCDML_REG_update_menu_function_content         4
    #define _LCDML_REG_update_update_dyn_content            3
    #define _LCDML_REG_update_free_2                        2
    #define _LCDML_REG_update_free_1                        1
    #define _LCDML_REG_update_free_0                        0


    // reg control
    #define _LCDML_REG_CTRL__button                         0
    #define _LCDML_REG_CTRL__events                         1


    // Configure Arduino flash lib and load it*/
    #ifndef __PROG_TYPES_COMPAT__
        #define __PROG_TYPES_COMPAT__
    #endif

    // Include PGMSPACE
    #ifndef _LCDML_cfg_use_ram
        #include <avr/pgmspace.h>
    #endif

    // Include menu class
    #include "LCDMenuLib2_typedef.h"
    #include "LCDMenuLib2_menu.h"    

    // Include macros for this lib
    #include "LCDMenuLib2_macros.h"


//# =======================
//# LCD Menu Lib
//# =======================
    class LCDMenuLib2
    {
        private:
            // object pointer            
            LCDMenuLib2_menu *curMenu;                                  // currrent menu structure

            // callback functions
            LCDML_FuncPtr       callback_menuControl;                   // a callback function which checks the input buttons
            LCDML_FuncPtr       callback_contentUpdate;                 // a callback function which contains the menu function content
            LCDML_FuncPtr       callback_contentClear;                  // a callback function which clears the display
            LCDML_FuncPtr_pu8   cb_screensaver;                         // a callback function as screensaver (a normal menu function, but a defined name)

            LCDML_FuncPtr_pu8   ce_cb[_LCDML_CE_cb_function_cnt];       // callback functions for custom events

            // activ menu values
            LCDML_FuncPtr_pu8   actMenu_cb_function;                    // Menu Function callback            
            uint8_t             actMenu_id;                             // Name of this menu
            uint8_t             actMenu_param;                          // Parameter this menu
            uint8_t             actMenu_lastFuncID;                     // History of the last three active menu functions  
            uint8_t             actMenu_cursorPositionID;               // current cursor position id          
            uint8_t             actMenu_lastCursorPositionID;           // Save the last Cursor position before a new function was called
            unsigned long       actMenu_default_time;                   // default loop time 
            uint64_t            actMenu_ce_mask;                        // ce mask          

            // jump To variables          
            uint8_t             jT_id;                                  // contains the jumpTo id
            uint8_t             jT_param;                               // contains the jumpTo param
            uint8_t             jT_paramOld;                            // contains the jumpTo param
            LCDML_FuncPtr_pu8   jT_function;                            // contains the jumpTo function

            // menu intern values
            uint8_t             last_element_id;                        // the id of the last element
            uint8_t             window_rows;                            // the maximum rows of the current windows  (1 is the minium)
            uint8_t             window_start;                           // the window start
            
            uint8_t             cursor_obj_pos;
            uint8_t             cursor_pos;                             // the current cursor position on the lcd
            uint8_t             cursor_pos_abs;

            uint8_t             layer;                                  // contains the current layer
            
            // variables with bitfields => bit register                       
            uint8_t             REG_control;                            // control flags
            uint8_t             REG_button;
            uint8_t             REG_MenuFunction;                       // control flags for menu functions
            uint8_t             REG_special;                            // control flags for special function like screensaver, jumpTo, setCursorTo, goRoot, ..
            uint8_t             REG_update;                             // control flags to update the content 
            uint64_t            REG_custom_event;                       // control flags for custom event actions             
        
            // variables for handling with menu function            
            uint8_t             goBackCnt;                              // save the layer to go back

            // timer variable for the loop time of a menu function
            unsigned long       menuFunction_loopTimer;

            // timer variable for the screensaver
            unsigned long       screensaver_timer;
            unsigned long       screensaver_default_time;

            // private methods
            void                MENU_goInto(void);                                              // activate the menu under the cursor
            void                MENU_goBack(void);                                              // move to the parent menu
            void                MENU_doScroll(uint8_t state);                                   // scroll the menu
            uint8_t             MENU_countChilds(LCDMenuLib2_menu *menu, uint8_t all=false);    // how many children exists on next layer
            void                MENU_initFirstElement(void);                                    // set counter to the first object child
            void                MENU_resetActiveMenu(void);                                     // clear all neccessary variables
                                 
            // callback function
            void                FUNC_call(void);            

        public:

            // constructor
            LCDMenuLib2(LCDMenuLib2_menu &p_r ,const uint8_t p_rows, const uint8_t p_cols, LCDML_FuncPtr contentUpdate, LCDML_FuncPtr contentClear, LCDML_FuncPtr menuControl);            
 
            // init method
            void                init(uint8_t);                          // initialisation of the menu / reset the complete menu

            // loop methods
            void                loop(void);                             // call the loop_menu and the loop_control function
            void                loop_control(void);                     // call callback function to control the menu 
            void                loop_menu(void);                        // checks if a button or a menu function are needed and call them

            // menu methods            
            void                MENU_display(uint8_t update=0);         // update the current menu structure but do not display it
            void                MENU_goRoot(void);                      // set the cursor to the root element
            void                MENU_enRollover(void);                  // enable menu rollover (first to last or last to first element on a layer)
            void                MENU_disRollover(void);                 // disable the menu rollover function
            void                MENU_enScroll(void);                    // enable the scroll function (this function can be disabled on dyn content elements and when a encoder is used)
            void                MENU_disScroll(void);                   // disable the scroll function to catch the cursor to an dyn content element when a encoder is used
            void                MENU_setDynContent(void);               // set this value when dyn content is shown on the current windows of the display                          
            uint8_t             MENU_checkDynContent(void);             // check if dyn content is displayed 
            bool                MENU_getScrollDisableStatus(void);      // check if the scroll function is disabled
            uint8_t             MENU_getLayer(void);                    // get the current layer of the menu
            uint8_t             MENU_getCursorPos(void);                // get the current cursor position (row 0, row 1, row 2, ...)
            uint8_t             MENU_getCursorPosAbs(void);             // get the current cursor postion absolute (..., row 10, row 11, ..) based on the menu structure
            uint8_t             MENU_getChilds(void);                   // get the current number of childs on this layer
            uint8_t             MENU_getParentID(uint8_t p_layer=0);    // get the parent id of a selected higher layer
            uint8_t             MENU_getScroll(void);                   // get the current scroll value
            uint8_t             MENU_getLastActiveFunctionID(void);     // returns the id of the last active function
            uint8_t             MENU_getLastCursorPositionID(void);     // returns the last cursor position function id
            uint8_t             MENU_getElementIDFromCursorPos(void);   // returns the menu element id from current cursor position  
            void                MENU_updateAllCondetions(void);         // update all menu element condetions
            void                MENU_setDynFunctionContentUpdate(void);       // this is a special function for dynamic content to update the dynamic content again
            void                MENU_clearDynFunctionContentUpdate(void);     // this is a special function to clear the dynamic content update. this function is called internally 
            void                MENU_allCondetionRefresh(void);         // update all condetions       

            LCDMenuLib2_menu *  MENU_getDisplayedObj(void);             // get the objection with the current content to display                    
            LCDMenuLib2_menu *  MENU_getCurrentObj(void);               // get the current menu child object
            LCDMenuLib2_menu *  MENU_getRootObj(void);                  // get the root menu object

            // button / event / reg methods
            bool                BT_setup(void);                         // check if the button initialisation was done                     
            bool                CE_setup(void);                         // check if the button initialisation was done
            //
            void                CE_setOnChangeCbFunction(uint8_t p_event, LCDML_FuncPtr_pu8 p_function);    // add callback function
            void                CE_clearOnChangeCbFunction(uint8_t p_event);                                // remove callback function
            //
            void                REG_set(uint8_t p_reg, uint8_t p_val);
            void                REG_reset(uint8_t p_reg, uint8_t p_val);
            void                REG_resetAll(uint8_t p_reg);
            bool                REG_check(uint8_t p_reg, uint8_t p_val);
            bool                REG_checkAny(uint8_t p_reg);           

            // display methods
            void                DISP_update(void);                                  // display the content and update the menu structure 
            void                DISP_menuUpdate(void);                              // display the content but do not update the menu structure
            void                DISP_clear(void);                                   // calls the callback function to clear the display
            bool                DISP_checkMenuUpdate(void);                         // check if there is new content to display
            bool                DISP_checkMenuCursorUpdate(void);                   // check if the cursor was moved
            uint8_t             DISP_getMenuContentId(uint8_t n);                   // get the id`s from a contentelement for a special line 

            // menu function methods
            bool                FUNC_setup(void);                                   // check if a menu function is called the first time to init some things
            bool                FUNC_loop(void);                                    // check if a menu function is running in a loop
            bool                FUNC_close(void);                                   // check if a menu function is closed to reach a stable state                     
            void                FUNC_goBackToMenu(uint8_t p_goBackCnt=0);           // close the current menu function (the FUNC_close check is true when this is set)
            uint8_t             FUNC_getID(void);                                   // get the ID of the current menu function
            void                FUNC_setLoopInterval(unsigned long p_t);            // set a loop intervall for the current menu function the default loop intervall is 100000000 ms
            void                FUNC_disableScreensaver(void);                      // disable the screensaver for the current menu function 
            void                FUNC_setGBAToLastCursorPos(void);                   // set a special "go back handling" - only to last cursor position
            void                FUNC_setGBAToLastFunc(void);                        // set a special "go back handling" - only to last function
            void                FUNC_setGBA(void);                                  // set a special "go back handling" which decide between the two function above 
            void                FUNC_setCEMask(unsigned long p_mask);               // set a mask to enable only special events for a menu function

            // timer methods
            bool                TIMER_ms(unsigned long &p_var, unsigned long p_t);  // a small timer based on the millis() function
            void                TIMER_msReset(unsigned long &p_var);                // reset the millis timer 
            bool                TIMER_us(unsigned long &p_var, unsigned long p_t);  // a small timer based on the micros() function
            void                TIMER_usReset(unsigned long &p_var);                // reset the micros timer

            // other methods
            void                OTHER_jumpToFunc(LCDML_FuncPtr_pu8 p_search, uint8_t p_para = 0);   // jumpTo a defined function based on the function name 
            void                OTHER_jumpToID(uint8_t p_search, uint8_t p_para = 0);               // jumpTo a defined function based on the function id
            void                OTHER_setCursorToID(uint8_t p_search);                              // set the cursor to a defined function based on the id
            void                OTHER_setCursorToFunc(LCDML_FuncPtr_pu8 p_search);                  // set the cursor to a defined function based on the function name
            uint8_t             OTHER_getIDFromFunction(LCDML_FuncPtr_pu8 p_search);                // get the menu element id from a menu element function name 
            void                OTHER_updateFunc(void);                                             // update a menu function directly when it runs             

            // screensaver methods
            void                SCREEN_enable(LCDML_FuncPtr_pu8 p_function, unsigned long p_t);     // enable the screensaver feature 
            void                SCREEN_disable(void);                                               // disable the screensaver feature
            void                SCREEN_resetTimer(void);                                            // reset the screensaver timer 
            void                SCREEN_start(void);                                                 // start the screensaver directly
    };
#endif
