/*
    Copyright (c) 2008-2024 Jan W. Krieger (<jan@jkrieger.de>)

    

    This software is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License (LGPL) as published by
    the Free Software Foundation, either version 2.1 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License (LGPL) for more details.

    You should have received a copy of the GNU Lesser General Public License (LGPL)
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/



#include "jkqtplotter/jkqtpdatastorage.h"
#include <QDebug>
#include <QtGlobal>
#include <limits>
#include <cmath>
#include <cstring>

/**************************************************************************************************************************
 * JKQTPColumn
 **************************************************************************************************************************/
////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumn::JKQTPColumn()
{
    datastore=nullptr;
    name="";
    datastoreItem=0;
    datastoreOffset=0;
    imageColumns=1;
    valid=false;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumn::JKQTPColumn(JKQTPDatastore *datastore, const QString &name, size_t datastoreItem, size_t datastoreOffset, size_t imageColumns)
{
    this->datastore=datastore;
    this->datastoreItem=datastoreItem;
    this->datastoreOffset=datastoreOffset;
    this->imageColumns=imageColumns;
    this->name=name;
    valid=true;

}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::setName(const QString &__value)
{
    this->name = __value;
}

////////////////////////////////////////////////////////////////////////////////////////////////
QString JKQTPColumn::getName() const
{
    return this->name;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::setImageColumns(size_t imageWidth)
{
    imageColumns=imageWidth;
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPColumn::getRows() const {
    if (!valid || !datastore) return 0;
    JKQTPDatastoreItem* i=datastore->getItem(datastoreItem);
    if (i) return i->getRows();
    else return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::copyData(QVector<double> &copyTo) const
{
    const size_t cnt=getRows();
    if (cnt>0) {
        const double* p = getPointer(0);
        if (p) {
            // contiguous memory available: fast memcpy path
            copyTo.resize(static_cast<int>(cnt));
            std::memcpy(copyTo.data(), p, cnt * sizeof(double));
        } else {
            copyTo.resize(static_cast<int>(cnt));
            for (size_t i=0; i<cnt; i++) {
                copyTo[static_cast<int>(i)] = getValue(i);
            }
       }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
QVector<double> JKQTPColumn::copyData()
{
    QVector<double> d;
    copyData(d);
    return d;
}


////////////////////////////////////////////////////////////////////////////////////////////////
const double *JKQTPColumn::getPointer(size_t n) const
{
    if (!datastore) return nullptr;
    if (!datastore->getItem(datastoreItem)) return nullptr;
    return datastore->getItem(datastoreItem)->getPointer(datastoreOffset, n);
}

////////////////////////////////////////////////////////////////////////////////////////////////
double *JKQTPColumn::getPointer(size_t n)
{
    if (!datastore) return nullptr;
    if (!datastore->getItem(datastoreItem)) return nullptr;
    return datastore->getItem(datastoreItem)->getPointer(datastoreOffset, n);
}


////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::copy(const double *data, size_t N, size_t offset) {
    if (!datastore) return ;
    JKQTPDatastoreItem* it=datastore->getItem(datastoreItem);
    if (!it) return;
    for (size_t i=0; i<N; i++) {
        it->set(datastoreOffset, i+offset, data[i]);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::exchange(double value, double replace)
{
    if (!datastore) return ;
    for (size_t i=0; i<getRows(); i++) {
        double v=getValue(i);
        if (v==value) v=replace;
        setValue(i, v);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::subtract(double value)
{
    if (!datastore) return ;
    double* data=getPointer();
    const size_t N=getRows();
    if (data){
        for (size_t i=0; i<N; i++) {
            data[i]=data[i]-value;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::scale(double factor)
{
    if (!datastore) return ;
    double* data=getPointer();
    size_t N=getRows();
    if (data){
        for (size_t i=0; i<N; i++) {
            data[i]=data[i]*factor;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPColumn::setAll(double value)
{
    if (!datastore) return ;
    double* data=getPointer();
    size_t N=getRows();
    if (data){
        for (size_t i=0; i<N; i++) {
            data[i]=value;
        }
    }
}


/**************************************************************************************************************************
 * JKQTPDatastoreItem
 **************************************************************************************************************************/
////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::JKQTPDatastoreItem(){
    this->dataformat=JKQTPDatastoreItemFormat::SingleColumn;
    this->allocated=false;
    this->data=nullptr;
    this->columns=0;
    this->rows=0;
    this->storageType=StorageType::Internal;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::JKQTPDatastoreItem(size_t columns, size_t rows){
    this->storageType=StorageType::Internal;
    this->allocated=false;
    if (columns>1) {
        this->dataformat=JKQTPDatastoreItemFormat::MatrixRow;
        if (rows>0) {
            this->data=static_cast<double*>(calloc(columns*rows, sizeof(double)));
        } else {
            this->data=nullptr;
        }
    } else {
        this->dataformat=JKQTPDatastoreItemFormat::SingleColumn;
        this->storageType=StorageType::Vector;
        if (rows>0) {
            datavec.resize(static_cast<int>(columns*rows));
            std::fill(datavec.begin(), datavec.end(), 0.0);
        } else {
            datavec.reserve(10);
        }
        this->data=datavec.data();

    }
    this->columns=columns;
    this->rows=rows;
    this->allocated=true;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::JKQTPDatastoreItem(const QVector<double>& data_)
{
    this->storageType=StorageType::Internal;
    this->allocated=false;
    this->dataformat=JKQTPDatastoreItemFormat::SingleColumn;
    this->storageType=StorageType::Vector;
    this->datavec=data_;
    this->data=datavec.data();
    this->columns=1;
    this->rows=static_cast<int>(data_.size());
    this->allocated=true;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::JKQTPDatastoreItem(QVector<double>&& data_)
{
    this->storageType=StorageType::Internal;
    this->allocated=false;
    this->dataformat=JKQTPDatastoreItemFormat::SingleColumn;
    this->storageType=StorageType::Vector;
    this->datavec=std::forward<QVector<double>>(data_);
    this->data=this->datavec.data();
    this->columns=1;
    this->rows=static_cast<int>(this->datavec.size());
    this->allocated=true;
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPDatastoreItem::resizeColumns(size_t new_rows) {
    bool dataRetained=false;
    if (storageType==StorageType::Internal && allocated && data!=nullptr) {
        free(data);
        data=nullptr;
    }
    if (columns>1) {
        this->dataformat=JKQTPDatastoreItemFormat::MatrixRow;
        this->data=static_cast<double*>(calloc(columns*new_rows, sizeof(double)));
        storageType=StorageType::Internal;
    } else {
        this->dataformat=JKQTPDatastoreItemFormat::SingleColumn;
        this->storageType=StorageType::Vector;
        datavec.resize(static_cast<int>(columns*new_rows));
        if (new_rows>rows) {
            for( size_t i=rows; i<new_rows; i++) { datavec[static_cast<int>(i)]=0.0; }
        }
        this->data=datavec.data();
        dataRetained=true;
    }
    rows=new_rows;
    allocated=true;
    return dataRetained;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::JKQTPDatastoreItem(JKQTPDatastoreItemFormat dataformat, double* data, size_t columns, size_t rows){
    this->dataformat=dataformat;
    this->allocated=true;
    this->data=data;
    this->columns=columns;
    this->rows=rows;
    this->storageType=StorageType::External;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::JKQTPDatastoreItem(JKQTPDatastoreItemFormat dataformat, double *data, size_t columns, size_t rows, bool internal)
{
    this->dataformat=dataformat;
    this->allocated=true;
    this->data=data;
    this->columns=columns;
    this->rows=rows;
    this->storageType=internal?StorageType::Internal:StorageType::External;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreItem::~JKQTPDatastoreItem(){
    if (storageType==StorageType::Internal && allocated && data!=nullptr) {
        free(data);
        data=nullptr;
    }
}



/**************************************************************************************************************************
 * JKQTPDatastore
 **************************************************************************************************************************/

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addItem(JKQTPDatastoreItem* item) {
    /*items.push_back(item);
    return items.size()-1;*/
    items.insert(maxItemID, item);
    maxItemID++;
    return maxItemID-1;
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addColumn(JKQTPColumn col) {
    columns.insert(maxColumnsID, col);
    maxColumnsID++;
    return maxColumnsID-1;
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastore::JKQTPDatastore():
    m_invalidColumn(new JKQTPColumn)
{
    maxItemID=0;
    maxColumnsID=0;
    clear();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastore::~JKQTPDatastore() {
    clear();
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPDatastore::hasColumn(size_t i) const
{
    return columns.find(i)!=columns.end();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnIterator JKQTPDatastore::begin(int i)
{
    JKQTPASSERT(i>=0);
    return begin(static_cast<size_t>(i));
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnIterator JKQTPDatastore::end(int i)
{
    JKQTPASSERT(i>=0);
    return end(static_cast<size_t>(i));
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::begin(int i) const
{
    JKQTPASSERT(i>=0);
    return cbegin(static_cast<size_t>(i));

}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::end(int i) const
{
    JKQTPASSERT(i>=0);
    return cend(static_cast<size_t>(i));

}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::cbegin(int i) const
{
    JKQTPASSERT(i>=0);
    return cbegin(static_cast<size_t>(i));

}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::cend(int i) const
{
    JKQTPASSERT(i>=0);
    return cend(static_cast<size_t>(i));
}



////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnIterator JKQTPDatastore::begin(size_t i)
{
    auto it=columns.find(i);
    if (it==columns.end()) throw std::runtime_error("column "+std::to_string(i)+" does not exist in JKQTPDatastore");
    else return it->begin();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnIterator JKQTPDatastore::end(size_t i)
{
    auto it=columns.find(i);
    if (it==columns.end()) throw std::runtime_error("column "+std::to_string(i)+" does not exist in JKQTPDatastore");
    else return it->end();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::begin(size_t i) const
{
    return cbegin(i);
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::end(size_t i) const
{
    return cend(i);
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::cbegin(size_t i) const
{
    auto it=columns.find(i);
    if (it==columns.end()) throw std::runtime_error("column "+std::to_string(i)+" does not exist in JKQTPDatastore");
    else return it->begin();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnConstIterator JKQTPDatastore::cend(size_t i) const
{
    auto it=columns.find(i);
    if (it==columns.end()) throw std::runtime_error("column "+std::to_string(i)+" does not exist in JKQTPDatastore");
    else return it->end();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnBackInserter JKQTPDatastore::backInserter(int i)
{
    if (i<0) return JKQTPColumnBackInserter(this, std::numeric_limits<size_t>::max());
    auto it=columns.find(static_cast<size_t>(i));
    if (it==columns.end()) return JKQTPColumnBackInserter(this, std::numeric_limits<size_t>::max());
    else return JKQTPColumnBackInserter(this, static_cast<size_t>(i));
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPColumnBackInserter JKQTPDatastore::backInserter(size_t i)
{
    auto it=columns.find(i);
    if (it==columns.end()) return JKQTPColumnBackInserter(this, std::numeric_limits<size_t>::max());
    else return JKQTPColumnBackInserter(this, static_cast<size_t>(i));
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::clear(){
    maxItemID=0;
    maxColumnsID=0;
    /*if (items.size()>0) {
        for (size_t i=0; i<items.size(); i++) {
            delete items[i];
        }
    }*/
    QMapIterator<size_t, JKQTPDatastoreItem*> it(items);
    while (it.hasNext()) {
        it.next();
        delete it.value();
    }
    items.clear();
    columns.clear();
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::deleteAllColumns(const QString& name, bool removeItems) {
    QList<size_t> ids;
    QMapIterator<size_t, JKQTPColumn> it(columns);
    while (it.hasNext()) {
        it.next();
        if (it.value().getName()==name) {
            ids.append(it.key());
        }
    }
    for (int i=0; i<ids.size(); i++) {
        deleteColumn(ids[i], removeItems);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::deleteAllPrefixedColumns(QString prefix, bool removeItems) {
    QList<size_t> ids;
    QMapIterator<size_t, JKQTPColumn> it(columns);
    while (it.hasNext()) {
        it.next();
        if (it.value().getName().startsWith(prefix)) {
            ids.append(it.key());
        }
    }
    for (int i=0; i<ids.size(); i++) {
        deleteColumn(ids[i], removeItems);
    }

}

////////////////////////////////////////////////////////////////////////////////////////////////
quint16 JKQTPDatastore::getColumnChecksum(int column) const
{
    return columns.value(column, JKQTPColumn()).calculateChecksum();
}


////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::deleteColumn(size_t column, bool removeItems) {
    if (removeItems) {
        size_t dsitem=columns[column].getDatastoreItemNum();
        int cnt=0;
        QMapIterator<size_t, JKQTPColumn> it(columns);
        while (it.hasNext()) {
            it.next();
            if (it.value().getDatastoreItemNum()==dsitem) {
                cnt++;
            }
        }
        if (cnt<=1) {
            delete items[dsitem];
            items.remove(dsitem);
        }
    }
    columns.remove(column);
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getColumnNum(const QString& name) {
    if (columns.size()<=0) return -1;
    QMapIterator<size_t, JKQTPColumn> it(columns);
    while (it.hasNext()) {
        it.next();
        if (it.value().getName()==name) return static_cast<int>(it.key());
    }
    return -1;
}

////////////////////////////////////////////////////////////////////////////////////////////////
QString JKQTPDatastore::getColumnName(size_t column)
{
    auto it=columns.find(column);

    if (it==columns.end()) return "";
    else return it->getName();
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::ensureColumnNum(const QString& name) {
    if (columns.size()<=0) return -1;
    QMapIterator<size_t, JKQTPColumn> it(columns);
    while (it.hasNext()) {
        it.next();
        if (it.value().getName()==name) return static_cast<int>(it.key());
    }
    return static_cast<int>(addColumn(0, name));
}


////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCopiedItem(JKQTPDatastoreItemFormat dataformat, double* data, size_t columnsnum, size_t rows) {
    JKQTPDatastoreItem* it=nullptr;
    if ((dataformat==JKQTPDatastoreItemFormat::SingleColumn)||(columnsnum==1)) {
        return addCopiedItem(data, rows);
    } else if (dataformat==JKQTPDatastoreItemFormat::MatrixColumn) {
        it=new JKQTPDatastoreItem(columnsnum, rows);
        for (size_t c=0; c<columnsnum; c++) {
            for (size_t r=0; r<rows; r++) {
                it->set(c, r, data[c*rows+r]);
            }
        }
    } else if (dataformat==JKQTPDatastoreItemFormat::MatrixRow) {
        it=new JKQTPDatastoreItem(columnsnum, rows);
        for (size_t r=0; r<rows; r++) {
            for (size_t c=0; c<columnsnum; c++) {
                it->set(c, r, data[r*columnsnum+c]);
            }
        }
    }
    /*items.push_back(it);
    return items.size()-1;*/
    return addItem(it);
};



////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addItem(size_t rows) {
    /*items.push_back(new JKQTPDatastoreItem(1, rows));
    return items.size()-1;*/
    return addItem(new JKQTPDatastoreItem(1, rows));
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addItem(size_t columnsnum, size_t rows) {
    /*items.push_back(new JKQTPDatastoreItem(columnsnum, rows));
    return items.size()-1;*/
    return addItem(new JKQTPDatastoreItem(columnsnum, rows));
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addItem(double* data, size_t rows) {
    /*items.push_back(new JKQTPDatastoreItem(JKQTPSingleColumn, data, 1, rows));
    return items.size()-1;*/
    return addItem(new JKQTPDatastoreItem(JKQTPDatastoreItemFormat::SingleColumn, data, 1, rows));
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addInternalItem(double *data, size_t rows)
{
    JKQTPDatastoreItem* dsi=new JKQTPDatastoreItem(JKQTPDatastoreItemFormat::SingleColumn, data, 1, rows, true);
    return addItem(dsi);
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addItem(JKQTPDatastoreItemFormat dataformat, double* data, size_t columnsnum, size_t rows) {
    /*items.push_back(new JKQTPDatastoreItem(dataformat, data, columnsnum, rows));
    return items.size()-1;*/
    return addItem(new JKQTPDatastoreItem(dataformat, data, columnsnum, rows));
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCopiedItem(const double* data, size_t rows) {
    JKQTPDatastoreItem* it=new JKQTPDatastoreItem(1, rows);
    if (data) {
        for (size_t i=0; i<rows; i++) {
            it->set(0, i, data[i]);
             //std::cout<<"copy@"<<i<<" = "<<data[i]<<std::endl;
        }
    }
    /*items.push_back(it);
    return items.size()-1;*/
    return addItem(it);
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addColumnForItem(size_t itemID, size_t columnInItem, const QString& name) {
    return addColumn(JKQTPColumn(this, name, itemID, columnInItem));
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addColumn(size_t rows, const QString& name) {
    size_t item= addItem(new JKQTPDatastoreItem(1, rows));
    return addColumnForItem(item, 0, name);
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addColumn(const QString& name) {
    return addColumn(0, name);
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addColumn(double* data, size_t rows, const QString& name) {
    size_t it=addItem(new JKQTPDatastoreItem(JKQTPDatastoreItemFormat::SingleColumn, data, 1, rows));
    return addColumnForItem(it, 0, name);
};

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addInternalColumn(double* data, size_t rows, const QString& name) {
    size_t it=addItem(new JKQTPDatastoreItem(JKQTPDatastoreItemFormat::SingleColumn, data, 1, rows,true));
    return addColumnForItem(it, 0, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addInternalColumn(QVector<double> &&data, const QString &name)
{
    size_t it=addItem(new JKQTPDatastoreItem(std::move(data)));
    return addColumnForItem(it, 0, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addImageColumn(size_t width, size_t height, const QString &name)
{
    size_t col= addColumn(width*height, name);
    columns[col].setImageColumns(width);
    return col;
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addImageColumn(double *data, size_t width, size_t height, const QString &name)
{
    size_t col= addColumn(data, width*height, name);
    columns[col].setImageColumns(width);
    return col;
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addInternalImageColumn(double *data, size_t width, size_t height, const QString &name)
{
    size_t col= addInternalColumn(data, width*height, name);
    columns[col].setImageColumns(width);
    return col;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::setColumnImageWidth(size_t column, size_t imageWidth)
{
    columns[column].setImageColumns(imageWidth);
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPDatastore::isColumnDataExternal(size_t column) const
{
    return columns[column].getDatastoreItem()->getStorageType()==JKQTPDatastoreItem::StorageType::External;
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPDatastore::isColumnDataExternal(int column) const
{
    return columns[column].getDatastoreItem()->getStorageType()==JKQTPDatastoreItem::StorageType::External;
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPDatastore::isVectorColumn(int column) const
{
    return columns[column].getDatastoreItem()->isVector();
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::convertToVectorColumn(size_t column)
{
    auto it=columns[column].getDatastoreItem();
    if (it->isVector()) return; // for vector columns we do not have to do anything

    QVector<double> old_data=columns[column].copyData();
    const auto old_imagecolumns=columns[column].getImageColumns();
    const auto old_name=columns[column].getName();
    size_t itemID=addItem(new JKQTPDatastoreItem(std::move(old_data)));
    columns[column]=JKQTPColumn(this, old_name, itemID, 0, old_imagecolumns );

}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::convertToVectorColumn(int column)
{
    convertToVectorColumn(static_cast<size_t>(column));
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPDatastore::isVectorColumn(size_t column) const
{
    return columns[column].getDatastoreItem()->isVector();
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::setColumnImageHeight(size_t column, size_t imageHeight)
{
    columns[column].setImageColumns(columns[column].getRows()/imageHeight);
}








////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::copyColumn(size_t old_column, size_t start, size_t stride, const QString& name)
{
    const JKQTPColumn& old=columns[old_column];
    size_t rows=old.getRows();
    QVector<double> d; d.reserve(rows);
    for (size_t i=start; i<rows; i+=stride) {
        d.push_back(old.getValue(i));
    }
    size_t n=addInternalColumn(std::move(d), name);
    return n;
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::copyColumn(size_t old_column, const QString& name)
{
    return copyColumn(old_column, 0, 1, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::copyColumnData(size_t toColumn, size_t fromColumn)
{
    resizeColumn(toColumn, getRows(fromColumn));
    std::copy(begin(fromColumn), end(fromColumn), begin(toColumn));
    setColumnImageWidth(toColumn, getColumnImageWidth(static_cast<int>(fromColumn)));
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const std::list<QPoint> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const std::list<QPointF> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const std::vector<QPoint> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const std::vector<QPointF> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const QList<QPoint> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const QList<QPointF> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

#if QT_VERSION<QT_VERSION_CHECK(6,0,0)
////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const QVector<QPoint> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addCopiedPoints(const QVector<QPointF> &points, const QString &namex, const QString &namey)
{
    const auto cx=addColumn(points.size(),namex);
    const auto cy=addColumn(points.size(),namey);
    int i=0;
    for (const auto& p: points) {
        set(cx, i, p.x());
        set(cy, i, p.y());
        i++;
    }
    return {cx,cy};
}
#endif

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addLinearColumn(size_t rows, double start, double end, const QString& name) {
    const double delta=(end-start)/static_cast<double>(rows-1);
    QVector<double> d(rows);
    for (size_t i=0; i<rows; i++) {
        d[i]= start+static_cast<double>(i) * delta;
         //std::cout<<"copy@"<<i<<" = "<<data[i]<<std::endl;
    }
    return addInternalColumn(std::move(d),name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addLogColumn(size_t rows, double start, double end, const QString &name)
{
    const double x0=log(start)/JKQTPSTATISTICS_LN10;
    const double x1=log(end)/JKQTPSTATISTICS_LN10;
    QVector<double> d(rows);
    for (size_t i=0; i<rows; i++) {
        d[i]= pow(10.0, x0+static_cast<double>(i)/static_cast<double>(rows-1)*(x1-x0));
    }
    return addInternalColumn(std::move(d),name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addDecadeLogColumn(size_t rows, double startDecade, double endDecade, const QString &name)
{
    const double x0=startDecade;
    const double x1=endDecade;
    QVector<double> d(rows);
    for (size_t i=0; i<rows; i++) {
        d[i]= pow(10.0, x0+static_cast<double>(i)/static_cast<double>(rows-1)*(x1-x0));
    }
    return addInternalColumn(std::move(d),name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
std::pair<size_t, size_t> JKQTPDatastore::addLinearGridColumns(size_t width, double startX, double endX, size_t height, double startY, double endY, const QString &nameX, const QString &nameY)
{
    const double decX=(endX-startX)/static_cast<double>(width-1);
    const double decY=(endY-startY)/static_cast<double>(height-1);
    double y=startY;
    size_t cx=addColumn(width*height, nameX);
    size_t cy=addColumn(width*height, nameY);
    size_t i=0;
    for (size_t iy=0; iy<height; iy++) {
        double x=startX;
        for (size_t ix=0; ix<width; ix++) {
            set(cx, i, x);
            set(cy, i, y);
            x+=decX;
            i++;
        }
        y+=decY;
    }
    return std::make_pair(cx,cy);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCalculatedColumn(size_t rows, const std::function<double (size_t, JKQTPDatastore *)> &f, const QString &name)
{
    JKQTPDatastoreItem* it=new JKQTPDatastoreItem(1, rows);
    for (size_t i=0; i<rows; i++) {
        it->set(0, i, f(i, this));
    }
    size_t itemid= addItem(it);
    return addColumnForItem(itemid, 0, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCalculatedColumn(size_t rows, const std::function<double (size_t)> &f, const QString &name)
{
    JKQTPDatastoreItem* it=new JKQTPDatastoreItem(1, rows);
    for (size_t i=0; i<rows; i++) {
        it->set(0, i, f(i));
    }
    size_t itemid= addItem(it);
    return addColumnForItem(itemid, 0, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCalculatedImageColumn(size_t cols, size_t rows, const std::function<double (size_t, size_t)> &f, const QString &name)
{
    size_t col=addImageColumn(cols, rows, name);
    JKQTPColumn& colref = columns[col];
    for (size_t y=0; y<rows; y++) {
        for (size_t x=0; x<cols; x++) {
            colref.setPixelValue(x, y, f(x,y));
        }
    }
    return col;

}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCalculatedColumnFromColumn(size_t otherColumn, const std::function<double (double)> &f, const QString &name)
{
    const JKQTPColumn& oc=columns.value(otherColumn);
    JKQTPDatastoreItem* it=new JKQTPDatastoreItem(1, oc.getRows());
    for (size_t i=0; i<oc.getRows(); i++) {
        it->set(0, i, f(oc.getValue(i)));
    }
    size_t itemid= addItem(it);
    return addColumnForItem(itemid, 0, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::addCalculatedColumnFromColumn(size_t otherColumnX, size_t otherColumnY, const std::function<double (double, double)> &f, const QString &name)
{
    const JKQTPColumn& ocx=columns.value(otherColumnX);
    const JKQTPColumn& ocy=columns.value(otherColumnY);
    const size_t N= qMin(ocx.getRows(), ocy.getRows());
    JKQTPDatastoreItem* it=new JKQTPDatastoreItem(1, N);
    for (size_t i=0; i<N; i++) {
        it->set(0, i, f(ocx.getValue(i), ocy.getValue(i)));
    }
    size_t itemid= addItem(it);
    return addColumnForItem(itemid, 0, name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
QVector<int> JKQTPDatastore::getColumnIDsIntVec() const {
    QVector<int> ks;
    const auto keys=columns.keys();
    for (auto it=keys.begin(); it!=keys.end(); ++it) {
        ks<<static_cast<int>(*it);
    }
    return ks;
}


////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextLowerIndex(size_t column, size_t row, int start, int end) const
{
    const JKQTPColumn& col=columns[column];
    if (start<0 && end>=0) return getNextLowerIndex(column, row, 0, end);
    else if (start>=0 && end<0)  return getNextLowerIndex(column, row, start, static_cast<int>(col.getRows())-1);
    else if (start<0 && end<0)  return getNextLowerIndex(column, row, 0, static_cast<int>(col.getRows())-1);
    else {
        double d=0;
        const double v=col.getValue(row);
        int res=-1;
        for ( int i=start; i<=end; i++) {
            if (i!=static_cast<int>(row)) {
                const double v1=col.getValue(i);
                const double dd=v1-v;
                if ((dd<0) && ((fabs(dd)<d)||(d==0.0))) {
                    res=i;
                    d=fabs(dd);
                    //std::cout<<"   getNextLowerIndex("<<column<<", "<<row<<", "<<start<<", "<<end<<"): i="<<i<<":  OK   res="<<res<<" d="<<d<<"\n";
                } /*else {
                    std::cout<<"   getNextLowerIndex("<<column<<", "<<row<<", "<<start<<", "<<end<<"): i="<<i<<":  FAIL res="<<res<<" dd="<<dd<<" v1="<<v1<<" v="<<v<<"\n";
                }*/
            }
        }
        return res;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextLowerIndex(size_t column, size_t row) const
{
    return getNextLowerIndex(column, row, 0, static_cast<int>(columns[column].getRows())-1);
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextHigherIndex(size_t column, size_t row, int start, int end) const
{
    const JKQTPColumn& col=columns[column];
    if (start<0 && end>=0) return getNextHigherIndex(column, row, 0, end);
    else if (start>=0 && end<0)  return getNextHigherIndex(column, row, start, static_cast<int>(col.getRows())-1);
    else if (start<0 && end<0)  return getNextHigherIndex(column, row, 0, static_cast<int>(col.getRows())-1);
    else {
        double d=0;
        const double v=col.getValue(row);
        int res=-1;
        for ( int i=start; i<=end; i++) {
            if (i!=static_cast<int>(row)) {
                const double v1=col.getValue(i);
                const double dd=v1-v;
                if ((dd>0) && ((fabs(dd)<d)||(d==0.0))) {
                    res=i;
                    d=fabs(dd);
                }
            }
        }
        return res;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextHigherIndex(size_t column, size_t row) const
{
    return getNextHigherIndex(column, row, 0, static_cast<int>(columns[column].getRows())-1);
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextLowerIndex(int column, size_t row, int start, int end) const {
    return getNextLowerIndex(static_cast<size_t>(column), row, start, end);
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextLowerIndex(int column, size_t row) const {
    return getNextLowerIndex(static_cast<size_t>(column), row);
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextHigherIndex(int column, size_t row, int start, int end) const {
    return getNextHigherIndex(static_cast<size_t>(column), row, start, end);
}


////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::eraseFromColumn(JKQTPColumnConstIterator pos) {
    eraseFromColumn(pos, pos);
}


////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::eraseFromColumn(JKQTPColumnConstIterator pos, JKQTPColumnConstIterator posEnd) {
    // both iterators must reference the same column object
    if (pos.getColumn() != posEnd.getColumn()) {
        JKQTPASSERT_M(false, "in eraseFromColumn(pos,posEnd) both iterators have to point to the same column");
        return;
    }

    const JKQTPColumn* pcol = pos.getColumn();
    if (!pcol) return;

    // find the key (index) of this column in the QMap by address comparison
    size_t itc = std::numeric_limits<size_t>::max();
    for (auto it = columns.begin(); it != columns.end(); ++it) {
        if (&(it.value()) == pcol) { itc = it.key(); break; }
    }
    if (itc == std::numeric_limits<size_t>::max()) {
        // column not found in map: nothing to do
        return;
    }

    JKQTPColumn& column = columns[itc];
    const size_t NRows = column.getRows();
    if (!pos.isValid()) {
        // nothing to erase if start iterator is invalid
        return;
    }

    // prepare ranges to copy: [start1..end1] and [start2..end2] (inclusive)
    size_t start1 = 0;
    size_t end1 = (NRows > 0 ? NRows - 1 : 0);
    size_t start2 = NRows; // disabled by default (start2 > end2)
    size_t end2 = NRows;

    // compute the two ranges that should be kept (i.e. copy those ranges into a new vector item)
    if (pos == column.begin()) {
        // deleting from begin
        if ( (posEnd == column.end()) ||
            (posEnd.isValid() && static_cast<size_t>(posEnd.getPosition()) == (NRows > 0 ? (NRows - 1) : 0)) ) {
            // erase everything -> replace by empty vector item
            const size_t itemID = addItem(new JKQTPDatastoreItem(1, 0));
            column.replaceMemory(itemID);
            return;
        } else if (posEnd.isValid()) {
            // keep rows after posEnd
            start1 = static_cast<size_t>((posEnd + 1).getPosition());
            end1 = (NRows > 0 ? NRows - 1 : 0);
            start2 = NRows; // disable second range
            end2 = NRows;
        } else {
            // posEnd invalid and not equal to end -> nothing sensible to do
            return;
        }
    } else {
        // deleting beginning somewhere after 0
        if (!posEnd.isValid() || posEnd == column.end()) {
            // keep 0..pos-1 only
            start1 = 0;
            end1 = static_cast<size_t>((pos - 1).getPosition());
            start2 = NRows; // disable second range
            end2 = NRows;
        } else {
            // keep 0..pos-1 and posEnd+1..NRows-1
            start1 = 0;
            end1 = static_cast<size_t>((pos - 1).getPosition());
            start2 = static_cast<size_t>((posEnd + 1).getPosition());
            end2 = (NRows > 0 ? NRows - 1 : 0);
        }
    }

    // try to convert to a vector item from the computed ranges; convertVectorItemFromRanges returns true if a new vector item was created
    if (!column.convertVectorItemFromRanges(start1, end1, start2, end2)) {
        // conversion was not performed (column already a vector?) - in that case invoke eraseFromVectorColumn directly
        if (posEnd.isValid()) {
            column.eraseFromVectorColumn(static_cast<size_t>(pos.getPosition()), static_cast<size_t>(posEnd.getPosition()));
        } else {
            // erase from pos to end (use NRows-1, avoid the erroneous large value)
            if (NRows == 0) return; // nothing to erase
            column.eraseFromVectorColumn(static_cast<size_t>(pos.getPosition()), NRows - 1);
        }
    }
}


////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::getMaxRows() const {
    size_t res=0;
    /*for (size_t i=0; i<columns.size(); i++) {
        size_t r=columns[i].getRows();
        if (r>res) res=r;
    }*/
    QMapIterator<size_t, JKQTPColumn> it(columns);
    while (it.hasNext()) {
        it.next();
        size_t r=it.value().getRows();
        if (r>res) res=r;
    }

    return res;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::saveCSV(const QString& filename, const QSet<int>& userColumns, const QString& separator, const QString& decimal_separator, const QString& comment, const QString& aroundStrings, char floatformat) const {
    //std::cout<<filename<<"\n";

    // find out the decimal and the thousand separator
    QLocale loc=QLocale::c();
    loc.setNumberOptions(QLocale::OmitGroupSeparator);
    //QChar dsep=loc.decimalPoint();
    QFile f(filename);
    if (!f.open(QIODevice::WriteOnly|QIODevice::Text)) return;
    QTextStream txt(&f);
    saveCSV(txt, userColumns, separator, decimal_separator, comment, aroundStrings, floatformat);
    f.close();
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::saveMatlab(const QString& filename, const QSet<int>& userColumns) const {
    //std::cout<<filename<<"\n";

    // find out the decimal and the thousand separator
    QLocale loc=QLocale::c();
    loc.setNumberOptions(QLocale::OmitGroupSeparator);
    //QChar dsep=loc.decimalPoint();
    QFile f(filename);
    if (!f.open(QIODevice::WriteOnly|QIODevice::Text)) return;
    QTextStream txt(&f);
    saveMatlab(txt, userColumns);
    f.close();
}

////////////////////////////////////////////////////////////////////////////////////////////////
QStringList JKQTPDatastore::getColumnNames() const {
    QStringList names;
    QMapIterator<size_t, JKQTPColumn> it(columns);
    int col=0;
    while (it.hasNext()) {
        it.next();
        names.append(it.value().getName());
        col++;
    }
    return names;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::saveMatlab(QTextStream &txt, const QSet<int>& userColumns) const {
    //std::cout<<filename<<"\n";

    // find out the decimal and the thousand separator
    QLocale loc=QLocale::c();
    loc.setNumberOptions(QLocale::OmitGroupSeparator);
    QSet<QString> varnames;


    txt.setLocale(loc);
/*    if (comment.size()>0) {
        txt<<comment<<" ";
        bool first=true;
        QMapIterator<size_t, JKQTPColumn> it(columns);
        while (it.hasNext()) {
            it.next();
            if (!first) txt<<separator;
            txt<<aroundStrings<<it.value().getName()<<aroundStrings;
            first=false;
        }
        txt<<"\n";
    }*/
    QMapIterator<size_t, JKQTPColumn> it(columns);
    int col=0;
    int i=0;
    while (it.hasNext()) {
        it.next();

        if (userColumns.isEmpty() || userColumns.contains(i)) {

            // convert the column name into a variable name (name cleanup!) If the column name is empty, we  use a default name ("column")
            QString newvarbase=jkqtp_to_valid_variable_name(it.value().getName().toStdString()).c_str();
            if (newvarbase.isEmpty()) newvarbase="column";
            int cnt=1;
            QString newvar=newvarbase;
            // now we make sure that the name is not already used, i.e. we compare it to all variable names used so far and if it has already been used,
            // an increasing number is added to the name.
            while (varnames.contains(newvar)) {
                newvar=newvarbase+QString::number(cnt);
                cnt++;
            }
            varnames.insert(newvar);
            txt<<QString("% data from columne %1 ('%2')\n").arg(col+1).arg(it.value().getName());
            txt<<QString("%1 = [ ").arg(newvar);
            for (size_t rr=0; rr<it.value().getRows(); rr++) {
                txt<<loc.toString(get(it.key(), rr))<<" ";

            }
            txt<<"];\n\n";
            col++;
        }
        i++;
    }

}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::saveCSV(QTextStream& txt, const QSet<int>& userColumns, const QString& separator, const QString& decimal_separator, const QString& comment, const QString& aroundStrings, char floatformat) const {
    //std::cout<<filename<<"\n";

    // find out the decimal and the thousand separator
    QLocale loc=QLocale::c();
    loc.setNumberOptions(QLocale::OmitGroupSeparator);
    const auto dsep=loc.decimalPoint();


    txt.setLocale(loc);
    if (comment.size()>0) {
        txt<<comment<<" ";
        bool first=true;
        QMapIterator<size_t, JKQTPColumn> it(columns);
        int i=0;
        while (it.hasNext()) {
            it.next();
            if (userColumns.isEmpty() || userColumns.contains(i)) {
                if (!first) txt<<separator;
                txt<<aroundStrings<<it.value().getName()<<aroundStrings;
                first=false;
            }
            i++;
        }
        txt<<"\n";
    }
    for (size_t i=0; i<getMaxRows(); i++) {
        bool first=true;
        QMapIterator<size_t, JKQTPColumn> it(columns);
        int j=0;
        while (it.hasNext()) {
            it.next();
            if (userColumns.isEmpty() || userColumns.contains(j)) {
                if (!first) txt<<separator;
                first=false;
                if (it.value().getRows()>i) {
                    QString num=QString("%1").arg(get(it.key(), i), 0, floatformat);
                    num=num.replace(dsep, decimal_separator);
                    txt<<num;
                }
                //std::cout<<"("<<i<<", "<<c<<")\n";
            }
            j++;
        }
        txt<<"\n";
    }
    txt<<"\n";

}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::saveSYLK(const QString& filename, const QSet<int>& userColumns, const QString& floatformat) const {
    Q_UNUSED(floatformat)
    // find out the decimal and the thousand separator
    QLocale loc=QLocale::c();
    loc.setNumberOptions(QLocale::OmitGroupSeparator);
    //QChar dsep=loc.decimalPoint();
    QFile f(filename);
    if (!f.open(QIODevice::WriteOnly|QIODevice::Text)) return;
    QTextStream txt(&f);
    txt.setLocale(loc);
    // write SYLK header
    txt<<"ID;P\n";

    // write column headers
    QMapIterator<size_t, JKQTPColumn> it(columns);
    size_t c=1;
    int i=0;
    while (it.hasNext()) {
        it.next();
        if (userColumns.isEmpty() || userColumns.contains(i)) {
            txt<<"C;Y1;X"<<c<<";K\""<<it.value().getName()<<"\"\n";
            txt<<"F;Y1;X"<<c<<";SDB\n";
            c++;
        }
        i++;
    }



    for (size_t rr=0; rr<getMaxRows(); rr++) {
        QMapIterator<size_t, JKQTPColumn> it(columns);
        c=1;
        while (it.hasNext()) {
            it.next();
            if (userColumns.isEmpty() || userColumns.contains(static_cast<int>(rr))) {
                if (it.value().getRows()>rr) {
                    txt<<QString("C;X%1;Y%2;N;K%3\n").arg(c).arg(rr+2).arg(get(it.key(), rr));
                }
                c++;
            }
            i++;
        }
    }
    txt<<"E\n\n";
    f.close();



}

////////////////////////////////////////////////////////////////////////////////////////////////
QList<QVector<double> > JKQTPDatastore::getData(QStringList *columnNames, const QSet<int>& userColumns) const
{
    QStringList cl;
    QList<QVector<double> > res;

    // write column headers
    QMapIterator<size_t, JKQTPColumn> it(columns);
    size_t c=1;
    int i=0;
    while (it.hasNext()) {
        it.next();
        if (userColumns.isEmpty() || userColumns.contains(i)) {
            QVector<double> dat;
            for (size_t i=0; i<it.value().getRows(); i++) {
                dat<<it.value().getValue(i);
            }

            cl<<it.value().getName();
            res<<dat;
            c++;
        }
        i++;
    }

    if (columnNames) *columnNames=cl;

    return res;
}

////////////////////////////////////////////////////////////////////////////////////////////////
QVector<double> JKQTPDatastore::getData(size_t column, QString *columnName) const
{
    QVector<double> res;
    if (hasColumn(column)) {
        if (columnName) *columnName=columns.value(column).getName();
        columns.value(column).copyData(res);
    }
    return res;
}


////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::saveDIF(const QString& filename, const QSet<int>& userColumns, const QString& floatformat) const {
    Q_UNUSED(floatformat)
    // find out the decimal and the thousand separator
    QLocale loc=QLocale::c();
    loc.setNumberOptions(QLocale::OmitGroupSeparator);
    //QChar dsep=loc.decimalPoint();
    QFile f(filename);
    if (!f.open(QIODevice::WriteOnly|QIODevice::Text)) return;
    QTextStream txt(&f);
    txt.setLocale(loc);
    const size_t rows=getMaxRows();

    // write DIF header
    txt<<QString("TABLE\n0,1\n\"\"\n");
    if (userColumns.isEmpty()) txt<<QString("VECTORS\n0,%1\n\"\"\n").arg(columns.size());
    else txt<<QString("VECTORS\n0,%1\n\"\"\n").arg(userColumns.size());
    txt<<QString("TUPLES\n0,%1\n\"\"\n").arg(rows);

    txt<<QString("-1,0\nBOT\n");

    // write column headers
    QMapIterator<size_t, JKQTPColumn> it(columns);
    size_t c=1;
    int i=0;
    while (it.hasNext()) {
        it.next();
        if (userColumns.isEmpty() || userColumns.contains(i)) {
            txt<<QString("1,0\n\"%1\"\n").arg(columns[c].getName());
            c++;
        }
        i++;
    }



    for (size_t i=0; i<rows; i++) {
        txt<<QString("-1,0\nBOT\n");
        QMapIterator<size_t, JKQTPColumn> it(columns);
        c=1;
        int j=0;
        while (it.hasNext()) {
            it.next();
            if (userColumns.isEmpty() || userColumns.contains(j)) {
                //qDebug()<<i<<"/"<<rows<<",   "<<c;
                if (i<it.value().getRows()) {
                    txt<<QString("0,%1\nV\n").arg(get(it.key(), i));
                }
                c++;
            }
            j++;
        }
    }
    txt<<"-1,0\nEOT\n";
    f.close();


}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreModel::JKQTPDatastoreModel(JKQTPDatastore *datastore, QObject *parent):
    QAbstractTableModel(parent)
{
    this->datastore=datastore;
    reloadModel();
}

////////////////////////////////////////////////////////////////////////////////////////////////
JKQTPDatastoreModel::~JKQTPDatastoreModel()
= default;

////////////////////////////////////////////////////////////////////////////////////////////////
QVariant JKQTPDatastoreModel::data(const QModelIndex &index, int role) const {
    int row=index.row();
    int column=index.column();
    if (datastore) {
        if (role==Qt::DisplayRole || role==Qt::EditRole) {
            int col=static_cast<int>(datastore->getColumnIDsIntVec().value(column, -1));
            if (col>-1 && row>=0 && row<static_cast<int>(datastore->getRows(col))) {
                return datastore->get(col, static_cast<size_t>(row));
            }
        }
    }
    return QVariant();
}

////////////////////////////////////////////////////////////////////////////////////////////////
Qt::ItemFlags JKQTPDatastoreModel::flags(const QModelIndex &/*index*/) const {
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

////////////////////////////////////////////////////////////////////////////////////////////////
QVariant JKQTPDatastoreModel::headerData(int section, Qt::Orientation orientation, int role) const {
    if (datastore) {
        if (role==Qt::DisplayRole) {
            if (orientation==Qt::Horizontal) {
                if (section>=0 && section<static_cast<int64_t>(datastore->getColumnCount())) {
                    //qDebug()<<"columns: "<<datastore->getColumnNames().size()<<datastore->getColumnIDs().size()<<": "<<datastore->getColumnNames();
                    return datastore->getColumnNames().value(section);
                }
            } else {
                return QString::number(section+1);
            }
        }
    }
    return QVariant();
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastoreModel::rowCount(const QModelIndex &/*parent*/) const {
    if (datastore) {
        return static_cast<int>(datastore->getMaxRows());
    }
    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastoreModel::columnCount(const QModelIndex &/*parent*/) const {
    if (datastore) {
        return static_cast<int>(datastore->getColumnCount());
    }
    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastoreModel::reloadModel()
{
    #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
    beginResetModel();
    endResetModel();
    #else
    reset();
    #endif
}

////////////////////////////////////////////////////////////////////////////////////////////////
int JKQTPDatastore::getNextHigherIndex(int column, size_t row) const {
    return getNextHigherIndex(static_cast<size_t>(column), row);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::setColumnData(size_t toColumn, const QVector<double> &data)
{
    setColumnCopiedData(toColumn, data.data(), data.size());
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::setColumnCopiedData(size_t toColumn, const double *data, size_t N)
{
    resizeColumn(toColumn, N);
    columns[toColumn].copy(data, N);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::setColumnCopiedImageData(size_t toColumn, const double *data, size_t width, size_t height)
{
    setColumnCopiedData(toColumn, data, width*height);
    columns[toColumn].setImageColumns(width);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumn(size_t column, double value)
{
    // quick guard: column must exist
    if (columns.find(column) == columns.end()) {
        qDebug()<<"column "<<column<<" does not exist, so we cannnot add a value";
        return;
    }

    // Try fast append into the existing item
    JKQTPDatastoreItem* item = columns[column].getDatastoreItem();
    if (item && item->append(columns[column].getDatastoreOffset(), value)) {
        return;
    }

    // If append failed, try to convert the column to a vector-backed item and reserve capacity
    size_t usedVectorItem = std::numeric_limits<size_t>::max();
    const size_t currentRows = columns[column].getRows();

    // Attempt conversion (this will create a vector-backed JKQTPDatastoreItem and make the column point to it)
    const bool converted = columns[column].convertVectorItem(&usedVectorItem);
    if (converted) {
        // compute reserve size (grow at least to current*2, at least 16)
        size_t reserveSize = std::max<size_t>(currentRows + 1, std::max<size_t>(currentRows * 2, 16));
        JKQTPDatastoreItem* newItem = getItem(usedVectorItem);
        if (newItem) {
            newItem->reserveVectorColumn(reserveSize);
        }

        // try append again
        item = columns[column].getDatastoreItem();
        if (item && item->append(columns[column].getDatastoreOffset(), value)) {
            return;
        }
    }

    // Fallback: create new internal item and copy all data (old behavior)
    QVector<double> old_data = columns[column].copyData();
    old_data<<value;
    size_t itemID = addItem(new JKQTPDatastoreItem(std::move(old_data)));
    columns[column] = JKQTPColumn(this, columns[column].getName(), itemID, 0);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::resizeColumn(size_t column, size_t new_rows)
{
    if (columns[column].getRows()==new_rows) return;
    convertToVectorColumn(column);
    columns[column].getDatastoreItem()->resizeVectorItem(new_rows);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::resizeImageColumn(size_t column, size_t new_image_width, size_t new_image_height)
{
    resizeColumn(column, new_image_width*new_image_height);
    columns[column].setImageColumns(new_image_width);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumns(size_t column1, size_t column2, double value1, double value2)
{
    appendToColumn(column1,value1);
    appendToColumn(column2,value2);
}


////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumns(size_t columnX, size_t columnY, const QPointF& value)
{
    appendToColumn(columnX,value.x());
    appendToColumn(columnY,value.y());
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumns(size_t columnX, size_t columnY, const QPoint& value)
{
    appendToColumn(columnX,value.x());
    appendToColumn(columnY,value.y());
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumns(size_t column1, size_t column2, size_t column3, double value1, double value2, double value3)
{
    appendToColumn(column1,value1);
    appendToColumn(column2,value2);
    appendToColumn(column3,value3);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumns(size_t column1, size_t column2, size_t column3, size_t column4, double value1, double value2, double value3, double value4)
{
    appendToColumn(column1,value1);
    appendToColumn(column2,value2);
    appendToColumn(column3,value3);
    appendToColumn(column4,value4);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void JKQTPDatastore::appendToColumns(size_t column1, size_t column2, size_t column3, size_t column4, size_t column5, double value1, double value2, double value3, double value4, double value5)
{
    appendToColumn(column1,value1);
    appendToColumn(column2,value2);
    appendToColumn(column3,value3);
    appendToColumn(column4,value4);
    appendToColumn(column5,value5);
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::getColumnImageWidth(int column) const
{
    if (column<0) return 0;
    return columns[static_cast<size_t>(column)].getImageColumns();
}

////////////////////////////////////////////////////////////////////////////////////////////////
size_t JKQTPDatastore::getColumnImageHeight(int column) const
{
    if (column<0) return 0;
    return columns[static_cast<size_t>(column)].getRows()/columns[static_cast<size_t>(column)].getImageColumns();
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPColumn::convertVectorItem(size_t *usedVectorItem)
{
    return convertVectorItemFromRanges(getRows(),getRows(),getRows(),getRows(),usedVectorItem);
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPColumn::convertVectorItemFromRanges(size_t start1, size_t end1, size_t start2, size_t end2, size_t *usedVectorItem)
{
    bool itemCreated=false;
    const bool isVectorCol=getDatastoreItem()->isVector();
    if (!isVectorCol) {
        const auto NRowsOld=getRows();
        JKQTPDatastore* ds=getDatastore();
        QVector<double> old_data;

        const bool copyRange1=start1<=end1 && start1<NRowsOld && end1<NRowsOld;
        const size_t NRowsRange1=end1-start1+1;
        const bool copyRange2=start2<=end2 && start2<NRowsOld && end2<NRowsOld;
        const size_t NRowsRange2=end2-start2+1;
        const bool copyAll=(start1==NRowsOld && end1==NRowsOld && start2==NRowsOld && end2==NRowsOld);
        const size_t NToCopy=copyAll?NRowsOld:(copyRange1?NRowsRange1:0)+(copyRange2?NRowsRange2:0)+10;

        // copy all data into old_data, as specified by start1,end1,start2,end2
        old_data.reserve(NToCopy);
        if (copyAll) {
            for (size_t it=0; it<NRowsOld; ++it) old_data.push_back(getValue(it));
        } else {
            if (copyRange1) {
                for (size_t it=start1; it<=std::min(NRowsOld-1,end1); ++it) old_data.push_back(getValue(it));
            }
            if (copyRange2) {
                for (size_t it=start2; it<=std::min(NRowsOld-1,end2); ++it) old_data.push_back(getValue(it));
            }
        }

        // create new JKQTPDatastoreItem and add it to ds, replace memory in this column by that item
        const size_t itemID=ds->addItem(new JKQTPDatastoreItem(std::move(old_data)));
        replaceMemory(itemID, 0);
        itemCreated=true;
    }
    if (usedVectorItem) *usedVectorItem=getDatastoreItemNum();
    return itemCreated;
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool JKQTPColumn::convertVectorItemFromRange(size_t start1, size_t end1, size_t *usedVectorItem)
{
    return convertVectorItemFromRanges(start1,end1, getRows(), getRows(), usedVectorItem);
}
