/******************************************************************************
 *
 * Copyright (c) 2017, the Perspective Authors.
 *
 * This file is part of the Perspective library, distributed under the terms of
 * the Apache License 2.0.  The full license can be found in the LICENSE file.
 *
 */

#pragma once
#include <perspective/base.h>
#include <perspective/comparators.h>
#include <perspective/shared_ptrs.h>
#include <perspective/scalar.h>
#include <perspective/exports.h>
#include <functional>
#include <set>

namespace perspective
{

// Filter operators
template <typename DATA_T, template <typename> class OP_T>
struct t_operator_base
{
    inline bool
    operator()(DATA_T value, DATA_T threshold_value)
    {
        OP_T<DATA_T> cmp;
        return cmp(value, threshold_value);
    }

    inline bool
    operator()(const char* value, const char* threshold_value)
    {
        t_const_char_comparator<OP_T> cmp;
        return cmp(value, threshold_value);
    }
};

template <typename DATA_T>
using t_operator_lt = t_operator_base<DATA_T, std::less>;

template <typename DATA_T>
using t_operator_lteq = t_operator_base<DATA_T, std::less_equal>;

template <typename DATA_T>
using t_operator_gt = t_operator_base<DATA_T, std::greater>;

template <typename DATA_T>
using t_operator_gteq = t_operator_base<DATA_T, std::greater_equal>;

template <typename DATA_T>
using t_operator_ne = t_operator_base<DATA_T, std::not_equal_to>;

template <typename DATA_T>
using t_operator_eq = t_operator_base<DATA_T, std::equal_to>;

template <typename DATA_T, int DTYPE_T>
struct t_operator_in
{
    inline bool
    operator()(const DATA_T& value, const std::set<DATA_T>& threshold_values)
    {
        return threshold_values.find(value) != threshold_values.end();
    }
};

template <typename DATA_T, int DTYPE_T>
struct t_operator_not_in
{
    inline bool
    operator()(const DATA_T& value, const std::set<DATA_T>& threshold_values)
    {
        return threshold_values.find(value) == threshold_values.end();
    }
};

template <typename DATA_T, int DTYPE_T>
struct t_operator_begins_with
{
    inline bool
    operator()(DATA_T value, DATA_T threshold_value)
    {
        return false;
    }
};

template <typename DATA_T, int DTYPE_T>
struct t_operator_ends_with
{
    inline bool
    operator()(DATA_T value, DATA_T threshold_value)
    {
        return false;
    }
};

template <typename DATA_T, int DTYPE_T>
struct t_operator_contains
{
    inline bool
    operator()(DATA_T value, DATA_T threshold_value)
    {
        return false;
    }
};

template <>
struct t_operator_begins_with<const t_char*, DTYPE_STR>
{
    inline bool
    operator()(const t_char* value, const t_char* threshold_value)
    {
        t_uindex t_vlen = strlen(value);
        t_uindex t_tlen = strlen(threshold_value);
        return t_vlen < t_tlen ? false
                               : strncmp(value, threshold_value, t_tlen) == 0;
    }
};

template <>
struct t_operator_ends_with<t_uindex, DTYPE_STR>
{
    inline bool
    operator()(const t_char* value, const t_char* threshold_value)
    {
        t_uindex t_vlen = strlen(value);
        t_uindex t_tlen = strlen(threshold_value);

        return t_vlen < t_tlen
            ? false
            : strncmp(value + t_vlen - t_tlen, threshold_value, t_tlen) == 0;
    }
};

template <>
struct t_operator_contains<t_uindex, DTYPE_STR>
{
    inline bool
    operator()(const t_char* value, const t_char* threshold_value)
    {
        return strstr(value, threshold_value) != 0;
    }
};

struct PERSPECTIVE_EXPORT t_fterm_recipe
{
    t_fterm_recipe() {}

    t_str m_colname;
    t_filter_op m_op;
    t_tscalar m_threshold;
    t_tscalvec m_bag;
};

typedef std::vector<t_fterm_recipe> t_fterm_recipevec;

struct PERSPECTIVE_EXPORT t_fterm
{
    t_fterm();

    t_fterm(const t_fterm_recipe& v);

    t_fterm(const t_str& colname, t_filter_op op, t_tscalar threshold,
        const t_tscalvec& bag);

    t_fterm(const t_str& colname, t_filter_op op, t_tscalar threshold,
        const t_tscalvec& bag, t_bool negated, t_bool is_primary);

    inline bool
    operator()(t_tscalar s) const
    {
        t_bool rv;
        switch (m_op)
        {
            case FILTER_OP_NOT_IN:
            {
                rv = std::find(m_bag.begin(), m_bag.end(), s) == m_bag.end();
            }
            break;
            case FILTER_OP_IN:
            {
                rv = std::find(m_bag.begin(), m_bag.end(), s) != m_bag.end();
            }
            break;
            default:
            {
                rv = s.cmp(m_op, m_threshold);
            }
            break;
        }

        return m_negated ? (!rv) : rv;
    }

    t_str get_expr() const;

    void coerce_numeric(t_dtype dtype);
    t_fterm_recipe get_recipe() const;

    t_str m_colname;
    t_filter_op m_op;
    t_tscalar m_threshold;
    t_tscalvec m_bag;
    t_bool m_negated;
    t_bool m_is_primary;
    t_bool m_use_interned;
};

typedef std::vector<t_fterm> t_ftermvec;

class PERSPECTIVE_EXPORT t_filter
{
public:
    t_filter();
    t_filter(const std::vector<t_str>& columns);

    t_filter(const std::vector<t_str>& columns, t_uindex bidx, t_uindex eidx);

    t_filter(const std::vector<t_str>& columns, t_uindex mask_size);
    t_filter(const t_mask& mask);

    t_uindex num_cols() const;
    bool has_filter() const;
    const std::vector<t_str>& columns() const;
    t_select_mode mode() const;
    t_uindex bidx() const;
    t_uindex eidx() const;
    t_maskcsptr cmask() const;
    t_masksptr mask() const;
    t_uindex count() const;

private:
    t_select_mode m_mode;
    t_uindex m_bidx;
    t_uindex m_eidx;
    std::vector<t_str> m_columns;
    t_masksptr m_mask;
};

} // end namespace perspective
