#ifndef BEACHMAT_CHARACTER_OUTPUT_H
#define BEACHMAT_CHARACTER_OUTPUT_H

#include "all_writers.h"
#include "output_param.h"

namespace beachmat { 

/* Virtual base class for character matrices. */

class character_output {
public:    
    character_output() = default;
    virtual ~character_output() = default;
    character_output(const character_output&) = default;
    character_output& operator=(const character_output&) = default;
    character_output(character_output&&) = default;
    character_output& operator=(character_output&&) = default;
    
    virtual size_t get_nrow() const=0;
    virtual size_t get_ncol() const=0;

    // Getters    
    void get_row(size_t, Rcpp::StringVector::iterator); 
    virtual void get_row(size_t, Rcpp::StringVector::iterator, size_t, size_t)=0;

    void get_col(size_t, Rcpp::StringVector::iterator);
    virtual void get_col(size_t, Rcpp::StringVector::iterator, size_t, size_t)=0;

    virtual Rcpp::String get(size_t, size_t)=0;

    // Setters
    void set_row(size_t, Rcpp::StringVector::iterator); 
    virtual void set_row(size_t, Rcpp::StringVector::iterator, size_t, size_t)=0;

    void set_col(size_t, Rcpp::StringVector::iterator);
    virtual void set_col(size_t, Rcpp::StringVector::iterator, size_t, size_t)=0;

    virtual void set(size_t, size_t, Rcpp::String)=0;

    virtual void set_col_indexed(size_t, size_t, Rcpp::IntegerVector::iterator, Rcpp::StringVector::iterator)=0;

    virtual void set_row_indexed(size_t, size_t, Rcpp::IntegerVector::iterator, Rcpp::StringVector::iterator)=0;

    // Other stuff.
    virtual Rcpp::RObject yield()=0;

    virtual std::unique_ptr<character_output> clone() const=0;

    virtual matrix_type get_matrix_type() const=0;
};

/* Simple character matrix */

class simple_character_output : public character_output {
public:
    simple_character_output(size_t, size_t);
    ~simple_character_output() = default;
    simple_character_output(const simple_character_output&) = default;
    simple_character_output& operator=(const simple_character_output&) = default;
    simple_character_output(simple_character_output&&) = default;
    simple_character_output& operator=(simple_character_output&&) = default;

    size_t get_nrow() const;
    size_t get_ncol() const;
 
    void get_row(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    void get_col(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    Rcpp::String get(size_t, size_t);

    void set_row(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    void set_col(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    void set(size_t, size_t, Rcpp::String);
    void set_col_indexed(size_t, size_t, Rcpp::IntegerVector::iterator, Rcpp::StringVector::iterator);
    void set_row_indexed(size_t, size_t, Rcpp::IntegerVector::iterator, Rcpp::StringVector::iterator);

    Rcpp::RObject yield();
    std::unique_ptr<character_output> clone() const;
    matrix_type get_matrix_type() const;
private:
    simple_writer<Rcpp::String, Rcpp::StringVector> writer;
};

/* HDF5Matrix */

class HDF5_character_output : public character_output {
public:   
    HDF5_character_output(size_t, size_t, 
            size_t=output_param::DEFAULT_STRLEN, 
            size_t=output_param::DEFAULT_CHUNKDIM, 
            size_t=output_param::DEFAULT_CHUNKDIM, 
            int=output_param::DEFAULT_COMPRESS);
    ~HDF5_character_output() = default;
    HDF5_character_output(const HDF5_character_output&) = default;
    HDF5_character_output& operator=(const HDF5_character_output&) = default;
    HDF5_character_output(HDF5_character_output&&) = default;
    HDF5_character_output& operator=(HDF5_character_output&&) = default;

    size_t get_nrow() const;
    size_t get_ncol() const;
 
    void get_row(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    void get_col(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    Rcpp::String get(size_t, size_t);

    void set_row(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    void set_col(size_t, Rcpp::StringVector::iterator, size_t, size_t);
    void set(size_t, size_t, Rcpp::String);
    void set_col_indexed(size_t, size_t, Rcpp::IntegerVector::iterator, Rcpp::StringVector::iterator);
    void set_row_indexed(size_t, size_t, Rcpp::IntegerVector::iterator, Rcpp::StringVector::iterator);

    Rcpp::RObject yield();
    std::unique_ptr<character_output> clone() const;
    matrix_type get_matrix_type() const;
protected:
    H5::StrType str_type;
    size_t bufsize;
    HDF5_writer<char, STRSXP> writer; 
    std::vector<char> buffer;
};

/* Dispatcher */

std::unique_ptr<character_output> create_character_output(int, int, const output_param&);

}

#endif
