/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#pragma once

#include <queue>
#include <deque>
#include <list>
#include "proxy_layer.h"
#include "geo/lib/geo_client.h"

namespace dsn {
namespace apps {
class rrdb_client;
}
}

class proxy_test;

namespace pegasus {
namespace proxy {

// http://redis.io/topics/protocol
class redis_parser : public proxy_session
{
protected:
    friend class ::proxy_test;

    struct redis_base_type
    {
        virtual ~redis_base_type() = default;
        virtual void marshalling(::dsn::binary_writer &write_stream) const = 0;
    };
    struct redis_integer : public redis_base_type
    {
        int64_t value = 0;

        explicit redis_integer(int64_t v = 0) : value(v) {}

        void marshalling(::dsn::binary_writer &write_stream) const final;
    };
    // represent both redis simple string and error
    struct redis_simple_string : public redis_base_type
    {
        bool is_error = false;
        std::string message;

        redis_simple_string(bool err, std::string &&msg) : is_error(err), message(std::move(msg)) {}

        void marshalling(::dsn::binary_writer &write_stream) const final;
    };
    struct redis_bulk_string : public redis_base_type
    {
        int length = -1; // max length is 512 MB
        ::dsn::blob data;

        redis_bulk_string() = default;
        redis_bulk_string(std::string str)
        {
            data = ::dsn::blob::create_from_bytes(std::move(str));
            length = data.length();
        }
        explicit redis_bulk_string(const ::dsn::blob &bb) : length(bb.length()), data(bb) {}

        void marshalling(::dsn::binary_writer &write_stream) const final;
    };
    struct redis_array : public redis_base_type
    {
        int count = -1;
        std::vector<std::shared_ptr<redis_base_type>> array;

        void resize(size_t size)
        {
            count = size;
            array.resize(size);
        }

        void marshalling(::dsn::binary_writer &write_stream) const final;
    };

    struct redis_request
    {
        int sub_request_count = 0;
        std::vector<redis_bulk_string> sub_requests;

        redis_request(int count = 0, std::vector<redis_bulk_string> requests = {})
            : sub_request_count(count), sub_requests(std::move(requests))
        {
        }
    };
    struct message_entry
    {
        redis_request request;
        std::atomic<dsn::message_ex *> response;
        int64_t sequence_id = 0;
    };

    bool parse(dsn::message_ex *msg) override;

    // this is virtual only because we can override and test other modules
    virtual void handle_command(std::unique_ptr<message_entry> &&entry);

private:
    // queue for pipeline the response
    dsn::zlock response_lock;
    std::deque<std::unique_ptr<message_entry>> pending_response;

    enum parser_status
    {
        kStartArray,
        kInArraySize,
        kStartBulkString,
        kInBulkStringSize,
        kStartBulkStringData,
    };
    // recieving message and parsing status
    // [
    // content for current parser
    redis_bulk_string _current_str;
    std::unique_ptr<message_entry> _current_msg;
    parser_status _status;
    std::string _current_size;

    // data stream content
    std::queue<dsn::message_ex *> _recv_buffers;
    size_t _total_length;
    char *_current_buffer;
    size_t _current_buffer_length;
    size_t _current_cursor;
    // ]

    // for rrdb
    std::unique_ptr<::dsn::apps::rrdb_client> client;
    std::unique_ptr<geo::geo_client> _geo_client;

protected:
    // function for data stream
    void append_message(dsn::message_ex *msg);
    void prepare_current_buffer();
    char peek();
    bool eat(char c);
    void eat_all(char *dest, size_t length);
    void reset_parser();

    // function for parser
    bool end_array_size();
    bool end_bulk_string_size();
    void append_current_bulk_string();
    bool parse_stream();

// function for rrdb operation
#define DECLARE_REDIS_HANDLER(function_name)                                                       \
    void function_name(message_entry &req);                                                        \
    static void g_##function_name(redis_parser *_this, message_entry &req)                         \
    {                                                                                              \
        _this->function_name(req);                                                                 \
    }

    DECLARE_REDIS_HANDLER(set)
    DECLARE_REDIS_HANDLER(get)
    DECLARE_REDIS_HANDLER(del)
    DECLARE_REDIS_HANDLER(setex)
    DECLARE_REDIS_HANDLER(ttl)
    DECLARE_REDIS_HANDLER(geo_add)
    DECLARE_REDIS_HANDLER(geo_dist)
    DECLARE_REDIS_HANDLER(geo_pos)
    DECLARE_REDIS_HANDLER(geo_radius)
    DECLARE_REDIS_HANDLER(geo_radius_by_member)
    DECLARE_REDIS_HANDLER(incr)
    DECLARE_REDIS_HANDLER(incr_by)
    DECLARE_REDIS_HANDLER(decr)
    DECLARE_REDIS_HANDLER(decr_by)
    DECLARE_REDIS_HANDLER(default_handler)

    void set_internal(message_entry &entry);
    void set_geo_internal(message_entry &entry);
    void del_internal(message_entry &entry);
    void del_geo_internal(message_entry &entry);
    void counter_internal(message_entry &entry);
    static void parse_set_parameters(const std::vector<redis_bulk_string> &opts, int &ttl_seconds);
    static void parse_geo_radius_parameters(const std::vector<redis_bulk_string> &opts,
                                            int base_index,
                                            double &radius_m,
                                            std::string &unit,
                                            geo::geo_client::SortType &sort_type,
                                            int &count,
                                            bool &WITHCOORD,
                                            bool &WITHDIST,
                                            bool &WITHHASH);
    void process_geo_radius_result(message_entry &entry,
                                   const std::string &unit,
                                   bool WITHCOORD,
                                   bool WITHDIST,
                                   bool WITHHASH,
                                   int ec,
                                   std::list<geo::SearchResult> &&results);

    // function for pipeline reply
    void enqueue_pending_response(std::unique_ptr<message_entry> &&entry);
    void fetch_and_dequeue_messages(std::vector<dsn::message_ex *> &msgs, bool only_ready_ones);
    void clear_reply_queue();
    void reply_all_ready();

    template <typename T>
    void reply_message(message_entry &entry, const T &value)
    {
        dsn::message_ex *resp = create_response();
        // release in reply_all_ready or reset
        resp->add_ref();

        dsn::rpc_write_stream s(resp);
        value.marshalling(s);
        s.commit_buffer();

        entry.response.store(resp, std::memory_order_release);
        reply_all_ready();
    }

    std::shared_ptr<redis_bulk_string> construct_bulk_string(double data);
    void simple_ok_reply(message_entry &entry);
    void simple_error_reply(message_entry &entry, const std::string &message);
    void simple_string_reply(message_entry &entry, bool is_error, std::string message);
    void simple_integer_reply(message_entry &entry, int64_t value);

    typedef void (*redis_call_handler)(redis_parser *, message_entry &);
    static std::unordered_map<std::string, redis_call_handler> s_dispatcher;
    static redis_call_handler get_handler(const char *command, unsigned int length);
    static std::atomic_llong s_next_seqid;

    static const char CR;
    static const char LF;

public:
    redis_parser(proxy_stub *op, dsn::message_ex *first_msg);
    ~redis_parser() override;
};
}
} // namespace
