#ifndef CLI_LINE_HPP
#define CLI_LINE_HPP
/*
 * ru utf8
 *
 *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - 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.
   - Neither the name of ARM 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 COPYRIGHT HOLDERS AND 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. *
   -------------------------------------------------------------------------
   this is a part of MCU HAL cli library
*/

/*
 * структуры для строки ввода shell
*/

#include "cli.hpp"



//----------------------------------------------------------------------------
// минимальный API командной строки консоли
struct ICLILine {
    public:
        typedef cli::Char*   line_p;
        typedef cli::Char&   line_t;
        void reserve(unsigned _Count);
        unsigned capacity() const;
        unsigned size() const;
        void     resize(unsigned x);
        line_p  begin() const;
        line_p  end() const;
        line_t  front() const;
        line_t  back() const;
        void    push_back(char x);
        void    push_back(const char* x, unsigned len);
        void    drop(line_p from, unsigned count);
        void    drop_head(unsigned count);
        bool    is_in(cli::const_line x) const;

};




class CLIVectorLine : public std::vector <cli::Char, SystemHeap <cli::Char> >
                    //, public ICLILine
{
    public:
        typedef std::vector <cli::Char, SystemHeap <cli::Char> > inherited;
        typedef cli::Char*   line_p;

        // drops count chars from position dst
        void drop( cli::Char* dst , unsigned count);
        void drop_head(unsigned count);
        bool is_in(cli::const_line x) const {
            return ( ( &(*cbegin()) <= x) && (x <= &(*cend()) ) );
        }

        using inherited::push_back;
        void push_back(const char* x, unsigned len){
            if (size() + len > capacity()){
                len = capacity() - size();
            }
            unsigned wasend = size();
            resize(size() + len);
            //strncpy( &(*this)[0] + wasend, x, len);
            memcpy(&((*this)[0]) + wasend, x, len);
            back() = '\0';
        }
};



namespace cli{

    // это описатель внешнего буфера для CLIBufLine.
    //чтото вроде обрезка stl::string_view
    struct LineBufView {
        char*       store;
        unsigned    limit;

        char& operator[](unsigned x){return store[x];}
        char* operator+(int x) {return store+x;}
        const char* operator+(int x) const {return store+x;}

        LineBufView() = default;
        LineBufView(char* x, unsigned size):store(x), limit(size){}
        LineBufView(LineBufView& x):store(x.store), limit(x.limit){}
        LineBufView(CLIVectorLine& x):store(x.data()), limit(x.capacity()){}

        template< int size >
        LineBufView(char x[size]):store(x), limit(size){}


        LineBufView& assign(char* x, unsigned size){
            store = x; limit = size;
            return *this;
        }

        LineBufView& assign(CLIVectorLine& x){
            store = x.data(); limit = x.capacity();
            return *this;
        }

        template<unsigned size>
        LineBufView& assign(char x[size]){
            store = x; limit = size;
            return *this;
        }

    };

    // это описатель явного буфера для CLIBufLine.
    template<unsigned buf_limit>
    struct LineBufStore{
        char store[buf_limit];
        enum {limit = buf_limit};
        char& operator[](unsigned x){return store[x];}
        char* operator+(int x) {return store+x;}
        const char* operator+(int x) const {return store+x;}
    };

    
    template<typename TStore>
    struct LineBuf: public ICLILine
    {
        public:
            typedef TStore store_t;
            TStore      line;
            unsigned len;

            void reserve(unsigned _Count){(void)_Count;}
            unsigned size() const{ return len;}
            unsigned capacity() const {return line.limit; }
            void resize(unsigned x) {len = x;}
            line_p  begin() const{return (line_p)(line+0); }
            line_p  end() const {return (line_p)(line+len); }
            line_t  front() const { return *begin(); }
            line_t  back() const { return *end(); }
            char operator[](unsigned _Pos) const{
                return line[_Pos];
            }
            void push_back(char x) {line[len++] = x; line[len] = '\0'; }

            void push_back(const char* x, unsigned len){
                if (size() + len > capacity()){
                    len = capacity() - size();
                }
                unsigned wasend = size();
                resize(size() + len);
                //strncpy( line + wasend, x, len);
                memcpy( begin() + wasend, x, len);
                back() = '\0';
            }

            bool is_in(const_line x) const {
                return (( &front() <= x) 
                        // treat after buffer, as part of buffer. at that pos places \z
                        && (&back() >= x) //  (&back() > x)
                        );
            }
    
            void    drop(line_p from, unsigned count){
                CLI_ASSERT( from >= line+0 );
                CLI_ASSERT( (from-begin()) < capacity() );
                if ( ((from-begin()) + count) >= size() ){
                    len = from-begin();
                    return;
                }
                memcpy( from, from+count, end() - (from+count) );
                len -= count;
            }
    
            void drop_head(unsigned count){
                if ( count >= len ){
                    len = 0;
                    return;
                }
                len -= count;
                memcpy( line+0, line+count, len );
            }



            LineBuf(): len(0) {}

            // constructors for LineBufView store
            template<int dummy = 1>
            LineBuf(char* x, unsigned size):line(x,size),len(0){}
            template< int size >
            LineBuf(char x[size]):line(x),len(0){}

    };

    // буфер строки на внешнем массиве
    typedef  LineBuf<LineBufView> LineView;



    struct ICLILineEdit {
    public:
        virtual bool input(int ch) = 0;
        virtual void input_clear() = 0;

        bool input(const char* s, unsigned len){
            bool ok = false;
            for( ; len > 0; --len){
                ok = input( *s++ );
            }
            return ok;
        }

        void edit_by(ICLILineEdit* e) {editor = e;}
        ICLILineEdit* edit(){return editor;}

    protected:
        ICLILineEdit* editor;
    };

};

template<unsigned buf_limit>
using CLIBufLine = cli::LineBuf< cli::LineBufStore<buf_limit> >;

typedef cli::LineView   CLIViewLine;



#include "lib/EnumClass.h"
#include "vt_hal.hpp"

//class VTerminalBaseDevice;
//class StdIO_Device;

// терминал. обеспечивает ввод строки с поддержкой backspace
class CLI_LineInput : public cli::ICLILineEdit // basic LineEdit for CLI
{
public:

    CLI_LineInput(CLIVectorLine& _line);
    CLI_LineInput(CLIViewLine& _line);

    template<typename T>
    CLI_LineInput(T& _line) : line(_line) {
        edit_by(this);
        clear();
    }

    virtual ~CLI_LineInput();

public:
    typedef VTerminalBaseDevice io_t;

    void init(io_t* adevice);

    io_t* io(){return device;}

    void prompt(cli::const_line str){prompt_str = str;};

public:
    CLIViewLine  line;      // cmdline buffer
    char*        cursor;
    unsigned        cursor_len;     //количество текста за курсором
    volatile bool   line_ok;        //строка завершена
    char            eol;            //пользовательский EOL
    char            ackeol;         //ответный EOL
    cli::const_line prompt_str;

    unsigned cursor_pos() const {
        // CLI_ASSERT( (cursor >= &(*line.begin())) );
        return (unsigned)( cursor - &(*line.begin()) ) ;
    }
    // общая длинна введенной строки
    unsigned len() const { return cursor_pos() + cursor_len;}

public:
    enum ModeID{
        mNONE        = 0,
        // разрешает писать в строку управляющие символы
        mNO_CONTROLS = 1, 
        mNO_ECHO     = 2,
    };

    bitmask<ModeID> mode;

public:
    void clear();

    // отбрасывает строку из буфера
    // @return true - есть завершенная строка завершена
    bool drop_head(unsigned len);

    //неблокирующее считывание из io строку до завершения.
    // @return true - введенная строка завершена
    bool input_io();

    //* добавляет символ в начало строки
    //void unwindChar(u8 ch);

    // @return < 0 - failed io operation
    int echo(char x);
    int echo(const char* x);
    int echo(const char* x, unsigned len);
    //< показывает приглашение перед вводом
    int echo_prompt();

public: // ICLILineEdit

    // !!! это не элемент CLI_LineInput, а реализация LineEdit
    //          он должен вызываеться через CLI_LineInput:edit()

    //* добавляет символ в строку, и завершает команду на RETURN или /0
    //  строка завершается символом LN
    //  отрабатывает BACKSPACE
    // @return true - введенная строка завершена
    using   ICLILineEdit::input;
    virtual bool input(int ch);

    virtual void input_clear();

protected :
    // @return false - буффер заполнен, невозможно положить символ
    bool put_cursor(char x);
    io_t*   device;
};

enableEnumClassBitmask(CLI_LineInput::ModeID);


//==============================================================================
namespace cli{

struct ILinesHystory {
    virtual void clear() = 0;
    // @return true - appends success
    virtual bool append(const_line from, unsigned len) = 0;
    // @ arg = nullptr - return first item
    virtual const_line pred(const_line from) const = 0;
    // @ arg = nullptr - return last item
    virtual const_line next(const_line from) const = 0;
    virtual void move_2top(const_line x) = 0;

    const_line begin() const {return pred(nullptr);}
    const_line end()   const {return next(nullptr);}

    const_line find_from(const_line x, unsigned len, const_line from = nullptr){
        if (from  == nullptr)
            from = pred(nullptr);
        for ( ; from != nullptr; from = next(from)){
            if (strncmp( x, from, len) == 0)
                return from;
        }
        return nullptr;
    }
};

template<typename TStore>
struct HystoryBuffer : public ILinesHystory {

    typedef TStore store_t;

    HystoryBuffer() {}
    virtual ~HystoryBuffer() {}

    virtual void clear(){
        store.resize(0);
    };

    virtual bool append(const_line from, unsigned len){
        if ( len > store.capacity() )
            //can`t add such long line
            return false;
        // drop oldest lines, to room new line
        while ( (store.size() + len) > store.capacity() ){
            if (store.size() == 0)
                break;
            unsigned lastlen = strnlen(store.begin(), store.size());
            store.drop_head(lastlen);
        }
        if (len > 0) {
            store.push_back(from, len);
            if( from[len-1] != '\0')
                store.push_back('\0');
        }
        return true;
    }

    // @ arg = nullptr - return first item
    virtual const_line pred(const_line from) const {
        if(store.size() <= 0)
            return nullptr;
        if (from ==nullptr)
            return store.begin();
        if (! store.is_in(from))
            return nullptr;

        if ( from <= store.begin() )
            return nullptr;

        // search pred line end
        const_line predend = pred_eol(from);
        if (predend == nullptr)
            return store.begin();
        // search pred line start
        const_line preds = pred_eol(predend);
        if (preds == nullptr)
            return store.begin();

        return after_eol(preds);
    }

    // @ arg = nullptr - return last item
    virtual const_line next(const_line from) const {
        
        if(store.size() <= 0)
            return nullptr;
        if (from ==nullptr)
            return store.end();
        if (! store.is_in(from))
            return nullptr;

        const_line nexts = from + line_len(from, store.end()-from);
        return after_eol(nexts);
    }

    const_line pred_eol(const_line from) const {
        const_line predline = (const_line) memrchr( store.begin(), '\n', from - store.begin());
        const_line predz = (const_line) memrchr( store.begin(), '\0', from - store.begin());
        if (predz > predline)
            return predz;
        return predline;
    }

    const_line after_eol(const_line from) const {
        for ( ; from < store.end() ; from++) 
            if (is_eol(from)< 0)
                return from;
        return nullptr;
    }

    virtual void move_2top(const_line x){
        if (! store.is_in(x))
            return;
        const_line n = next(x);
        //if it is last line - nothing move
        if (n == nullptr)        return;
        int len  = line_len(x, n-x);
        if (append(x, len)) {
            store.drop((cli::Char*)x, (n-x) );
        }
    }

    TStore store;
};

};//namespace cli



//==============================================================================
namespace cli{

struct CLIEditHistory : public ICLILineEdit {
public:
    CLIEditHistory(CLI_LineInput& li, ILinesHystory& hys) 
        : self(li) 
        , history(hys)
        , history_line(nullptr)
        , controlSymbolIndex(0)
    {
        edit_by(&li);
    }
    virtual ~CLIEditHistory(){}

    CLI_LineInput* line() {return &self;}
    using   ICLILineEdit::input;
    virtual bool input(int ch);
    virtual void input_clear();

    bool hystory_control(int ch);
    void hystory_append();
    void hystory_2top( const_line x);

protected:
    CLI_LineInput& self;
    ILinesHystory& history;
    const_line     history_line;
    enum ControlChar{
        ccESC = 0x1b ,
    };
    
    //static const char CSIMark[2];
    unsigned char  controlSymbolIndex;            ///< Индекс сравнения контрольного символа                
};

};//namespace cli



#endif // CLI_LINE_HPP
