//****************************************************************
/** @class FitsDumper

  @brief Dumps contents of fits tables to stdout or a file

 */
 //****************************************************************
#include "Configuration.h"

#include <map>
#include <fstream>

#include <CCfits/CCfits>

//#define PLOTTING_PLEASE

#ifdef PLOTTING_PLEASE
#include <QPen>
#include <QtGui>
#include <QApplication>

#include <qwt_plot.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_zoomer.h>
#include <qwt_legend.h>
#include <qwt_scale_draw.h>

#include "Time.h"
#endif

using namespace std;

class MyColumn : public CCfits::Column
{
public:
    const string &comment() const { return CCfits::Column::comment(); }
    long width() const
    {//the width() method seems to be buggy (or empty ?) as it always return 1... redo it ourselves.
        string inter = format();
        inter = inter.substr(0, inter.size()-1);
        return atoi(inter.c_str());
    }
};

#ifdef PLOTTING_PLEASE
class TimeScaleDraw: public QwtScaleDraw
{
public:
    virtual QwtText label(double v) const
    {
        Time t(v);
        string time = t.GetAsStr("%H:%M:%S%F");
        while (time[time.size()-1] == '0' && time.size() > 2)
        {
            time = time.substr(0, time.size()-1);
        }
        return QwtText(time.c_str());
    }
};
#endif

class FitsDumper
{
public:
    FitsDumper();
    ~FitsDumper();

private:
    
    CCfits::FITS  *fFile;                 /// FITS pointer
    CCfits::Table *fTable;                /// Table pointer
    map<string, MyColumn*> fColMap; /// map between the column names and their CCfits objects

    // Convert CCfits::ValueType into a human readable string
    string ValueTypeToStr(CCfits::ValueType type) const;

    // Convert CCfits::ValueType into a number of associated bytes
    int    ValueTypeToSize(CCfits::ValueType type) const;

    /// Calculate the buffer size required to read a row of the fits table, as well as the offsets to each column
    vector<int> CalculateOffsets() const;

    template<class T>
        T PtrToValue(const unsigned char* &ptr) const;
//    template<class T>
//        double PtrToDouble(const unsigned char *ptr) const;
//    double PtrToDouble(const unsigned char *ptr, CCfits::ValueType type) const;

    /// Write a single row of the selected data
    int  WriteRow(ostream &, const vector<MyColumn*> &, const vector<int> &, unsigned char *, const vector<pair<int, int> >&) const;

    bool OpenFile(const string &);        /// Open a file
    bool OpenTable(const string &);       /// Open a table

    /// Lists all columns of an open file
    void List();                          
    void ListHeader();
    void ListKeywords(ostream &);

    bool separateColumnsFromRanges(const vector<string>& list,
                                   vector<pair<int, int> >& ranges,
                                   vector<string>& listNamesOnly);
    /// Perform the dumping, based on the current dump list
    bool Dump(const string &, const vector<string> &list, int);
    ///Display the selected columns values VS time
#ifdef PLOTTING_PLEASE
    int doCurvesDisplay( const vector<string> &list, const string& tableName);
#endif
    //    bool Plot(const vector<string> &list);

public:
    ///Configures the fitsLoader from the config file and/or command arguments.
    int ExecConfig(Configuration& conf);
};

// --------------------------------------------------------------------------
//
//! Constructor
//! @param out
//!        the ostream where to redirect the outputs
//
FitsDumper::FitsDumper() : fFile(0), fTable(0)
{
}

// --------------------------------------------------------------------------
//
//! Destructor
//
FitsDumper::~FitsDumper()
{
    if (fFile)
        delete fFile;
}


string FitsDumper::ValueTypeToStr(CCfits::ValueType type) const
{
    switch (type)
    {
    case CCfits::Tbyte:     return "uint8_t";
    case CCfits::Tushort:   return "uint16_t";
    case CCfits::Tshort:    return "int16_t";
    case CCfits::Tuint:     return "uint32_t";
    case CCfits::Tint:      return "int32_t";
    case CCfits::Tulong:    return "uint32_t";
    case CCfits::Tlong:     return "int32_t";
    case CCfits::Tlonglong: return "int64_t";
    case CCfits::Tfloat:    return "float";
    case CCfits::Tdouble:   return "double";

    default:
        return "unknwown";
    }
}

int FitsDumper::ValueTypeToSize(CCfits::ValueType type) const
{
    switch (type)
    {
    case CCfits::Tbyte:     return sizeof(uint8_t);
    case CCfits::Tushort:
    case CCfits::Tshort:    return sizeof(uint16_t);
    case CCfits::Tuint:
    case CCfits::Tint:
    case CCfits::Tulong:
    case CCfits::Tlong:     return sizeof(uint32_t);
    case CCfits::Tlonglong: return sizeof(uint64_t);
    case CCfits::Tfloat:    return sizeof(float);
    case CCfits::Tdouble:   return sizeof(double);

    default:
        return 0;
    }
}

template<class T>
T FitsDumper::PtrToValue(const unsigned char* &ptr) const
{
    T t;
    reverse_copy(ptr, ptr+sizeof(T), reinterpret_cast<unsigned char*>(&t));
    ptr += sizeof(T);

    return t;
}
/*
template<class T>
double FitsDumper::PtrToDouble(const unsigned char *ptr) const
{
    T t;
    reverse_copy(ptr, ptr+sizeof(T), reinterpret_cast<unsigned char*>(&t));
    return t;
}

double FitsDumper::PtrToDouble(const unsigned char *ptr, CCfits::ValueType type) const
{
    switch (type)
    {
    case CCfits::Tbyte:     return PtrToDouble<uint8_t> (ptr);
    case CCfits::Tushort:   return PtrToDouble<uint16_t>(ptr);
    case CCfits::Tuint:
    case CCfits::Tulong:    return PtrToDouble<uint32_t>(ptr);
    case CCfits::Tshort:    return PtrToDouble<int16_t> (ptr);
    case CCfits::Tint:
    case CCfits::Tlong:     return PtrToDouble<int32_t> (ptr);
    case CCfits::Tlonglong: return PtrToDouble<int64_t> (ptr);
    case CCfits::Tfloat:    return PtrToDouble<float>   (ptr);
    case CCfits::Tdouble:   return PtrToDouble<double>  (ptr);

    default:
        throw runtime_error("Data type not implemented yet.");
    }
}
*/

// --------------------------------------------------------------------------
//
//! Writes a single row of the selected FITS data to the output file.
//!
//! Data type \b not yet implemented:
//!   CCfits::Tnull, CCfits::Tbit, CCfits::Tlogical, CCfits::Tstring,
//!   CCfits::Tcomplex, CCfits::Tdblcomplex, CCfits::VTbit,
//!   CCfits::VTbyte, CCfits::VTlogical, CCfits::VTushort,
//!   CCfits::VTshort, CCfits::VTuint, CCfits::VTint, CCfits::VTulong,
//!   CCfits::VTlong, CCfits::VTlonglong, CCfits::VTfloat,
//!   CCfits::VTdouble, CCfits::VTcomplex, CCfits::VTdblcomplex
//!
//! @param offsets
//!         a vector containing the offsets to the columns (in bytes)
//! @param targetFile
//!         the ofstream where to write to
//! @param fitsBuffer
//!         the memory were the row has been loaded by cfitsio
//
int FitsDumper::WriteRow(ostream &out, const vector<MyColumn*> &list, const vector<int> &offsets, unsigned char* fitsBuffer, const vector<pair<int, int> >& ranges) const
{
    int cnt = 0;
    vector<pair<int, int> >::const_iterator jt = ranges.begin();
    for (vector<MyColumn*>::const_iterator it=list.begin(); it!=list.end(); it++, jt++)
    {
        if (jt == ranges.end())
        {
            cout << "ERROR: END OF RANGE POINTER REACHED" << endl;
            return false;
        }
        const MyColumn *col = *it;

        // CCfits starts counting at 1 not 0
        const int offset = offsets[col->index()-1];

        // Get the pointer to the array which we are supposed to print
        const unsigned char *ptr = fitsBuffer + offset;

        // Loop over all array entries
        int sizeToSkip = 0;
        switch (col->type())
        {
        case CCfits::Tbyte:     sizeToSkip = sizeof(uint8_t); break;
        case CCfits::Tushort:   sizeToSkip = sizeof(uint16_t); break;
        case CCfits::Tuint:
        case CCfits::Tulong:    sizeToSkip = sizeof(uint32_t); break;
        case CCfits::Tshort:    sizeToSkip = sizeof(int16_t); break;
        case CCfits::Tint:
        case CCfits::Tlong:     sizeToSkip = sizeof(int32_t); break;
        case CCfits::Tlonglong: sizeToSkip = sizeof(int64_t); break;
        case CCfits::Tfloat:    sizeToSkip = sizeof(float); break;
        case CCfits::Tdouble:   sizeToSkip = sizeof(double); break;
        default:
            cerr << "Data type not implemented yet." << endl;
            return 0;
        }
        ptr += sizeToSkip*jt->first;
        for (int width=jt->first; width<jt->second; width++)
        {
            switch (col->type())
            {
            case CCfits::Tbyte:     out << PtrToValue<uint8_t> (ptr); break;
            case CCfits::Tushort:   out << PtrToValue<uint16_t>(ptr); break;
            case CCfits::Tuint:
            case CCfits::Tulong:    out << PtrToValue<uint32_t>(ptr); break;
            case CCfits::Tshort:    out << PtrToValue<int16_t> (ptr); break;
            case CCfits::Tint:
            case CCfits::Tlong:     out << PtrToValue<int32_t> (ptr); break;
            case CCfits::Tlonglong: out << PtrToValue<int64_t> (ptr); break;
            case CCfits::Tfloat:    out << PtrToValue<float>   (ptr); break;
            case CCfits::Tdouble:   out << PtrToValue<double>  (ptr); break;

            default:
                cerr << "Data type not implemented yet." << endl;
                return 0;
            }

            out << " ";
            cnt++;
        }
    }

    if (cnt>0)
        out << endl;

    if (out.fail())
    {
        cerr << "ERROR - writing output: " << strerror(errno) << endl;
        return -1;
    }

    return cnt;
}

// --------------------------------------------------------------------------
//
//! Calculates the required buffer size for reading one row of the current
//! table. Also calculates the offsets to all the columns
//
vector<int> FitsDumper::CalculateOffsets() const
{
    map<int,int> sizes;

    for (map<string, MyColumn*>::const_iterator it=fColMap.begin();
         it!=fColMap.end(); it++)
    {
        const int &width = it->second->width();
        const int &idx   = it->second->index();

        const int size = ValueTypeToSize(it->second->type());
        if (size==0)
        {
            cerr << "Data type " << (int)it->second->type() << " not implemented yet." << endl;
            return vector<int>();
        }
//cout << "data size: " << size << " width: " << width << " index: " << idx << endl;
        int realwidth = (width == 0)? 1 : width;
        sizes[idx] = size*realwidth;
    }

    //calculate the offsets in the vector.
    vector<int> result(1, 0);

    int size = 0;
    int idx  = 0;

    for (map<int,int>::const_iterator it=sizes.begin(); it!=sizes.end(); it++)
    {
        size += it->second;
        result.push_back(size);

        if (it->first == ++idx)
            continue;

        cerr << "Expected index " << idx << ", but found " << it->first << endl;
        return vector<int>();
    }

    return result;
}

// --------------------------------------------------------------------------
//
//! Loads the fits file based on the current parameters
//
bool FitsDumper::OpenFile(const string &filename)
{
    if (fFile)
        delete fFile;

    ostringstream str;
    try
    {
        fFile = new CCfits::FITS(filename);
    }
    catch (CCfits::FitsException e)
    {
        cerr << "Could not open FITS file " << filename << " reason: " << e.message() << endl;
        return false;
    }

    return true;
}

bool FitsDumper::OpenTable(const string &tablename)
{
    if (!fFile)
    {
        cerr << "No file open." << endl;
        return false;
    }

    const multimap< string, CCfits::ExtHDU * > extMap = fFile->extension();
    if (extMap.find(tablename) == extMap.end())
    {
        cerr << "Table '" << tablename << "' not found." << endl;
        return false;
    }

    fTable  = dynamic_cast<CCfits::Table*>(extMap.find(tablename)->second);
    if (!fTable)
    {
        cerr << "Object '" << tablename << "' returned not a CCfits::Table." << endl;
        return false;
    }

    map<string, CCfits::Column*>& tCols = fTable->column();
    for (map<string, CCfits::Column*>::const_iterator it = tCols.begin(); it != tCols.end(); it++)
    {
        fColMap.insert(make_pair(it->first, static_cast<MyColumn*>(it->second)));
    }
//    fColMap = fTable->column();

    fTable->makeThisCurrent();

    fTable->getComments();
    fTable->getHistory();
    fTable->readAllKeys();

    return true;
}


void FitsDumper::List()
{
    if (!fFile)
    {
        cerr << "No file open." << endl;
        return;
    }

    cout << "\nFile: " << fFile->name() << "\n";

    const multimap< string, CCfits::ExtHDU * > extMap = fFile->extension();
    for (std::multimap<string, CCfits::ExtHDU*>::const_iterator it=extMap.begin(); it != extMap.end(); it++)
    {

        CCfits::Table *table = dynamic_cast<CCfits::Table*>(extMap.find(it->first)->second);

        table->makeThisCurrent();
        table->readData();

        cout << " " << it->first << " [" << table->rows() << "]\n";

        const map<string, CCfits::Column*> &cols = table->column();

//        for (map<string, CCfits::Column*>::const_iterator id = cols.begin(); ib != cols.end(); ib++)
//        {
//            TFORM
//        }

        for (map<string, CCfits::Column*>::const_iterator ic=cols.begin();
             ic != cols.end(); ic++)
        {
            const MyColumn *col = static_cast<MyColumn*>(ic->second);

            cout << "   " << col->name() << "[" ;
            if (col->width() == 0)
                cout << 1;
            else
                cout << col->width();
            cout  << "] * " << col->scale() << " (" << col->unit() << ":" <<  ValueTypeToStr(col->type())<<  ") " << col->comment() << "\n";
            /*
             inline size_t Column::repeat () const
             inline bool   Column::varLength () const
             inline double Column::zero () const
             inline const  String& Column::display () const
             inline const  String& Column::dimen () const
             inline const  String& Column::TBCOL ()
             inline const  String& Column::TTYPE ()
             inline const  String& Column::TFORM ()
             inline const  String& Column::TDISP ()
             inline const  String& Column::TZERO ()
             inline const  String& Column::TDIM  ()
             inline const  String& Column::TNULL ()
             inline const  String& Column::TLMIN ()
             inline const  String& Column::TLMAX ()
             inline const  String& Column::TDMAX ()
             inline const  String& Column::TDMIN ()
            */
        }
        cout << '\n';
    }
    cout << flush;
}

class MyKeyword : public CCfits::Keyword
{
public:
    CCfits::ValueType keytype() const { return CCfits::Keyword::keytype(); }
};

void FitsDumper::ListKeywords(ostream &out)
{
    map<string,CCfits::Keyword*> keys = fTable->keyWord();
    for (map<string,CCfits::Keyword*>::const_iterator it=keys.begin();
         it!=keys.end(); it++)
    {
        string str;
        double d;
        int l;

        const MyKeyword *kw = static_cast<MyKeyword*>(it->second);
        kw->keytype();
        out << "## " << setw(8) << kw->name() << " = " << setw(10);
        if (kw->keytype()==16)
            out << ("'"+kw->value(str)+"'");
        if (kw->keytype()==31)
            out << kw->value(l);
        if (kw->keytype()==82)
            out << kw->value(d);
        out << " / " << kw->comment() << endl;
    }
}

void FitsDumper::ListHeader()
{
    if (!fTable)
    {
        cerr << "No table open." << endl;
        return;
    }

    cout << "\nTable: " << fTable->name() << " (rows=" << fTable->rows() << ")\n";
    if (!fTable->comment().empty())
        cout << "Comment: \t" << fTable->comment() << '\n';
    if (!fTable->history().empty())
        cout << "History: \t" << fTable->history() << '\n';

    ListKeywords(cout);
    cout << endl;
}

bool FitsDumper::separateColumnsFromRanges(const vector<string>& list,
                               vector<pair<int, int> >& ranges,
                               vector<string>& listNamesOnly)
{
    for (vector<string>::const_iterator it=list.begin(); it!=list.end(); it++)
    {
        string columnNameOnly = *it;
        unsigned long bracketIndex0 = columnNameOnly.find_first_of('[');
        unsigned long bracketIndex1 = columnNameOnly.find_first_of(']');
        unsigned long colonIndex = columnNameOnly.find_first_of(':');
//        cout << bracketIndex0 << " " << bracketIndex1 << " " << colonIndex << endl;
        int columnStart = -1;
        int columnEnd = -1;
        if (bracketIndex0 != string::npos)
        {//there is a range given. Extract the range
            if (colonIndex != string::npos)
            {//we have a range here
                columnStart = atoi(columnNameOnly.substr(bracketIndex0+1, colonIndex-(bracketIndex0+1)).c_str());
                columnEnd = atoi(columnNameOnly.substr(colonIndex+1, bracketIndex1-(colonIndex+1)).c_str());
                columnEnd++;
            }
            else
            {//only a single index there
                columnStart = atoi(columnNameOnly.substr(bracketIndex0+1, bracketIndex1 - (bracketIndex0+1)).c_str());
                columnEnd = columnStart+1;
//                cout << "Cstart " << columnStart  << " end: " << columnEnd << endl;
            }
            columnNameOnly = columnNameOnly.substr(0, bracketIndex0);
        }

        if (fColMap.find(columnNameOnly) == fColMap.end())
        {
            cerr << "ERROR - Column '" << columnNameOnly << "' not found in table." << endl;
            return false;
        }
//        cout << "The column name is: " << columnNameOnly << endl;
        MyColumn *cCol = static_cast<MyColumn*>(fColMap.find(columnNameOnly)->second);
        if (bracketIndex0 == string::npos)
        {//no range given: use the full range
            ranges.push_back(make_pair(0, cCol->width()));
            columnStart = 0;
            columnEnd = 1;
        }
        else
        {//use the range extracted earlier
            if (columnStart < 0)
            {
                cerr << "ERROR - Start range for column " << columnNameOnly << " is less than zero (" << columnStart << "). Aborting" << endl;
                return false;
            }
            if (columnEnd > cCol->width())
            {
                cerr << "ERROR - End range for column " << columnNameOnly << " is greater than the last element (" << cCol->width() << " vs " << columnEnd << "). Aborting" << endl;
                return false;
            }
            ranges.push_back(make_pair(columnStart, columnEnd));
        }
//        cout << "Will be exporting from " << columnStart << " to " << columnEnd-1 << " for column " << columnNameOnly << endl;
        listNamesOnly.push_back(columnNameOnly);
    }
    return true;
}
// --------------------------------------------------------------------------
//
//! Perform the actual dump, based on the current parameters
//
bool FitsDumper::Dump(const string &filename, const vector<string> &list, int precision)
{

    //first of all, let's separate the columns from their ranges and check that the requested columns are indeed part of the file
    vector<pair<int, int> > ranges;
    vector<string> listNamesOnly;

    if (!separateColumnsFromRanges(list, ranges, listNamesOnly))
    {
        cerr << "Something went wrong while extracting the columns names from parameters. Aborting" << endl;
        return false;
    }

    // FIXME: Maybe do this when opening a table?
    const vector<int> offsets = CalculateOffsets();
 //   for (int i=0;i<offsets.size(); i++)
 //       cout << offsets[i] << " ";
//    cout << endl;
    if (offsets.size()==0)
        return false;

    ofstream out(filename=="-"?"/dev/stdout":filename);
    if (!out)
    {
        cerr << "Cannot open file " << filename << ": " << strerror(errno) << endl;
        return false;
    }

    out.precision(precision);

    out << "## --------------------------------------------------------------------------\n";
    if (filename!="-")
        out << "## File:    \t" << filename << '\n';
    out << "## Table:   \t" << fTable->name() << '\n';
    if (!fTable->comment().empty())
        out << "## Comment: \t" << fTable->comment() << '\n';
    if (!fTable->history().empty())
        out << "## History: \t" << fTable->history() << '\n';
    out << "## NumRows: \t" << fTable->rows() << '\n';
    out << "## --------------------------------------------------------------------------\n";
    ListKeywords(out);
    out << "## --------------------------------------------------------------------------\n";
    out << "#\n";
    //vector<pair<int, int> >::const_iterator rangesIt = ranges.begin();
    //the above is soooo yesterday ;) let's try the new auto feature of c++0x
    auto rangesIt = ranges.begin();
    for (vector<string>::const_iterator it=listNamesOnly.begin(); it!=listNamesOnly.end(); it++, rangesIt++)
    {
        const MyColumn *col = static_cast<MyColumn*>(fTable->column()[*it]);
        if (rangesIt->first != 0 || rangesIt->second != col->width())
        {
            out << "#";
            for (int i=rangesIt->first; i<rangesIt->second; i++)
                out << " " << col->name() << "[" << i << "]";
            out << ": " << col->unit();
        }
        else
            out << "# " << col->name() << "[" << col->width() << "]: " << col->unit();

        if (!col->comment().empty())
            out << " (" <<col->comment() << ")";
        out << '\n';
    }
    out << "#" << endl;


    // Loop over all columns in our list of requested columns
    vector<MyColumn*> columns;
    for (vector<string>::const_iterator it=listNamesOnly.begin(); it!=listNamesOnly.end(); it++)
    {
        MyColumn *cCol = static_cast<MyColumn*>(fColMap.find(*it)->second);
        columns.push_back(cCol);
    }
    const int size = offsets[offsets.size()-1];
    unsigned char* fitsBuffer = new unsigned char[size];

    int status = 0;
    for (int i=1; i<=fTable->rows(); i++)
    {
        fits_read_tblbytes(fFile->fitsPointer(), i, 1, size, fitsBuffer, &status);
        if (status)
        {
            cerr << "An error occurred while reading fits row #" << i << " error code: " << status << endl;
            break;
        }
        if (WriteRow(out, columns, offsets, fitsBuffer, ranges)<0)
        {
            status=1;
            break;
        }
    }
    delete[] fitsBuffer;

    return status==0;
}

// --------------------------------------------------------------------------
//
//! Perform the actual dump, based on the current parameters
//
/*
bool FitsDumper::Plot(const vector<string> &list)
{
   for (vector<string>::const_iterator it=list.begin(); it!=list.end(); it++)
        if (fColMap.find(*it) == fColMap.end())
        {
            cerr << "WARNING - Column '" << *it << "' not found in table." << endl;
            return false;
        }

    // FIXME: Maybe do this when opening a table?
    const vector<int> offsets = CalculateOffsets();
    if (offsets.size()==0)
        return false;

    // Loop over all columns in our list of requested columns
    const CCfits::Column *col[3] =
    {
        list.size()>0 ? fColMap.find(list[0])->second : 0,
        list.size()>1 ? fColMap.find(list[1])->second : 0,
        list.size()>2 ? fColMap.find(list[2])->second : 0
    };

    const int size = offsets[offsets.size()-1];
    unsigned char* fitsBuffer = new unsigned char[size];

    const int idx = 0;

    // CCfits starts counting at 1 not 0
    const size_t pos[3] =
    {
        col[0] ? offsets[col[0]->index()-1] + idx*col[0]->width()*ValueTypeToSize(col[0]->type()) : 0,
        col[1] ? offsets[col[1]->index()-1] + idx*col[1]->width()*ValueTypeToSize(col[1]->type()) : 0,
        col[2] ? offsets[col[2]->index()-1] + idx*col[2]->width()*ValueTypeToSize(col[2]->type()) : 0
    };

    int status = 0;
    for (int i=1; i<=fTable->rows(); i++)
    {
        fits_read_tblbytes(fFile->fitsPointer(), i, 1, size, fitsBuffer, &status);
        if (status)
        {
            cerr << "An error occurred while reading fits row #" << i << " error code: " << status << endl;
            break;
        }

        try
        {
            const double x[3] =
            {
                col[0] ? PtrToDouble(fitsBuffer+pos[0], col[0]->type()) : 0,
                col[1] ? PtrToDouble(fitsBuffer+pos[1], col[1]->type()) : 0,
                col[2] ? PtrToDouble(fitsBuffer+pos[2], col[2]->type()) : 0
            };
        }
        catch (const runtime_error &e)
        {
            cerr << e.what() << endl;
            status=1;
            break;
        }
    }
    delete[] fitsBuffer;

    return status==0;
}
*/

// --------------------------------------------------------------------------
//
//! Retrieves the configuration parameters
//! @param conf
//!             the configuration object
//
int FitsDumper::ExecConfig(Configuration& conf)
{
    if (conf.Has("fitsfile"))
    {
        if (!OpenFile(conf.Get<string>("fitsfile")))
            return -1;
    }

    if (conf.Get<bool>("list"))
        List();

    if (conf.Has("tablename"))
    {
        if (!OpenTable(conf.Get<string>("tablename")))
            return -1;
    }

#ifdef PLOTTING_PLEASE
    if (conf.Get<bool>("graph"))
    {
        if (!conf.Has("col"))
        {
            cout << "Please specify the columns that should be dumped as arguments. Aborting" << endl;
            return 0;
        }
        doCurvesDisplay(conf.Get<vector<string>>("col"),
                        conf.Get<string>("tablename"));
        return 1;
    }
#endif

    if (conf.Get<bool>("header"))
        ListHeader();

    if (conf.Get<bool>("header") || conf.Get<bool>("list"))
        return 1;

    if (conf.Has("outfile"))
    {
        if (!conf.Has("col"))
        {
            cout << "Please specify the columns that should be dumped as arguments. Aborting" << endl;
            return 0;
        }
        if (!Dump(conf.Get<string>("outfile"),
                  conf.Get<vector<string>>("col"),
                  conf.Get<int>("precision")))
            return -1;
    }


    return 0;
}

void PrintUsage()
{
    cout <<
        "fitsdump is a tool to dump data from a FITS table as ascii.\n"
        "\n"
        "Usage: fitsdump [OPTIONS] fitsfile col col ... \n"
        "  or:  fitsdump [OPTIONS]\n";
    cout << endl;
}

void PrintHelp()
{
    // 
}
#ifdef PLOTTING_PLEASE
int FitsDumper::doCurvesDisplay( const vector<string> &list, const string& tableName)
{
    //first of all, let's separate the columns from their ranges and check that the requested columns are indeed part of the file
     vector<pair<int, int> > ranges;
     vector<string> listNamesOnly;
     if (!separateColumnsFromRanges(list, ranges, listNamesOnly))
     {
         cerr << "Something went wrong while extracting the columns names from parameters. Aborting" << endl;
         return false;
     }
     vector<string> curvesNames;
     stringstream str;
     for (auto it=ranges.begin(), jt=listNamesOnly.begin(); it != ranges.end(); it++, jt++)
     {
         for (int i=it->first; i<it->second;i++)
         {
                 str.str("");
                 str << *jt << "[" << i << "]";
                 curvesNames.push_back(str.str());
         }
     }
     char* handle = new char[17];
     sprintf(handle,"FitsDump Display");
//    Qt::HANDLE h = *handle;//NULL
    int argc = 1;
    char ** argv = &handle;
    QApplication a(argc, argv);



    QwtPlot* plot = new QwtPlot();
    QwtPlotGrid* grid = new QwtPlotGrid;
    grid->enableX(false);
    grid->enableY(true);
    grid->enableXMin(false);
    grid->enableYMin(false);
    grid->setMajPen(QPen(Qt::black, 0, Qt::DotLine));
    grid->attach(plot);
    plot->setAutoReplot(true);
    string title = tableName;
    plot->setAxisScaleDraw(  QwtPlot::xBottom, new TimeScaleDraw());

    QWidget window;
    QHBoxLayout* layout = new QHBoxLayout(&window);
    layout->setContentsMargins(0,0,0,0);
    layout->addWidget(plot);

    QwtPlotZoomer zoom(plot->canvas());
    zoom.setRubberBandPen(QPen(Qt::gray, 2, Qt::DotLine));
    zoom.setTrackerPen(QPen(Qt::gray));
    int totalSize = 0;
    for (unsigned int i=0;i<list.size();i++)
        totalSize += ranges[i].second - ranges[i].first;
    cout << "Total size: " << totalSize << endl;
    vector<QwtPlotCurve*> curves(totalSize);
    int ii=0;
    for (auto it = curves.begin(), jt=curvesNames.begin(); it != curves.end(); it++, jt++)
    {
        *it = new QwtPlotCurve(jt->c_str());
        switch (ii%6)
        {
        case 0:
            (*it)->setPen(QColor(255,0,0));
            break;
        case 1:
            (*it)->setPen(QColor(0,255,0));
            break;
        case 2:
            (*it)->setPen(QColor(0,0,255));
            break;
        case 3:
            (*it)->setPen(QColor(255,255,0));
            break;
        case 4:
            (*it)->setPen(QColor(0,255,255));
            break;
        case 5:
            (*it)->setPen(QColor(255,0,255));
            break;
        default:
            (*it)->setPen(QColor(0,0,0));
        };
        ii++;
        (*it)->setStyle(QwtPlotCurve::Lines);
        (*it)->attach(plot);
    }
    plot->insertLegend(new QwtLegend(), QwtPlot::RightLegend);

     const vector<int> offsets = CalculateOffsets();
     if (offsets.size()==0)
         return false;


     // Loop over all columns in our list of requested columns
      vector<MyColumn*> columns;
      for (vector<string>::const_iterator it=listNamesOnly.begin(); it!=listNamesOnly.end(); it++)
      {
          MyColumn *cCol = static_cast<MyColumn*>(fColMap.find(*it)->second);
          columns.push_back(cCol);
      }
      //add the time column to the given columns
      MyColumn* timeCol = static_cast<MyColumn*>(fColMap.find("Time")->second);
      if (!timeCol)
      {
          cerr << "Error: time column could not be found in given table. Aborting" << endl;
          return false;
      }
      columns.push_back(timeCol);
      ranges.push_back(make_pair(0,1));
      /////
      const int size = offsets[offsets.size()-1];
      unsigned char* fitsBuffer = new unsigned char[size];

//      stringstream str;
      str.str("");
      int status = 0;

      vector<double*> xValues(totalSize);
      double* yValues;
      cout.precision(10);
      str.precision(20);
      for (auto it=xValues.begin(); it!=xValues.end(); it++)
          *it = new double[fTable->rows()];

      yValues = new double[fTable->rows()];

      for (int i=1; i<=fTable->rows(); i++)
      {
          fits_read_tblbytes(fFile->fitsPointer(), i, 1, size, fitsBuffer, &status);
          if (status)
          {
              cerr << "An error occurred while reading fits row #" << i << " error code: " << status << endl;
              break;
          }
          if (WriteRow(str, columns, offsets, fitsBuffer, ranges)<0)
          {
              status=1;
              cerr << "An Error occured while reading the fits row " << i << endl;
              return -1;
          }
//          yValues[i-1] = i;
          for (auto it=xValues.begin(); it!= xValues.end(); it++)
          {
              str >> (*it)[i-1];
//              cout << (*it)[i-1] << " ";
          }
          str >> yValues[i-1];
          if (i==1)
          {
              Time t(yValues[0]);
              title += " - " + t.GetAsStr("%Y-%m-%d");
              plot->setTitle(title.c_str());
          }
//          cout << yValues[i-1] << " ";
//          cout << endl;
      }
      //set the actual data.
      auto jt = xValues.begin();
      for (auto it=curves.begin(); it != curves.end(); it++, jt++)
          (*it)->setRawData(yValues, *jt, fTable->rows());

      QStack<QRectF> stack;
      double minX, minY, maxX, maxY;
      minX = minY = 1e10;
      maxX = maxY = -1e10;
      QRectF rect;
      QPointF point;
      for (auto it=curves.begin(); it!= curves.end(); it++)
      {
          rect = (*it)->boundingRect();
          point = rect.bottomRight();
          if (point.x() < minX) minX = point.x();
          if (point.y() < minY) minY = point.y();
          if (point.x() > maxX) maxX = point.x();
          if (point.y() > maxY) maxY = point.y();
          point = rect.topLeft();
          if (point.x() < minX) minX = point.x();
          if (point.y() < minY) minY = point.y();
          if (point.x() > maxX) maxX = point.x();
          if (point.y() > maxY) maxY = point.y();
      }
      QPointF bottomRight(maxX, minY);
      QPointF topLeft(minX, maxY);
      QPointF center((bottomRight+topLeft)/2.f);
      stack.push(QRectF(topLeft + (topLeft-center)*(.5f),bottomRight + (bottomRight-center)*(.5f)));
      zoom.setZoomStack(stack);

      delete[] fitsBuffer;
    window.resize(600, 400);
    window.show();

    a.exec();

    for (auto it = curves.begin(); it != curves.end(); it++)
        delete *it;
    for (auto it = xValues.begin(); it != xValues.end(); it++)
        delete[] *it;
    delete[] yValues;
    delete[] handle;
    return 0;
}
#endif

void SetupConfiguration(Configuration& conf)
{
    po::options_description configs("Fitsdump options");
    configs.add_options()
        ("fitsfile,f",  var<string>()
#if BOOST_VERSION >= 104200
         ->required()
#endif
                                                  , "Name of FITS file")
        ("tablename,t", var<string>("DATA")
#if BOOST_VERSION >= 104200
         ->required()
#endif
                                                  , "Name of input table")
        ("col,c",       vars<string>(),             "List of columns to dump\narg is a list of columns, separated by a space.\nAdditionnally, a list of sub-columns can be added\ne.g. Data[3] will dump sub-column 3 of column Data\nData[3:4] will dump sub-columns 3 and 4\nOmitting this argument dump the entire column\nnota: all indices start at zero")
        ("outfile,o",   var<string>("/dev/stdout"), "Name of output file (-:/dev/stdout)")
        ("precision,p", var<int>(20),               "Precision of ofstream")
        ("list,l",      po_switch(),                "List all tables and columns in file")
        ("header,h",    po_switch(),                "Dump header of given table")
#ifdef PLOTTING_PLEASE
        ("graph,g",     po_switch(),                "Plot the columns instead of dumping them")
#endif
        ;

    po::positional_options_description p;
    p.add("fitsfile",   1); // The first positional options
    p.add("col",       -1); // All others

    conf.AddOptions(configs);
    conf.SetArgumentPositions(p);
}

int main(int argc, const char** argv)
{
    Configuration conf(argv[0]);
    conf.SetPrintUsage(PrintUsage);
    SetupConfiguration(conf);

    if (!conf.DoParse(argc, argv, PrintHelp))
        return -1;

    FitsDumper loader;
    return loader.ExecConfig(conf);
}
