// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2020.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Mathias Walzer $
// $Authors: Mathias Walzer $
// --------------------------------------------------------------------------

#include <OpenMS/FORMAT/QcMLFile.h>
#include <OpenMS/SYSTEM/File.h>

#include <fstream>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>

using namespace std;

// TODO fix all the shadowed "const_iterator qpsit"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"

namespace OpenMS
{

  QcMLFile::QualityParameter::QualityParameter() :
    name(),
    id(),
    value(),
    cvRef(),
    cvAcc(),
    unitRef(),
    unitAcc(),
    flag()
  {
  }

  QcMLFile::QualityParameter::QualityParameter(const QualityParameter& rhs) :
    name(rhs.name),
    id(rhs.id),
    value(rhs.value),
    cvRef(rhs.cvRef),
    cvAcc(rhs.cvAcc),
    unitRef(rhs.unitRef),
    unitAcc(rhs.unitAcc),
    flag(rhs.flag)
  {
  }

  QcMLFile::QualityParameter& QcMLFile::QualityParameter::operator=(const QualityParameter& rhs)
  {
    if (this != &rhs)
    {
      name = rhs.name;
      id = rhs.id;
      value = rhs.value;
      cvRef = rhs.cvRef;
      cvAcc = rhs.cvAcc;
      unitRef = rhs.unitRef;
      unitAcc = rhs.unitAcc;
      flag = rhs.flag;
    }
    return *this;
  }

  bool QcMLFile::QualityParameter::operator<(const QualityParameter& rhs) const
  {
    return name.toQString() < rhs.name.toQString();
  }

  bool QcMLFile::QualityParameter::operator>(const QualityParameter& rhs) const
  {
    return name.toQString() > rhs.name.toQString();
  }

  bool QcMLFile::QualityParameter::operator==(const QualityParameter& rhs) const
  {
    return name.toQString() == rhs.name.toQString();
  }

  String QcMLFile::QualityParameter::toXMLString(UInt indentation_level) const
  {
    String indent = String(indentation_level, '\t');
    String s = indent;
    s += "<qualityParameter";
    s += " name=\"" + name + "\"" + " ID=\"" + id + "\"" + " cvRef=\"" + cvRef + "\"" + " accession=\"" + cvAcc + "\"";
    if (value != "")
    {
      s += " value=\"" + value + "\"";
    }
    if (unitRef != "")
    {
      s += " unitRef=\"" + unitRef + "\"";
    }
    if (unitAcc != "")
    {
      s += " unitAcc=\"" + unitAcc + "\"";
    }
    if (!flag.empty())
    {
      s += " flag=\"true\"";
    }

    s += "/>\n";

    return s;
  }

  QcMLFile::Attachment::Attachment() :
    name(),
    id(),
    value(),
    cvRef(),
    cvAcc(),
    unitRef(),
    unitAcc(),
    qualityRef(),
    colTypes(),
    tableRows()
  {
  }

  QcMLFile::Attachment::Attachment(const Attachment& rhs) :
    name(rhs.name),
    id(rhs.id),
    value(rhs.value),
    cvRef(rhs.cvRef),
    cvAcc(rhs.cvAcc),
    unitRef(rhs.unitRef),
    unitAcc(rhs.unitAcc),
    binary(rhs.binary),
    qualityRef(rhs.qualityRef),
    colTypes(rhs.colTypes),
    tableRows(rhs.tableRows)
  {
  }

  QcMLFile::Attachment& QcMLFile::Attachment::operator=(const Attachment& rhs)
  {
    if (this != &rhs)
    {
      name = rhs.name;
      id = rhs.id;
      value = rhs.value;
      cvRef = rhs.cvRef;
      cvAcc = rhs.cvAcc;
      unitRef = rhs.unitRef;
      unitAcc = rhs.unitAcc;
      binary = rhs.binary;
      qualityRef = rhs.qualityRef;
      colTypes = rhs.colTypes;
      tableRows = rhs.tableRows;
    }
    return *this;
  }

  bool QcMLFile::Attachment::operator<(const Attachment& rhs) const
  {
    return name.toQString() < rhs.name.toQString();
  }

  bool QcMLFile::Attachment::operator>(const Attachment& rhs) const
  {
    return name.toQString() > rhs.name.toQString();
  }

  bool QcMLFile::Attachment::operator==(const Attachment& rhs) const
  {
    return name.toQString() == rhs.name.toQString();
  }

  String QcMLFile::Attachment::toCSVString(const String separator) const
  {
    String s = "";
    if ((!colTypes.empty()) && (!tableRows.empty()))
    {
      String replacement = "_";
      if (separator == replacement)
      {
        replacement = "$";
      }
      std::vector<String> copy = colTypes;
      for (std::vector<String>::iterator it = copy.begin(); it != copy.end(); ++it)
      {
        it->substitute(separator, replacement);
      }
      s += ListUtils::concatenate(copy, separator).trim();
      s += "\n";
      for (std::vector<std::vector<String> >::const_iterator it = tableRows.begin(); it != tableRows.end(); ++it)
      {
        std::vector<String> copy_row = *it;
        for (std::vector<String>::iterator sit = copy_row.begin(); sit != copy_row.end(); ++sit)
        {
          sit->substitute(separator, replacement);
        }
        s += ListUtils::concatenate(copy_row, separator).trim();
        s += "\n";
      }
    }
    return s;
  }

  //TODO CHANGE TO ACCEPTABLE WAY OF GENERATING A PRETTY XML FILE
  String QcMLFile::Attachment::toXMLString(UInt indentation_level) const
  {
    //TODO manage IDREF to qp internally
    String indent = String(indentation_level, '\t');
    String s = indent;
    s += "<attachment ";
    s += " name=\"" + name + "\"" + " ID=\"" + id + "\"" + " cvRef=\"" + cvRef + "\"" + " accession=\"" + cvAcc + "\"";
    if (value != "")
    {
      s += " value=\"" + value + "\"";
    }
    if (unitRef != "")
    {
      s += " unitRef=\"" + unitRef + "\"";
    }
    if (unitAcc != "")
    {
      s += " unitAcc=\"" + unitAcc + "\"";
    }
    if (qualityRef != "")
    {
      s += " qualityParameterRef=\"" + qualityRef + "\"";
    }

    if (binary != "")
    {
      s += ">\n";
      s += indent + "\t" + "<binary>" + binary + "</binary>\n";
      s += indent + "</attachment>\n";
    }
    else if ((!colTypes.empty()) && (!tableRows.empty()))
    {
      s += ">\n";
      s += "<table>";
      s += indent + "\t" + "<tableColumnTypes>";

      std::vector<String> copy = colTypes;
      for (std::vector<String>::iterator it = copy.begin(); it != copy.end(); ++it)
      {
        it->substitute(String(" "), String("_"));
      }

      s += ListUtils::concatenate(copy, " ").trim();
      s += "</tableColumnTypes>\n";
      for (std::vector<std::vector<String> >::const_iterator it = tableRows.begin(); it != tableRows.end(); ++it)
      {
        s += indent + "\t" + "<tableRowValues>";

        std::vector<String> copy_row = *it;
        for (std::vector<String>::iterator sit = copy_row.begin(); sit != copy_row.end(); ++sit)
        {
          sit->substitute(String(" "), String("_"));
        }

        s += ListUtils::concatenate(*it, " ").trim();
        s += "</tableRowValues>\n";
      }
      s += "</table>";
      s += indent + "</attachment>\n";
    }
    else
    {
      //TODO warning invalid attachment!
      return "";
    }

    return s;
  }

  QcMLFile::QcMLFile() :
    XMLHandler("", "0.7"), XMLFile("/SCHEMAS/qcml.xsd", "0.7"), ProgressLogger() //TODO keep version uptodate
  {
  }

  QcMLFile::~QcMLFile()
  {

  }

  void QcMLFile::addRunQualityParameter(String run_id, QualityParameter qp)
  {
    // TODO warn that run has to be registered!
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = runQualityQPs_.find(run_id); //if 'filename is a ID:'
    if (qpsit != runQualityQPs_.end())
    {
      runQualityQPs_[run_id].push_back(qp);
    }
    else
    {
      std::map<String, String>::const_iterator qpsit = run_Name_ID_map_.find(run_id); //if 'filename' is a name
      if (qpsit != run_Name_ID_map_.end())
      {
        runQualityQPs_[qpsit->second].push_back(qp);
      }
    }
    //TODO redundancy check
  }

  void QcMLFile::addSetQualityParameter(String set_id, QualityParameter qp)
  {
    // TODO warn that set has to be registered!
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = setQualityQPs_.find(set_id); //if 'filename is a ID:'
    if (qpsit != setQualityQPs_.end())
    {
      setQualityQPs_[set_id].push_back(qp);
    }
    else
    {
      std::map<String, String>::const_iterator qpsit = set_Name_ID_map_.find(set_id); //if 'filename' is a name
      if (qpsit != set_Name_ID_map_.end())
      {
        setQualityQPs_[qpsit->second].push_back(qp);
      }
    }
    //TODO redundancy check
  }

  void QcMLFile::addRunAttachment(String run_id, Attachment at)
  {
    runQualityAts_[run_id].push_back(at); //TODO permit AT without a QP (or enable orphan writeout in store),redundancy check
  }

  void QcMLFile::addSetAttachment(String run_id, Attachment at)
  {
    setQualityAts_[run_id].push_back(at); //TODO add file QP to set member
  }

  void QcMLFile::getRunNames(std::vector<String>& ids) const
  {
    ids.clear();
    boost::copy(run_Name_ID_map_ | boost::adaptors::map_keys, std::back_inserter(ids));
  }

  void QcMLFile::getRunIDs(std::vector<String>& ids) const
  {
    ids.clear();
    boost::copy(runQualityQPs_ | boost::adaptors::map_keys, std::back_inserter(ids));
  }

  bool QcMLFile::existsRun(const String filename, bool checkname) const
  {
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = runQualityQPs_.find(filename); //if 'filename is a ID:'
    if (qpsit != runQualityQPs_.end()) //NO, do not!: permit AT without a QP
    {
      return true;
    }
    else if (checkname)
    {
      std::map<String, String>::const_iterator qpsit = run_Name_ID_map_.find(filename); //if 'filename' is a name
      if (qpsit != run_Name_ID_map_.end()) //NO, do not!: permit AT without a QP
      {
        return true;
      }

    }
    return false;
  }

  bool QcMLFile::existsSet(const String filename, bool checkname) const
  {
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = setQualityQPs_.find(filename); //if 'filename is a ID:'
    if (qpsit != setQualityQPs_.end()) //NO, do not!: permit AT without a QP
    {
      return true;
    }
    else if (checkname)
    {
      std::map<String, String>::const_iterator qpsit = set_Name_ID_map_.find(filename); //if 'filename' is a name
      if (qpsit != set_Name_ID_map_.end()) //NO, do not!: permit AT without a QP
      {
        return true;
      }
    }
    return false;
  }

  void QcMLFile::existsRunQualityParameter(const String filename, const String qpname, std::vector<String>& ids) const
  {
    ids.clear();
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = runQualityQPs_.find(filename);
    if (qpsit == runQualityQPs_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = run_Name_ID_map_.find(filename);
      if (mapsit != run_Name_ID_map_.end())
      {
        qpsit = runQualityQPs_.find(mapsit->second);
      }
    }
    if (qpsit != runQualityQPs_.end())
    {
      for (std::vector<QcMLFile::QualityParameter>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
      {
        if (qpname == qit->cvAcc)
        {
          ids.push_back(qit->id);
        }
      }
    }
  }

  void QcMLFile::existsSetQualityParameter(const String filename, const String qpname, std::vector<String>& ids) const
  {
    ids.clear();
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = setQualityQPs_.find(filename);
    if (qpsit == setQualityQPs_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = set_Name_ID_map_.find(filename);
      if (mapsit != set_Name_ID_map_.end())
      {
        qpsit = setQualityQPs_.find(mapsit->second);
      }
    }
    if (qpsit != setQualityQPs_.end())
    {
      for (std::vector<QcMLFile::QualityParameter>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
      {
        //~ std::cout << qit->name << "setexists" << std::endl;
        //~ std::cout << qpname << "qpname" << std::endl;
        if (qpname == qit->cvAcc)
        {
          ids.push_back(qit->id);
        }
      }
    }
  }

  void QcMLFile::removeQualityParameter(String r, std::vector<String>& ids)
  {
    removeAttachment(r, ids);
    for (Size i = 0; i < ids.size(); ++i)
    {
      std::vector<QcMLFile::QualityParameter>::iterator qit = runQualityQPs_[r].begin();
      while (qit != runQualityQPs_[r].end())
      {
        if (qit->id == ids[i])
        {
          qit = runQualityQPs_[r].erase(qit);
        }
        else
        {
          ++qit;
        }
      }
      qit = setQualityQPs_[r].begin();
      while (qit != setQualityQPs_[r].end())
      {
        if (qit->id == ids[i])
        {
          qit = setQualityQPs_[r].erase(qit);
        }
        else
        {
          ++qit;
        }
      }
    }
  }

  void QcMLFile::removeAttachment(String r, std::vector<String>& ids, String at)
  {
    bool not_all = at.size();
    for (Size i = 0; i < ids.size(); ++i)
    {
      std::vector<QcMLFile::Attachment>::iterator qit = runQualityAts_[r].begin();
      while (qit != runQualityAts_[r].end())
      {
        if (qit->qualityRef == ids[i] && ((qit->name == at) || (!not_all)))
        {
          qit = runQualityAts_[r].erase(qit);
        }
        else
        {
          ++qit;
        }
      }
      qit = setQualityAts_[r].begin();
      while (qit != setQualityAts_[r].end())
      {
        if (qit->qualityRef == ids[i] && ((qit->name == at) || (!not_all)))
        {
          qit = setQualityAts_[r].erase(qit);
        }
        else
        {
          ++qit;
        }
      }
    }
  }

  void QcMLFile::removeAttachment(String r, String at)
  {
    if (existsRun(r))
    {
      std::vector<QcMLFile::Attachment>::iterator qit = runQualityAts_[r].begin();
      //~ cout << "remove from " << r << endl;
      while (qit != runQualityAts_[r].end())
      {
        if (qit->cvAcc == at)
        {
          qit = runQualityAts_[r].erase(qit);
          //~ cout << "remove  " << at << endl;
        }
        else
        {
          ++qit;
        }
      }
    }
    if (existsSet(r))
    {
      std::vector<QcMLFile::Attachment>::iterator qit = setQualityAts_[r].begin();
      while (qit != setQualityAts_[r].end())
      {
        if (qit->cvAcc == at)
        {
          qit = setQualityAts_[r].erase(qit);
        }
        else
        {
          ++qit;
        }
      }
    }
  }

  void QcMLFile::removeAllAttachments(String at)
  {
    for (std::map<String, std::vector<Attachment> >::iterator it = runQualityAts_.begin(); it != runQualityAts_.end(); ++it)
    {
      removeAttachment(it->first, at);
    }
  }

  void QcMLFile::registerRun(const String id, const String name)
  {
    runQualityQPs_[id] = std::vector<QualityParameter>();
    runQualityAts_[id] = std::vector<Attachment>();
    run_Name_ID_map_[name] = id;
  }

  void QcMLFile::registerSet(const String id, const String name, const std::set<String>& names)
  {
    setQualityQPs_[id] = std::vector<QualityParameter>();
    setQualityAts_[id] = std::vector<Attachment>();
    set_Name_ID_map_[name] = id;
    setQualityQPs_members_[id] = names;
  }

  void QcMLFile::merge(const QcMLFile& addendum, String setname)
  {
    //~ runs (and create set if setname not empty)
    for (std::map<String, std::vector<QualityParameter> >::const_iterator it = addendum.runQualityQPs_.begin(); it != addendum.runQualityQPs_.end(); ++it)
    {
      runQualityQPs_[it->first].insert(runQualityQPs_[it->first].end(), it->second.begin(), it->second.end());
      std::sort(runQualityQPs_[it->first].begin(), runQualityQPs_[it->first].end());
      runQualityQPs_[it->first].erase(std::unique(runQualityQPs_[it->first].begin(), runQualityQPs_[it->first].end()), runQualityQPs_[it->first].end());
      if (setname != "")
      {
        setQualityQPs_members_[setname].insert(it->first);
      }
    }
    for (std::map<String, std::vector<Attachment> >::const_iterator it = addendum.runQualityAts_.begin(); it != addendum.runQualityAts_.end(); ++it)
    {
      runQualityAts_[it->first].insert(runQualityAts_[it->first].end(), it->second.begin(), it->second.end());
      std::sort(runQualityAts_[it->first].begin(), runQualityAts_[it->first].end());
      runQualityAts_[it->first].erase(std::unique(runQualityAts_[it->first].begin(), runQualityAts_[it->first].end()), runQualityAts_[it->first].end());
      if (setname != "")
      {
        setQualityQPs_members_[setname].insert(it->first);
      }
    }

    // sets
    //~ TODO sets are not supposed to overlap - throw error if so
    setQualityQPs_members_.insert(addendum.setQualityQPs_members_.begin(), addendum.setQualityQPs_members_.end());
    for (std::map<String, std::vector<QualityParameter> >::const_iterator it = addendum.setQualityQPs_.begin(); it != addendum.setQualityQPs_.end(); ++it)
    {
      setQualityQPs_[it->first].insert(setQualityQPs_[it->first].end(), it->second.begin(), it->second.end());
      std::sort(setQualityQPs_[it->first].begin(), setQualityQPs_[it->first].end());
      setQualityQPs_[it->first].erase(std::unique(setQualityQPs_[it->first].begin(), setQualityQPs_[it->first].end()), setQualityQPs_[it->first].end());
    }
    for (std::map<String, std::vector<Attachment> >::const_iterator it = addendum.setQualityAts_.begin(); it != addendum.setQualityAts_.end(); ++it)
    {
      setQualityAts_[it->first].insert(setQualityAts_[it->first].end(), it->second.begin(), it->second.end());
      std::sort(setQualityAts_[it->first].begin(), setQualityAts_[it->first].end());
      setQualityAts_[it->first].erase(std::unique(setQualityAts_[it->first].begin(), setQualityAts_[it->first].end()), setQualityAts_[it->first].end());
    }
  }

  String QcMLFile::exportAttachment(const String filename, const String qpname) const
  {
    std::map<String, std::vector<QcMLFile::Attachment> >::const_iterator qpsit = runQualityAts_.find(filename);
    if (qpsit == runQualityAts_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = run_Name_ID_map_.find(filename);
      if (mapsit != run_Name_ID_map_.end())
      {
        qpsit = runQualityAts_.find(mapsit->second);
      }
    }
    if (qpsit != runQualityAts_.end())
    {
      for (std::vector<QcMLFile::Attachment>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
      {
        if ((qpname == qit->name) || (qpname == qit->cvAcc))
        {
          return qit->toCSVString("\t");
          //~ return qit->toXMLString(1);
        }
      }
    }

    // if the return statement wasn't hit from runs maybe it is from sets?
    qpsit = setQualityAts_.find(filename);
    if (qpsit == setQualityAts_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = set_Name_ID_map_.find(filename);
      if (mapsit != set_Name_ID_map_.end())
      {
        qpsit = setQualityAts_.find(mapsit->second);
      }
    }
    if (qpsit != setQualityAts_.end())
    {
      for (std::vector<QcMLFile::Attachment>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
      {
        if ((qpname == qit->name) || (qpname == qit->cvAcc))
        {
          return qit->toCSVString("\t");
          //~ return qit->toXMLString(1);
        }
      }
    }

    return "";
  }

  String QcMLFile::exportQP(const String filename, const String qpname) const
  {
    std::map<String, std::vector<QcMLFile::QualityParameter> >::const_iterator qpsit = runQualityQPs_.find(filename);
    if (qpsit == runQualityQPs_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = run_Name_ID_map_.find(filename);
      if (mapsit != run_Name_ID_map_.end())
      {
        qpsit = runQualityQPs_.find(mapsit->second);
      }
    }
    if (qpsit != runQualityQPs_.end())
    {
      for (std::vector<QcMLFile::QualityParameter>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
      {
        if (qpname == qit->cvAcc)
        {
          return /* "\""+ */ qit->value /* +"\"" */;
        }
      }
    }

    // if the return statement wasn't hit from runs maybe it is from sets?
    qpsit = setQualityQPs_.find(filename);
    if (qpsit == setQualityQPs_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = set_Name_ID_map_.find(filename);
      if (mapsit != set_Name_ID_map_.end())
      {
        qpsit = setQualityQPs_.find(mapsit->second);
      }
    }
    if (qpsit != setQualityQPs_.end())
    {
      for (std::vector<QcMLFile::QualityParameter>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
      {
        if (qpname == qit->name)
        {
          return /* "\""+ */ qit->value /* +"\"" */;
        }
      }
    }

    return "N/A";
  }

  String QcMLFile::exportQPs(const String filename, const StringList qpnames) const
  {
    String ret = "";
    for (StringList::const_iterator qit = qpnames.begin(); qit != qpnames.end(); ++qit)
    {
      ret += exportQP(filename, *qit);
      ret += ",";
    }
    return ret;
  }

  String QcMLFile::map2csv(const std::map<String, std::map<String, String> >& cvs_table, const String& separator) const
  {
    String ret = "";
    std::vector<String> cols;
    if (!cvs_table.empty())
    {
      for (std::map<String, String>::const_iterator it = cvs_table.begin()->second.begin(); it != cvs_table.begin()->second.end(); ++it)
      {
        cols.push_back(it->first);
      }
      ret += "qp";
      ret += separator;
      for (std::vector<String>::const_iterator jt = cols.begin(); jt != cols.end(); ++jt)
      {
        ret += *jt;
        ret += separator;
      }
      ret += "\n";
      for (std::map<String, std::map<String, String> >::const_iterator it = cvs_table.begin(); it != cvs_table.end(); ++it)
      {
        ret += it->first;
        ret += separator;
        for (std::vector<String>::const_iterator jt = cols.begin(); jt != cols.end(); ++jt)
        {
          std::map<String, String>::const_iterator found = it->second.find(*jt);
          if (found != it->second.end())
          {
            ret += found->second;
            ret += separator;
          } //TODO else throw error
        }
        ret += "\n";
      }
    }
    return ret;
  }

  String QcMLFile::exportIDstats(const String& filename) const
  {
    std::map<String, std::vector<QualityParameter> >::const_iterator found = setQualityQPs_.find(filename);
    if (found == setQualityQPs_.end()) //try name mapping if 'filename' is no ID but name
    {
      std::map<String, String>::const_iterator mapsit = set_Name_ID_map_.find(filename);
      if (mapsit != set_Name_ID_map_.end())
      {
        found = setQualityQPs_.find(mapsit->second);
      }
    }
    if (found != setQualityQPs_.end())
    {
      std::map<String, std::map<String, String> > cvs_table;
      for (std::vector<QualityParameter>::const_iterator it = found->second.begin(); it != found->second.end(); ++it)
      {
        if (it->cvAcc == "QC:0000043" || it->cvAcc == "QC:0000044" || it->cvAcc == "QC:0000045" || it->cvAcc == "QC:0000046" || it->cvAcc == "QC:0000047")
        {
          cvs_table["id"][it->name.prefix(' ')] = it->value;
        }
        else if (it->cvAcc == "QC:0000053" || it->cvAcc == "QC:0000054" || it->cvAcc == "QC:0000055" || it->cvAcc == "QC:0000056" || it->cvAcc == "QC:0000057")
        {
          cvs_table["ms2"][it->name.prefix(' ')] = it->value;
        }
      }
      if (!cvs_table.empty())
      {
        return map2csv(cvs_table, "\t");
      }
    }

    return "";
  }

  void QcMLFile::collectSetParameter(const String setname, const String qp, std::vector<String>& ret)
  {
    for (std::set<String>::const_iterator it = setQualityQPs_members_[setname].begin(); it != setQualityQPs_members_[setname].end(); ++it)
    {
      for (std::vector<QualityParameter>::const_iterator jt = runQualityQPs_[*it].begin(); jt != runQualityQPs_[*it].end(); ++jt)
      {
        if (jt->cvAcc == qp)
        {
          ret.push_back(jt->value);
        }
      }
    }
  }

  void QcMLFile::load(const String& filename)
  {
    //Filename for error messages in XMLHandler
    file_ = filename;

    runQualityQPs_.clear(); // clear
    runQualityAts_.clear(); // clear
    setQualityQPs_.clear(); // clear
    setQualityAts_.clear(); // clear
    setQualityQPs_members_.clear(); // clear

    parse_(filename, this);
  }

  void QcMLFile::startElement(const XMLCh* const /*uri*/, const XMLCh* const /*local_name*/, const XMLCh* const qname, const xercesc::Attributes& attributes)
  {
    tag_ = sm_.convert(qname);
    String parent_tag;
    if (!open_tags_.empty())
    {
      parent_tag = open_tags_.back();
    }
    open_tags_.push_back(tag_);

    static set<String> to_ignore;
    if (to_ignore.empty())
    {
      to_ignore.insert("tableColumnTypes"); // will be handled entirely in characters.
      to_ignore.insert("tableRowValues"); // ...
      to_ignore.insert("binary"); // ...
    }

    if (to_ignore.find(tag_) != to_ignore.end())
    {
      return;
    }

    String tmp_str;
    if (tag_ == "qcML")
    {
      startProgress(0, 0, "loading qcML file");
      progress_ = 0;
      setProgress(++progress_);
    }
    else if (tag_ == "runQuality")
    {
      run_id_ = attributeAsString_(attributes, "ID"); //TODO!
      setProgress(++progress_);
      qps_.clear();
      ats_.clear();
      qp_ = QualityParameter();
      at_ = Attachment();
      name_ = "";
      //for the run name wait for the qp with the right cv, otherwise use a uid
    }
    else if (tag_ == "qualityParameter")
    {
      optionalAttributeAsString_(qp_.value, attributes, "value");
      optionalAttributeAsString_(qp_.unitAcc, attributes, "unitAccession");
      optionalAttributeAsString_(qp_.unitRef, attributes, "unitCvRef");
      optionalAttributeAsString_(qp_.flag, attributes, "flag");
      qp_.cvRef = attributeAsString_(attributes, "cvRef");
      qp_.cvAcc = attributeAsString_(attributes, "accession");
      qp_.id = attributeAsString_(attributes, "ID");
      qp_.name = attributeAsString_(attributes, "name");
      if (parent_tag == "runQuality")
      {
        if (qp_.cvAcc == "MS:1000577") //no own qc cv
        {
          name_ = qp_.value;
        }
        //TODO add cvhandling for validation etc
      }
      else //setQuality
      {
        if (qp_.cvAcc == "MS:1000577") //TODO make sure these exist in runs later!
        {
          names_.insert(qp_.value);
        }
        if (qp_.cvAcc == "QC:0000058") //id: MS:1000577 name: raw data file  - with value of the file name of the run
        {
          name_ = qp_.value;
        }
      }
    }
    else if (tag_ == "attachment")
    {
      optionalAttributeAsString_(at_.value, attributes, "value");
      optionalAttributeAsString_(at_.unitAcc, attributes, "unitAccession");
      optionalAttributeAsString_(at_.unitRef, attributes, "unitCvRef");
      at_.cvRef = attributeAsString_(attributes, "cvRef");
      at_.cvAcc = attributeAsString_(attributes, "accession");
      at_.name = attributeAsString_(attributes, "name");
      at_.id = attributeAsString_(attributes, "ID");
      at_.qualityRef = attributeAsString_(attributes, "qualityParameterRef");
    }
    else if (tag_ == "setQuality")
    {
      setProgress(++progress_);
      run_id_ = attributeAsString_(attributes, "ID"); //TODO!
      qps_.clear();
      ats_.clear();
      qp_ = QualityParameter();
      at_ = Attachment();
      name_ = "";
    }
  }

  void QcMLFile::characters(const XMLCh* const chars, const XMLSize_t /*length*/)
  {
    if (tag_ == "tableRowValues")
    {
      String s = sm_.convert(chars);
      s.trim();
      if (!s.empty()) // always two notifications for a row, only the first one contains chars - dunno why
      {
        s.split(" ", row_);
      }
    }
    else if (tag_ == "tableColumnTypes")
    {
      String s = sm_.convert(chars);
      if (!s.empty()) // always two notifications for a row, only the first one contains chars - dunno why
      {
        s.split(" ", header_);
      }
    }
    else if (tag_ == "binary")
    {
      //chars may be split to several chunks => concatenate them
      at_.binary += sm_.convert(chars);
      //~ at_.binary = "bla";
    }
  }

  void QcMLFile::endElement(const XMLCh* const /*uri*/, const XMLCh* const /*local_name*/, const XMLCh* const qname)
  {
    static set<String> to_ignore;
    if (to_ignore.empty())
    {
      //~ to_ignore.insert("binary");
    }

    tag_ = sm_.convert(qname);

    //determine parent tag
    String parent_tag;
    if (open_tags_.size() > 1)
    {
      parent_tag = *(open_tags_.end() - 2);
    }
    String parent_parent_tag;
    if (open_tags_.size() > 2)
    {
      parent_parent_tag = *(open_tags_.end() - 3);
    }

    //close current tag
    open_tags_.pop_back();

    if (to_ignore.find(tag_) != to_ignore.end())
    {
      return;
    }
    if (tag_ == "tableColumnTypes")
    {
      at_.colTypes.swap(header_);
      header_.clear();
    }
    else if (tag_ == "tableRowValues")
    {
      if (!row_.empty())
      {
        at_.tableRows.push_back(row_);
      }
      row_.clear();
    }
    else if (tag_ == "qualityParameter")
    {
      if (!(qp_.cvAcc == "MS:1000577" && parent_tag == "setQuality")) //set members get treated differently!
      {
        qps_.push_back(qp_);
        qp_ = QualityParameter();
      }
    }
    else if (tag_ == "attachment")
    {
      ats_.push_back(at_);
      at_ = Attachment();
    }
    else if (tag_ == "runQuality")
    {
      if (name_ == "")
      {
        name_ = run_id_;
        //~ name_ = String(UniqueIdGenerator::getUniqueId());
        //TODO give warning that a run should have a name cv!!!
      }
      registerRun(run_id_, name_);
      for (std::vector<QualityParameter>::const_iterator it = qps_.begin(); it != qps_.end(); ++it)
      {
        addRunQualityParameter(run_id_, *it);
      }
      for (std::vector<Attachment>::const_iterator it = ats_.begin(); it != ats_.end(); ++it)
      {
        addRunAttachment(run_id_, *it);
      }
      ats_.clear();
      qps_.clear();
    }
    else if (tag_ == "setQuality")
    {
      if (name_ == "")
      {
        name_ = run_id_;
        //~ name_ = String(UniqueIdGenerator::getUniqueId());
        //TODO give warning that a run should have a name cv!!!
      }
      registerSet(run_id_, name_, names_);
      for (std::vector<QualityParameter>::const_iterator it = qps_.begin(); it != qps_.end(); ++it)
      {
        addSetQualityParameter(run_id_, *it);
      }
      for (std::vector<Attachment>::const_iterator it = ats_.begin(); it != ats_.end(); ++it)
      {
        addSetAttachment(run_id_, *it);
      }
      ats_.clear();
      qps_.clear();
    }
  }

  void QcMLFile::store(const String& filename) const
  {
    //~ startProgress(0, 0, "storing qcML file");
    //~ progress_ = 0;
    //~ setProgress(++progress_);

    //~ file should either contain the complete stylesheet injection (including the stylesheet file preamble, the DOCTYPE definition and the stylesheet itself) or be empty
    std::string xslt = "";
    std::string xslt_ref = "";
    try
    {
      String xslt_file = File::find("XSL/QcML_report_sheet.xsl"); //TODO make this user defined pt.1
      std::ifstream in(xslt_file.c_str());
      xslt = std::string((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
      xslt = xslt.erase(0, xslt.find('\n') + 1);
      xslt_ref = "openms-qc-stylesheet"; //TODO make this user defined pt.2
    }
    catch (Exception::FileNotFound &)
    {
      warning(STORE, String("No qcml stylesheet found, result will not be viewable in a browser!"));
    }


    //open stream
    ofstream os(filename.c_str());
    if (!os)
    {
      throw Exception::UnableToCreateFile(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, filename);
    }

    os.precision(writtenDigits<double>(0.0));

    //~ setProgress(++progress_);
    //header & xslt

    os << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n";
    if (!xslt_ref.empty())
    {
        os << "<?xml-stylesheet type=\"text/xml\" href=\"#" << xslt_ref << "\"?>\n";
        os << "<!DOCTYPE catelog [\n"
           << "  <!ATTLIST xsl:stylesheet\n"
           << "  id  ID  #REQUIRED>\n"
           << "  ]>\n";
    }
    os << "<qcML xmlns=\"https://github.com/qcML/qcml\" >\n"; //TODO creation date into schema!!

    //content runs
    std::set<String> keys;
    for (std::map<String, std::vector<QualityParameter> >::const_iterator it = runQualityQPs_.begin(); it != runQualityQPs_.end(); ++it)
    {
      keys.insert(it->first);
    }
    for (std::map<String, std::vector<Attachment> >::const_iterator it = runQualityAts_.begin(); it != runQualityAts_.end(); ++it)
    {
      keys.insert(it->first);
    }

    if (!keys.empty())
    {
      for (std::set<String>::const_iterator it = keys.begin(); it != keys.end(); ++it)
      {
        os << "\t<runQuality ID=\"" << String(*it) << "\">\n";
        std::map<String, std::vector<QualityParameter> >::const_iterator qpsit = runQualityQPs_.find(*it);
        if (qpsit != runQualityQPs_.end())
        {
          for (std::vector<QcMLFile::QualityParameter>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
          {
            os << qit->toXMLString(4);
          }
        }
        std::map<String, std::vector<Attachment> >::const_iterator attit = runQualityAts_.find(*it);
        if (attit != runQualityAts_.end())
        {
          for (std::vector<QcMLFile::Attachment>::const_iterator ait = attit->second.begin(); ait != attit->second.end(); ++ait)
          {
            os << ait->toXMLString(4); //TODO check integrity of reference to qp!
          }
        }
        os << "\t</runQuality>\n";
      }
    }

    //content sets
    keys.clear();
    for (std::map<String, std::vector<QualityParameter> >::const_iterator it = setQualityQPs_.begin(); it != setQualityQPs_.end(); ++it)
    {
      keys.insert(it->first);
    }
    for (std::map<String, std::vector<Attachment> >::const_iterator it = setQualityAts_.begin(); it != setQualityAts_.end(); ++it)
    {
      keys.insert(it->first);
    }

    if (!keys.empty())
    {
      for (std::set<String>::const_iterator it = keys.begin(); it != keys.end(); ++it)
      {
        os << "\t<setQuality ID=\"" << String(*it) << "\">\n";
        //~ TODO warn if key has no entries in members_

        //document set members
        std::map<String, std::set<String> >::const_iterator jt = setQualityQPs_members_.find(*it);

        if (jt != setQualityQPs_members_.end())
        {
          for (std::set<String>::const_iterator kt = jt->second.begin(); kt != jt->second.end(); ++kt)
          {
            std::map<String, std::vector<QualityParameter> >::const_iterator rq = runQualityQPs_.find(*kt);
            if (rq != runQualityQPs_.end())
            {
                QcMLFile::QualityParameter qp;
                qp.id = *kt; ///< Identifier
                qp.name = "set name"; ///< Name
                qp.cvRef = "QC"; ///< cv reference
                qp.cvAcc = "QC:0000005";
                for (std::vector<QualityParameter>::const_iterator qit = rq->second.begin(); qit != rq->second.end(); ++qit)
                {
                  ///<qualityParameter name="mzML file" ID="OTT0650-S44-A-Leber_1_run_name" cvRef="MS" accession="MS:1000577" value="OTT0650-S44-A-Leber_1"/>
                  if (qit->cvAcc == "MS:1000577")
                    qp.value = qit->value;
                }
                os << qp.toXMLString(4);
            }
            else
            {
              //TODO warn - no mzML file registered for this runQC
            }
          }
        }

        std::map<String, std::vector<QualityParameter> >::const_iterator qpsit = setQualityQPs_.find(*it);
        if (qpsit != setQualityQPs_.end())
        {
          for (std::vector<QcMLFile::QualityParameter>::const_iterator qit = qpsit->second.begin(); qit != qpsit->second.end(); ++qit)
          {
            os << qit->toXMLString(4);
          }
        }

        std::map<String, std::vector<Attachment> >::const_iterator attit = setQualityAts_.find(*it);
        if (attit != setQualityAts_.end())
        {
          for (std::vector<QcMLFile::Attachment>::const_iterator ait = attit->second.begin(); ait != attit->second.end(); ++ait)
          {
            os << ait->toXMLString(4);
          }
        }
        os << "\t</setQuality>\n";
      }
    }
    os <<  "\t<cvList>\n";
    os <<  "\t<cv uri=\"http://psidev.cvs.sourceforge.net/viewvc/psidev/psi/psi-ms/mzML/controlledVocabulary/psi-ms.obo\" ID=\"psi_cv_ref\" fullName=\"PSI-MS\" version=\"3.41.0\"/>\n";
    os <<  "\t<cv uri=\"https://github.com/qcML/qcML-development/blob/master/cv/qc-cv.obo\" ID=\"qc_cv_ref\" fullName=\"QC-CV\" version=\"0.1.1\"/>\n";
    os <<  "\t<cv uri=\"http://obo.cvs.sourceforge.net/viewvc/obo/obo/ontology/phenotype/unit.obo\" ID=\"uo_cv_ref\" fullName=\"unit\" version=\"1.0.0\"/>\n";
    os <<  "\t</cvList>\n";

    if (!xslt_ref.empty())
    {
      os << xslt << "\n";
    }

    os << "</qcML>\n";
  }

}

#pragma clang diagnostic pop

