text: /*************************************************************************
   * odil - Copyright (C) Universite de Strasbourg
   * Distributed under the terms of the CeCILL-B license, as published by
   * the CEA-CNRS-INRIA. Refer to the LICENSE file or to
   * http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
   * for details.
   ************************************************************************/
  
  #include <iterator>
  #include <map>
  #include <vector>
  
  #include <boost/property_tree/ptree.hpp>
  
  #include "odil/base64.h"
  #include "odil/DataSet.h"
  #include "odil/registry.h"
  #include "odil/Value.h"
  #include "odil/xml_converter.h"
  
  namespace
  {
  
  template<typename TIterator>
  std::string join(TIterator first, TIterator last, std::string const & separator)
  {
      std::string joined;
      bool is_first=true;
      while(first != last)
      {
          if(!is_first)
          {
              joined += separator;
          }
          joined += *first;
          is_first = false;
          ++first;
      }
      return joined;
  }
  
  template<typename TIterator>
  void split(
      std::string const & string, std::string const & separator,
      TIterator destination)
  {
      std::string::size_type begin=0;
      while(begin < string.size())
      {
          auto end = std::min(string.find(separator, begin), string.size());
          *destination = string.substr(begin, end-begin);
          ++destination;
          begin = end+1;
      }
  }
  
  }
  
  namespace odil
  {
  
  /// @brief Element visitor converting to XML.
  struct ToXMLVisitor
  {
      BulkDataCreator bulk_data_creator;
  
      typedef boost::property_tree::ptree result_type;
  
      result_type operator()(VR const vr) const
      {
          result_type result;
  
          result.put("<xmlattr>.vr", as_string(vr)); // Mandatory
  
          return result;
      }
  
      template<typename T>
      result_type operator()(VR const vr, T const & value) const
      {
          result_type result;
  
          result.put("<xmlattr>.vr", as_string(vr)); // Mandatory
  
          unsigned int count = 0;
          for(auto const & item: value)
          {
              ++count;
              boost::property_tree::ptree tag_value;
              tag_value.put("<xmlattr>.number", count); // Mandatory
              tag_value.put_value(item);
  
              result.add_child("Value", tag_value);
          }
  
          return result;
      }
  
      result_type operator()(VR const vr, Value::Strings const & value) const
      {
          result_type result;
  
          result.put("<xmlattr>.vr", as_string(vr)); // Mandatory
  
          if(vr == VR::PN)
          {
              std::vector<std::string> const representation_names{
                  "Alphabetic", "Ideographic", "Phonetic" };
              std::vector<std::string> const field_names{
                  "FamilyName", "GivenName", "MiddleName", "NamePrefix",
                  "NameSuffix" };
  
              unsigned int count = 0;
              for(auto const & item: value)
              {
                  ++count;
                  boost::property_tree::ptree tag_value;
                  tag_value.put("<xmlattr>.number", count); // Mandatory
  
                  std::vector<std::string> representations;
                  split(item, "=", std::back_inserter(representations));
                  for(unsigned int r=0;
                      r<std::min(representation_names.size(), representations.size());
                      ++r)
                  {
                      if(representations[r].empty())
                      {
                          continue;
                      }
  
                      boost::property_tree::ptree representation_node;
  
                      std::vector<std::string> fields;
                      split(representations[r], "^", std::back_inserter(fields));
                      for(unsigned int f=0;
                          f<std::min(field_names.size(), fields.size()); ++f)
                      {
                          if(fields[f].empty())
                          {
                              continue;
                          }
  
                          representation_node.put(field_names[f], fields[f]);
                      }
  
                      tag_value.add_child(
                          representation_names[r], representation_node);
                  }
  
                  result.add_child("PersonName", tag_value);
              }
          }
          else
          {
              unsigned int count = 0;
              for(auto const & item: value)
              {
                  ++count;
                  boost::property_tree::ptree tag_value;
                  tag_value.put("<xmlattr>.number", count); // Mandatory
                  tag_value.put_value(item);
  
                  result.add_child("Value", tag_value);
              }
          }
  
          return result;
      }
  
      result_type operator()(VR const vr, Value::DataSets const & value) const
      {
          result_type result;
  
          result.put("<xmlattr>.vr", as_string(vr)); // Mandatory
  
          unsigned int count = 0;
          for(auto const & item: value)
          {
              ++count;
              boost::property_tree::ptree tag_value;
              tag_value.put("<xmlattr>.number", count); // Mandatory
              boost::property_tree::ptree tag_result = as_xml(
                  item, this->bulk_data_creator);
              tag_value.insert(tag_value.end(), tag_result.front().second.begin(),
                                                tag_result.front().second.end());
              result.add_child("Item", tag_value);
          }
  
          return result;
      }
  
      result_type operator()(VR const vr, Value::Binary const & value) const
      {
          if(value.size() > 1)
          {
              // PS3.18 2016b, F.2.7: There is a single InlineBinary value
              // representing the entire Value Field.
              // PS3.18 2016b, Figure 6.5-1: Pixel data is not encoded in
              // JSON/XML, but transfered using a different content type
              throw Exception("Binary element is multiple-valued");
          }
  
          result_type result;
  
          result.put("<xmlattr>.vr", as_string(vr)); // Mandatory
  
          boost::property_tree::ptree tag_value;
  
          std::string encoded;
          encoded.reserve(value[0].size()*4/3);
          base64::encode(value[0].begin(), value[0].end(), std::back_inserter(encoded));
          tag_value.put_value(encoded);
  
          result.add_child("InlineBinary", tag_value);
  
          return result;
      }
  };
  
  boost::property_tree::ptree as_xml(
      std::shared_ptr<DataSet const> data_set,
      BulkDataCreator const & bulk_data_creator)
  {
      ToXMLVisitor visitor;
      visitor.bulk_data_creator = bulk_data_creator;
  
      // XML dataset element
      boost::property_tree::ptree nativedicommodel;
      if(data_set)
      {
          for(auto const & it: *data_set)
          {
              auto const & tag = it.first;
              auto const & element = it.second;
  
              boost::property_tree::ptree dicomattribute;
  
              auto const bulk_data_info =
                  bulk_data_creator?bulk_data_creator(data_set, tag)
                  :std::make_pair(std::string(), std::string());
              if(!bulk_data_info.first.empty())
              {
                  boost::property_tree::ptree bulk_data_element;
                  bulk_data_element.put(
                      "<xmlattr>."+bulk_data_info.first, bulk_data_info.second);
                  dicomattribute.add_child("BulkData", bulk_data_element);
                  dicomattribute.put("<xmlattr>.vr", as_string(element.vr));
              }
              else
              {
                  dicomattribute = apply_visitor(visitor, element);
              }
              // Add Mandatory attribute Tag
              dicomattribute.put("<xmlattr>.tag",  std::string(tag));
              // Add Optional attribute Keyword
              auto const dictionary_it = registry::public_dictionary.find(tag);
              if(dictionary_it != registry::public_dictionary.end())
              {
                  dicomattribute.put(
                      "<xmlattr>.keyword", dictionary_it->second.keyword);
              }
              // Add Optional attribute PrivateCreator
              //dicomattribute.put("<xmlattr>.privateCreator", todo);
              nativedicommodel.add_child("DicomAttribute", dicomattribute);
          }
      }
  
      // root element
      boost::property_tree::ptree dataset_xml;
  
      // Add XML dataset into root element
      dataset_xml.add_child("NativeDicomModel", nativedicommodel);
  
      return dataset_xml;
  }
  
  void parse_value(
      boost::property_tree::ptree const & xml, Element & element)
  {
      auto const number = xml.get<unsigned int>("<xmlattr>.number");
      if(element.is_int())
      {
          element.as_int()[number-1] = xml.get_value<Value::Integer>();
      }
      else if(element.is_real())
      {
          element.as_real()[number-1] = xml.get_value<Value::Real>();
      }
      else if(element.is_string())
      {
          // FIXME: Orthanc does not split strings using backslash
          element.as_string()[number-1] = xml.get_value<Value::String>();
      }
      // SQ is handled by parse_item
      // Binary is handled by parse_inline_binary
      else
      {
          throw Exception("Cannot parse "+as_string(element.vr)+" as Value");
      }
  }
  
  void parse_bulk_data(
      boost::property_tree::ptree const & xml, Element & element)
  {
      try
      {
          element.as_string() = {xml.get<std::string>("<xmlattr>.uri")};
      }
      catch(boost::property_tree::ptree_bad_path const &)
      {
          element.as_string() = {xml.get<std::string>("<xmlattr>.uuid")};
      }
  }
  
  void parse_person_name(
      boost::property_tree::ptree const & xml, Element & element)
  {
      auto const number = xml.get<unsigned int>("<xmlattr>.number");
  
      std::vector<std::string> const representations = {
          "Alphabetic", "Ideographic", "Phonetic"
      };
      std::vector<std::string> names;
      for(auto const & representation: representations)
      {
          auto const representation_it = xml.find(representation);
          if(representation_it == xml.not_found())
          {
              names.push_back("");
          }
          else
          {
              std::vector<std::string> name;
  
              static auto const components = {
                  "FamilyName", "GivenName", "MiddleName", "NamePrefix",
                  "NameSuffix"};
              for(auto const & component: components)
              {
                  auto const & component_it = representation_it->second.find(component);
                  if(component_it == representation_it->second.not_found())
                  {
                      name.push_back("");
                  }
                  else
                  {
                      name.push_back(component_it->second.get_value<std::string>());
                  }
              }
  
              while(!name.empty() && name.rbegin()->empty())
              {
                  name.pop_back();
              }
  
              names.push_back(join(name.begin(), name.end(), "^"));
          }
      }
  
      while(!names.empty() && names.rbegin()->empty())
      {
          names.pop_back();
      }
  
      element.as_string()[number-1] = join(names.begin(), names.end(), "=");
  }
  
  void parse_attributes(
      boost::property_tree::ptree const & xml, std::shared_ptr<DataSet> data_set);
  
  void parse_item(boost::property_tree::ptree const & xml, Element & element)
  {
      auto data_set = std::make_shared<DataSet>();
      parse_attributes(xml, data_set);
      auto const number = xml.get<unsigned int>("<xmlattr>.number");
      element.as_data_set()[number-1] = data_set;
  }
  
  void parse_inline_binary(
      boost::property_tree::ptree const & xml, Element & element)
  {
      auto const & encoded = xml.get_value<std::string>();
      Value::Binary::value_type decoded;
      decoded.reserve(encoded.size()*3/4);
      base64::decode(encoded.begin(), encoded.end(), std::back_inserter(decoded));
      element.as_binary() = { decoded };
  }
  
  void parse_attributes(
      boost::property_tree::ptree const & xml, std::shared_ptr<DataSet> data_set)
  {
      auto const attributes = xml.equal_range("DicomAttribute");
      for(auto attribute = attributes.first; attribute != attributes.second; ++attribute)
      {
          auto const & children = attribute->second;
  
          Tag const tag(children.get<std::string>("<xmlattr>.tag"));
          auto const vr = as_vr(children.get<std::string>("<xmlattr>.vr"));
  
          Element element(VR::UN);
  
          std::pair<
              boost::property_tree::ptree::const_assoc_iterator,
              boost::property_tree::ptree::const_assoc_iterator> range(
                  children.not_found(), children.not_found());
          std::function<void(boost::property_tree::ptree const &, Element &)> parser;
          boost::property_tree::ptree normalized;
          if(children.find("Value") != children.not_found())
          {
              range = children.equal_range("Value");
              if(vr != VR::LT && vr != VR::ST && vr != VR::UT)
              {
                  // Some providers use a wrong representation where multi-valued
                  // elements are joined using \\ instead of using multiple
                  // <Value> elements. Be nice to them.
                  bool must_normalize = false;
                  for(auto it=range.first; it != range.second; ++it)
                  {
                      auto const value = it->second.get_value<std::string>();
                      if(value.find("\\") != std::string::npos)
                      {
                          must_normalize = true;
                          break;
                      }
                  }
                  if(must_normalize)
                  {
                      int count=0;
                      for(auto it=range.first; it != range.second; ++it)
                      {
                          auto const original_value =
                              it->second.get_value<std::string>();
                          std::vector<std::string> values;
                          split(original_value, "\\", std::back_inserter(values));
                          for(auto const & value: values)
                          {
                              boost::property_tree::ptree value_node;
                              value_node.put("<xmlattr>.number", 1+count);
                              value_node.put_value(value);
                              normalized.add_child("Value", value_node);
                              ++count;
                          }
                      }
                      range = normalized.equal_range("Value");
                  }
                  // Otherwise, nothing to do: range is OK
              }
              // Otherwise, nothing to do: range is OK
              element = Element(vr);
              parser = parse_value;
          }
          else if(children.find("BulkData") != children.not_found())
          {
              range = children.equal_range("BulkData");
              element = Element(VR::UR);
              parser = parse_bulk_data;
          }
          else if(children.find("PersonName") != children.not_found())
          {
              range = children.equal_range("PersonName");
              element = Element(VR::PN);
              parser = parse_person_name;
          }
          else if(children.find("Item") != children.not_found())
          {
              range = children.equal_range("Item");
              element = Element(VR::SQ);
              parser = parse_item;
          }
          else if(children.find("InlineBinary") != children.not_found())
          {
              range = children.equal_range("InlineBinary");
              element = Element(vr);
              parser = parse_inline_binary;
          }
          else
          {
              element = Element(vr);
              parser = [](boost::property_tree::ptree const &, Element &) {};
          }
  
          auto const size =
              range.first==children.not_found()
              ?0
              :std::distance(range.first, range.second);
          if(element.is_int())
          {
              element.as_int().resize(size);
          }
          else if(element.is_real())
          {
              element.as_real().resize(size);
          }
          else if(element.is_string())
          {
              element.as_string().resize(size);
          }
          else if(element.is_data_set())
          {
              element.as_data_set().resize(size);
          }
          else if(element.is_binary())
          {
              element.as_binary().resize(size);
          }
          else
          {
              throw Exception("Cannot resize "+as_string(vr));
          }
  
          for(auto it = range.first; it != range.second; ++it)
          {
              parser(it->second, element);
          }
  
          data_set->add(tag, element);
      }
  }
  
  std::shared_ptr<DataSet> as_dataset(boost::property_tree::ptree const & xml)
  {
      if(xml.size() < 1 || xml.front().first != "NativeDicomModel")
      {
          throw Exception("Missing root node NativeDicomModel");
      }
  
      auto data_set = std::make_shared<DataSet>();
  
      auto const & root = xml.get_child("NativeDicomModel");
      parse_attributes(root, data_set);
      return data_set;
  }
  
  } // namespace odil
  