/*
 * Copyright (C) 2005-2020 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <iomanip>

#include "otbVectorDataKeywordlist.h"

#include "otbConfigure.h"
#include "itkNumericTraits.h"

namespace otb
{

VectorDataKeywordlist::VectorDataKeywordlist()
{
  // Nothing to do here
}

VectorDataKeywordlist::VectorDataKeywordlist(const Self& other)
{
  *this = other;
}

VectorDataKeywordlist::~VectorDataKeywordlist()
{
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    if (m_FieldList[i].first->GetType() == OFTString)
    {
      VSIFree(m_FieldList[i].second.String);
    }
    delete (m_FieldList[i].first);
  }
}

void VectorDataKeywordlist::AddField(OGRFieldDefn* fieldDefn, OGRField* field)
{
  FieldType newField;
  newField.first  = fieldDefn;
  newField.second = *field;
  // TODO: evaluate performance impact of fieldDefn copy
  // the object itself could be handled at the VectorData level
  // keeping only pointer here. (but it does not seem
  // necessary so far...)
  m_FieldList.push_back(CopyOgrField(newField));
}

void VectorDataKeywordlist::AddField(const std::string& key, const std::string& value)
{
  FieldType newField;

  OGRFieldDefn* fieldDefn = new OGRFieldDefn(key.c_str(), OFTString);

  OGRField field;
  char*    cstr = (char*)VSIMalloc((value.length() + 1) * sizeof(char));
  strcpy(cstr, value.c_str());
  field.String = cstr;

  newField.first  = fieldDefn;
  newField.second = field;
  m_FieldList.push_back(newField);
}

std::string VectorDataKeywordlist::GetFieldAsString(const std::string& key) const
{
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
    {
      switch (m_FieldList[i].first->GetType())
      {
      case OFTString:
      {
        return m_FieldList[i].second.String;
      }
      case OFTInteger:
      {
        std::ostringstream ss;
        ss << std::setprecision(15) << m_FieldList[i].second.Integer;
        return ss.str();
      }
      case OFTInteger64:
      {
        std::ostringstream ss;
        ss << std::setprecision(15) << m_FieldList[i].second.Integer64;
        return ss.str();
      }
      case OFTReal:
      {
        std::ostringstream ss;
        ss << std::setprecision(15) << m_FieldList[i].second.Real;
        return ss.str();
      }
      default:
      {
        itkExceptionMacro(<< "Type of field " << m_FieldList[i].first->GetNameRef() << " (" << m_FieldList[i].first->GetType()
                          << ") is not handled (yet) by GetFieldAsString(), please request for it");
      }
      }
    }
  }
  return "";
}

double VectorDataKeywordlist::GetFieldAsDouble(const std::string& key) const
{
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
    {
      switch (m_FieldList[i].first->GetType())
      {
      case OFTInteger:
      {
        return (double)(m_FieldList[i].second.Integer);
      }
      case OFTReal:
      {
        return (double)(m_FieldList[i].second.Real);
      }
      case OFTString:
      {
        std::istringstream is(m_FieldList[i].second.String);
        double             value;
        is >> value;
        return value;
      }
      default:
      {
        itkExceptionMacro(<< "Type of field " << m_FieldList[i].first->GetNameRef() << " (" << m_FieldList[i].first->GetType()
                          << ") is not handled (yet) by GetFieldAsDouble(), please request for it");
      }
      }
    }
  }
  return 0.;
}

int VectorDataKeywordlist::GetFieldAsInt(const std::string& key) const
{
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
    {
      switch (m_FieldList[i].first->GetType())
      {
      case OFTInteger:
      {
        return (int)(m_FieldList[i].second.Integer);
      }
      // Some fields that were OFTInteger with gdal 1.x are now
      // exposed as OFTInteger64. So as to make the old code still
      // work with the same data, here we downcast to Integer (if
      // and only if no overflow occur).
      case OFTInteger64:
      {
        if (m_FieldList[i].second.Integer64 > itk::NumericTraits<int>::max())
        {
          itkExceptionMacro(<< "value " << m_FieldList[i].second.Integer64 << " of field " << m_FieldList[i].first->GetNameRef()
                            << " can not be safely casted to 32 bits integer");
        }

        return static_cast<int>(m_FieldList[i].second.Integer64);
      }
      case OFTReal:
      {
        return (int)(m_FieldList[i].second.Real);
      }
      case OFTString:
      {
        std::istringstream is(m_FieldList[i].second.String);
        int                value;
        is >> value;
        return value;
      }
      default:
      {
        itkExceptionMacro(<< "Type of field " << m_FieldList[i].first->GetNameRef() << " (" << m_FieldList[i].first->GetType()
                          << ") is not handled (yet) by GetFieldAsInt(), please request for it");
      }
      }
    }
  }
  return 0.;
}

void VectorDataKeywordlist::SetFieldAsDouble(const std::string& key, double value)
{
  if (HasField(key))
  {
    for (unsigned int i = 0; i < m_FieldList.size(); ++i)
    {
      if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
      {
        if (m_FieldList[i].first->GetType() == OFTReal)
        {
          OGRField field;
          field.Real            = value;
          m_FieldList[i].second = field;
        }
        else
        {
          itkExceptionMacro(<< "This type is not of double type, can't add the element in it");
        }
      }
    }
  }
  else
  {
    FieldType newField;

    OGRFieldDefn* fieldDefn = new OGRFieldDefn(key.c_str(), OFTReal);

    OGRField field;
    field.Real      = value;
    newField.first  = fieldDefn;
    newField.second = field;
    m_FieldList.push_back(newField);
  }
}

void VectorDataKeywordlist::SetFieldAsInt(const std::string& key, int value)
{
  if (HasField(key))
  {
    for (unsigned int i = 0; i < m_FieldList.size(); ++i)
    {
      if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
      {
        if (m_FieldList[i].first->GetType() == OFTInteger)
        {
          OGRField field;
          field.Integer         = value;
          m_FieldList[i].second = field;
        }
        else if (m_FieldList[i].first->GetType() == OFTReal)
        {
          OGRField field;
          field.Real            = static_cast<double>(value);
          m_FieldList[i].second = field;
        }
        else
        {
          itkExceptionMacro(<< "This type is not of integer type, can't add the element in it");
        }
      }
    }
  }
  else
  {
    FieldType newField;

    OGRFieldDefn* fieldDefn = new OGRFieldDefn(key.c_str(), OFTInteger);
    OGRField      field;
    field.Integer   = value;
    newField.first  = fieldDefn;
    newField.second = field;
    m_FieldList.push_back(newField);
  }
}

bool VectorDataKeywordlist::HasField(const std::string& key) const
{
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
    {
      return true;
    }
  }
  return false;
}

void VectorDataKeywordlist::SetFieldAsString(const std::string& key, const std::string& value)
{
  if (HasField(key))
  {
    for (unsigned int i = 0; i < m_FieldList.size(); ++i)
    {
      if (key.compare(m_FieldList[i].first->GetNameRef()) == 0)
      {
        if (m_FieldList[i].first->GetType() == OFTString)
        {
          OGRField field;
          char*    cstr = (char*)VSIMalloc((value.length() + 1) * sizeof(char));
          strcpy(cstr, value.c_str());
          field.String          = cstr;
          m_FieldList[i].second = field;
        }
        else
        {
          itkExceptionMacro(<< "This type is not of string type, can't add the element in it");
        }
      }
    }
  }
  else
  {
    AddField(key, value);
  }
}

VectorDataKeywordlist::FieldType VectorDataKeywordlist::GetNthField(unsigned int index) const
{
  if (index > m_FieldList.size())
  {
    itkExceptionMacro(<< " Accessing out-of-range metadata ");
  }
  return m_FieldList[index];
}

unsigned int VectorDataKeywordlist::GetNumberOfFields() const
{
  return m_FieldList.size();
}

std::vector<std::string> VectorDataKeywordlist::GetFieldList() const
{
  std::vector<std::string> res;
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    res.push_back((m_FieldList[i].first)->GetNameRef());
  }
  return res;
}

void VectorDataKeywordlist::operator=(const Self& p)
{
  for (unsigned int i = 0; i < p.m_FieldList.size(); ++i)
  {
    m_FieldList.push_back(CopyOgrField(p.m_FieldList[i]));
  }
}

void VectorDataKeywordlist::Print(std::ostream& os, itk::Indent indent) const
{
  this->PrintSelf(os, indent.GetNextIndent());
}

void VectorDataKeywordlist::PrintSelf(std::ostream& os, itk::Indent indent) const
{
  os << indent << " VectorData Keyword list: ";
  os << indent << " - Size: " << m_FieldList.size() << std::endl;
  for (unsigned int i = 0; i < m_FieldList.size(); ++i)
  {
    os << indent << "    " << PrintField(m_FieldList[i]);
  }
}

void VectorDataKeywordlist::CopyFieldList(const Self& kwl)
{
  for (unsigned int idx = 0; idx < kwl.GetNumberOfFields(); ++idx)
  {
    switch (kwl.GetNthField(idx).first->GetType())
    {
    case OFTString:
    {
      this->SetFieldAsString(kwl.GetNthField(idx).first->GetNameRef(), kwl.GetNthField(idx).second.String);
      break;
    }
    case OFTInteger:
    {
      this->SetFieldAsInt(kwl.GetNthField(idx).first->GetNameRef(), kwl.GetNthField(idx).second.Integer);
      break;
    }
    case OFTReal:
    {
      this->SetFieldAsDouble(kwl.GetNthField(idx).first->GetNameRef(), kwl.GetNthField(idx).second.Real);
      break;
    }
    default:
    {
      std::cerr << "Type not handled for Integer conversion" << std::endl;
      break;
    }
    }
  }
}

std::string VectorDataKeywordlist::PrintField(FieldType field) const
{
  std::stringstream output;
  output << std::setprecision(15);
  output << field.first->GetNameRef() << " (";
  output << field.first->GetFieldTypeName(field.first->GetType()) << "): ";
  switch (field.first->GetType())
  {
  case OFTInteger:
  {
    output << field.second.Integer;
    break;
  }
  case OFTReal:
  {
    output << field.second.Real;
    break;
  }
  case OFTString:
  {
    if (field.second.String != nullptr)
    {
      output << field.second.String;
    }
    break;
  }
  case OFTDate:
  {
    output << field.second.Date.Year << field.second.Date.Month << field.second.Date.Day;
    break;
  }
  case OFTTime:
  {
    output << field.second.Date.Hour << field.second.Date.Minute << field.second.Date.Second;
    break;
  }
  case OFTDateTime:
  {
    output << field.second.Date.Year << field.second.Date.Month << field.second.Date.Day << "-" << field.second.Date.Hour << field.second.Date.Minute
           << field.second.Date.Second;
    break;
  }
  case OFTInteger64:
  {
    output << std::setprecision(15) << field.second.Integer64;
    break;
  }
  default:
    output << "Type not handled for printing";
    break;
  }
  output << std::endl;
  return output.str();
}

VectorDataKeywordlist::FieldType VectorDataKeywordlist::CopyOgrField(FieldType field)
{
  FieldType outField;
  outField.first = new OGRFieldDefn(field.first);
  switch (field.first->GetType())
  {
  case OFTInteger:
  {
    outField.second.Integer = field.second.Integer;
    break;
  }
  case OFTInteger64:
  {
    outField.second.Integer64 = field.second.Integer64;
    break;
  }
  case OFTReal:
  {
    outField.second.Real = field.second.Real;
    break;
  }
  case OFTString:
  {
    if (field.second.String != nullptr)
    {
      CPLFree(outField.second.String);
      outField.second.String = CPLStrdup(field.second.String);
    }
    break;
  }
  case OFTDate:
  {
    outField.second.Date.Year  = field.second.Date.Year;
    outField.second.Date.Month = field.second.Date.Month;
    outField.second.Date.Day   = field.second.Date.Day;
    break;
  }
  case OFTTime:
  {
    outField.second.Date.Hour   = field.second.Date.Hour;
    outField.second.Date.Minute = field.second.Date.Minute;
    outField.second.Date.Second = field.second.Date.Second;
    break;
  }
  case OFTDateTime:
  {
    outField.second.Date.Year   = field.second.Date.Year;
    outField.second.Date.Month  = field.second.Date.Month;
    outField.second.Date.Day    = field.second.Date.Day;
    outField.second.Date.Hour   = field.second.Date.Hour;
    outField.second.Date.Minute = field.second.Date.Minute;
    outField.second.Date.Second = field.second.Date.Second;
    break;
  }
  default:
  {
    std::cerr << "OGR type not handled" << std::endl;
    break;
  }
  }
  return outField;
}

std::ostream& operator<<(std::ostream& os, const VectorDataKeywordlist& kwl)
{
  kwl.Print(os);
  return os;
}
}
