#include "imageringlist.h"
#include <QThreadPool>
#include <QDir>
#include "loadrunable.h"
#include "rawimage.h"

using namespace std;

const int DEFAULT_WIDTH = 2;

Image::Image(const QString name, ImageRingList *ringList) :
    m_loading(false),
    m_released(true),
    m_current(false),
    m_name(name),
    m_ringList(ringList)
{
}

void Image::load()
{
    if(!m_rawImage && !m_loading)
    {
        m_loading = true;
        m_released = false;
        QThreadPool::globalInstance()->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
    }
    if(!m_loading && m_rawImage)
        emit pixmapLoaded(this);
}

void Image::release()
{
    m_rawImage.reset();
    m_released = true;
    m_loading = false;
}

QString Image::name() const
{
    return m_name;
}

RawImage *Image::rawImage()
{
    return m_rawImage.get();
}

ImageInfoData Image::info() const
{
    return m_info;
}

bool Image::isCurrent() const
{
    return !m_released;
}

void Image::imageLoaded(void *rawImage, ImageInfoData info)
{
    m_loading = false;
    if(!m_released)
    {
        m_rawImage.reset(static_cast<RawImage*>(rawImage));
        m_info = info;
        emit pixmapLoaded(this);
    }
    else
    {
        delete static_cast<RawImage*>(rawImage);
    }
}

ImageRingList::ImageRingList(QObject *parent) : QAbstractItemModel(parent)
  , m_liveMode(false)
  , m_analyzeLevel(None)
{
    connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
}

ImageRingList::~ImageRingList()
{
    QThreadPool::globalInstance()->clear();
    QThreadPool::globalInstance()->waitForDone();
}

bool ImageRingList::setDir(const QString path, const QString &currentFile)
{
    QDir dir(path);

    if(dir.exists())
    {
        QStringList nameFilter;
        nameFilter << "*.jpg" << "*.jpeg" << "*.png" << "*.cr2" << "*.fit" << "*.fits" << "*.xisf";

        QStringList list = dir.entryList(nameFilter, QDir::Files | QDir::Readable, m_liveMode ? QDir::Time : QDir::Name);
        QStringList absolutePaths;
        foreach(const QString &file, list)
        {
            absolutePaths.append(dir.absoluteFilePath(file));
        }
        setFiles(absolutePaths, m_liveMode ? list.first() : currentFile);

        m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
        m_fileSystemWatcher.addPath(path);
        return true;
    }
    return false;
}

void ImageRingList::setFile(const QString &file)
{
    QFileInfo info(file);
    if(info.isDir())
        setDir(file);
    else
        setDir(info.absolutePath(), file);
}

ImagePtr ImageRingList::currentImage()
{
    if(m_images.size())
        return *m_currImage;
    else
        return 0;
}

void ImageRingList::increment()
{
    if(m_images.size())
    {
        (*m_firstImage)->release();
        m_firstImage = increment(m_firstImage);
        m_currImage = increment(m_currImage);
        (*m_currImage)->load();
        m_lastImage = increment(m_lastImage);
        (*m_lastImage)->load();
    }
}

void ImageRingList::decrement()
{
    if(m_images.size())
    {
        (*m_lastImage)->release();
        m_firstImage = decrement(m_firstImage);
        m_currImage = decrement(m_currImage);
        (*m_currImage)->load();
        m_lastImage = decrement(m_lastImage);
        (*m_firstImage)->load();
    }
}

void ImageRingList::setLiveMode(bool live)
{
    m_liveMode = live;
}

void ImageRingList::setCalculateStats(bool stats)
{
    m_analyzeLevel = stats ? Statistics : None;
}

void ImageRingList::setFindPeaks(bool findPeaks)
{
    m_analyzeLevel = findPeaks ? Peaks : None;
}

void ImageRingList::setFindStars(bool findStars)
{
    m_analyzeLevel = findStars ? Stars : None;
}

AnalyzeLevel ImageRingList::analyzeLevel() const
{
    return m_analyzeLevel;
}

void ImageRingList::loadFile(int row)
{
    if(row < m_images.size())
    {
        m_firstImage = m_currImage = m_lastImage = m_images.begin()+row;
        if(m_images.empty())
            return;

        (*m_currImage)->load();

        m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
        if(m_liveMode)
            m_width = 0;

        for(int i=0; i<m_width; i++)
        {
            m_firstImage = decrement(m_firstImage);
            (*m_firstImage)->load();
            m_lastImage = increment(m_lastImage);
            (*m_lastImage)->load();
        }
        if(m_lastImage != m_firstImage)
        {
            QList<ImagePtr>::iterator iter = increment(m_lastImage);
            while(m_firstImage != iter)
            {
                (*iter)->release();
                iter = increment(iter);
            }
        }
    }
}

QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
{
    return createIndex(row, column, m_images.at(row).get());
}

QModelIndex ImageRingList::parent(const QModelIndex &child) const
{
    return QModelIndex();
}

int ImageRingList::rowCount(const QModelIndex &parent) const
{
    if(parent == QModelIndex())
        return m_images.size();
    else
        return 0;
}

int ImageRingList::columnCount(const QModelIndex &parent) const
{
    return 1;
}

QVariant ImageRingList::data(const QModelIndex &index, int role) const
{
    switch(role)
    {
    case Qt::DisplayRole:
    {
        QFileInfo info(m_images.at(index.row())->name());
        return info.fileName();
    }
    default:
        return QVariant();
    }
}

QVariant ImageRingList::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(section==0 && orientation==Qt::Horizontal && role==Qt::DisplayRole)
    {
        return tr("Name");
    }
    return QVariant();
}

void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
{
    QThreadPool::globalInstance()->clear();
    QThreadPool::globalInstance()->waitForDone();
    beginResetModel();
    m_images.clear();
    foreach(const QString &file, files)
    {
        ImagePtr ptr = make_shared<Image>(file, this);
        connect(ptr.get(), SIGNAL(pixmapLoaded(Image*)), this, SLOT(imageLoaded(Image*)));
        m_images.append(ptr);
    }

    int index = files.indexOf(currentFile);
    if(index < 0)
        index = 0;

    endResetModel();
    loadFile(index);
}

QList<ImagePtr>::iterator ImageRingList::increment(QList<ImagePtr>::iterator iter)
{
    iter++;
    if(iter == m_images.end())
        iter = m_images.begin();
    return iter;
}

QList<ImagePtr>::iterator ImageRingList::decrement(QList<ImagePtr>::iterator iter)
{
    if(iter == m_images.begin())
        iter = m_images.end();
    iter--;
    return iter;
}

void ImageRingList::imageLoaded(Image *image)
{
    if(image->name() == (*m_currImage)->name())
    {
        emit pixmapLoaded(image);
        emit infoLoaded(image->info());
        emit currentImageChanged(m_currImage-m_images.begin());
    }
}

void ImageRingList::dirChanged(QString dir)
{
    QString currentFile;

    if(m_images.size())
        currentFile = (*m_currImage)->name();

    setDir(dir, currentFile);
    if(m_images.size())
        emit currentImageChanged(m_currImage-m_images.begin());
}
