/***************************************************************************
    copyright            : (C) 2001-2006 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

#include "field.h"
#include "tellico_utils.h"
#include "latin1literal.h"
#include "tellico_debug.h"
#include "core/tellico_config.h"
#include "collection.h"

#include <tdelocale.h>
#include <tdeglobal.h>

#include <tqstringlist.h>
#include <tqregexp.h>
#include <tqdatetime.h>

namespace {
  static const TQRegExp comma_split = TQRegExp(TQString::fromLatin1("\\s*,\\s*"));
}

using Tellico::Data::Field;

//these get overwritten, but are here since they're static
TQStringList Field::s_articles;
TQStringList Field::s_articlesApos;
TQRegExp Field::s_delimiter = TQRegExp(TQString::fromLatin1("\\s*;\\s*"));

// this constructor is for anything but Choice type
Field::Field(const TQString& name_, const TQString& title_, Type type_/*=Line*/)
    : TDEShared(), m_name(name_), m_title(title_),  m_category(i18n("General")), m_desc(title_),
      m_type(type_), m_flags(0), m_formatFlag(FormatNone) {

#ifndef NDEBUG
  if(m_type == Choice) {
    kdWarning() << "Field() - A different constructor should be called for multiple choice attributes." << endl;
    kdWarning() << "Constructing a Field with name = " << name_ << endl;
  }
#endif
  // a paragraph's category is always its title, along with tables
  if(isSingleCategory()) {
    m_category = m_title;
  }
  if(m_type == Table || m_type == Table2) {
    m_flags = AllowMultiple;
    if(m_type == Table2) {
      m_type = Table;
      setProperty(TQString::fromLatin1("columns"), TQChar('2'));
    } else {
      setProperty(TQString::fromLatin1("columns"), TQChar('1'));
    }
  } else if(m_type == Date) {  // hidden from user
    m_formatFlag = FormatDate;
  } else if(m_type == Rating) {
    setProperty(TQString::fromLatin1("minimum"), TQChar('1'));
    setProperty(TQString::fromLatin1("maximum"), TQChar('5'));
  }
  m_id = getID();
}

// if this constructor is called, the type is necessarily Choice
Field::Field(const TQString& name_, const TQString& title_, const TQStringList& allowed_)
    : TDEShared(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_),
      m_type(Field::Choice), m_allowed(allowed_), m_flags(0), m_formatFlag(FormatNone) {
  m_id = getID();
}

Field::Field(const Field& field_)
    : TDEShared(field_), m_name(field_.name()), m_title(field_.title()), m_category(field_.category()),
      m_desc(field_.description()), m_type(field_.type()),
      m_flags(field_.flags()), m_formatFlag(field_.formatFlag()),
      m_properties(field_.propertyList()) {
  if(m_type == Choice) {
    m_allowed = field_.allowed();
  } else if(m_type == Table2) {
    m_type = Table;
    setProperty(TQString::fromLatin1("columns"), TQChar('2'));
  }
  m_id = getID();
}

Field& Field::operator=(const Field& field_) {
  if(this == &field_) return *this;

  static_cast<TDEShared&>(*this) = static_cast<const TDEShared&>(field_);
  m_name = field_.name();
  m_title = field_.title();
  m_category = field_.category();
  m_desc = field_.description();
  m_type = field_.type();
  if(m_type == Choice) {
    m_allowed = field_.allowed();
  } else if(m_type == Table2) {
    m_type = Table;
    setProperty(TQString::fromLatin1("columns"), TQChar('2'));
  }
  m_flags = field_.flags();
  m_formatFlag = field_.formatFlag();
  m_properties = field_.propertyList();
  m_id = getID();
  return *this;
}

Field::~Field() {
}

void Field::setTitle(const TQString& title_) {
  m_title = title_;
  if(isSingleCategory()) {
    m_category = title_;
  }
}

void Field::setType(Field::Type type_) {
  m_type = type_;
  if(m_type != Field::Choice) {
    m_allowed = TQStringList();
  }
  if(m_type == Table || m_type == Table2) {
    m_flags |= AllowMultiple;
    if(m_type == Table2) {
      m_type = Table;
      setProperty(TQString::fromLatin1("columns"), TQChar('2'));
    }
    if(property(TQString::fromLatin1("columns")).isEmpty()) {
      setProperty(TQString::fromLatin1("columns"), TQChar('1'));
    }
  }
  if(isSingleCategory()) {
    m_category = m_title;
  }
  // hidden from user
  if(type_ == Date) {
    m_formatFlag = FormatDate;
  }
}

void Field::setCategory(const TQString& category_) {
  if(!isSingleCategory()) {
    m_category = category_;
  }
}

void Field::setFlags(int flags_) {
  // tables always have multiple allowed
  if(m_type == Table || m_type == Table2) {
    m_flags = AllowMultiple | flags_;
  } else {
    m_flags = flags_;
  }
}

void Field::setFormatFlag(FormatFlag flag_) {
  // Choice and Data fields are not allowed a format
  if(m_type != Choice && m_type != Date) {
    m_formatFlag = flag_;
  }
}

const TQString& Field::defaultValue() const {
  return property(TQString::fromLatin1("default"));
}

void Field::setDefaultValue(const TQString& value_) {
  if(value_.isEmpty() || m_type != Choice || m_allowed.findIndex(value_) > -1) {
    setProperty(TQString::fromLatin1("default"), value_);
  }
}

bool Field::isSingleCategory() const {
  return (m_type == Para || m_type == Table || m_type == Table2 || m_type == Image);
}

// format is something like "%{year} %{author}"
Tellico::Data::FieldVec Field::dependsOn(CollPtr coll_) const {
  FieldVec vec;
  if(m_type != Dependent || !coll_) {
    return vec;
  }

  const TQStringList fieldNames = dependsOn();
  // do NOT call recursively!
  for(TQStringList::ConstIterator it = fieldNames.begin(); it != fieldNames.end(); ++it) {
    FieldPtr field = coll_->fieldByName(*it);
    if(!field) {
      // allow the user to also use field titles
      field = coll_->fieldByTitle(*it);
    }
    if(field) {
      vec.append(field);
    }
  }
  return vec;
}

TQStringList Field::dependsOn() const {
  TQStringList list;
  if(m_type != Dependent) {
    return list;
  }

  TQRegExp rx(TQString::fromLatin1("%\\{(.+)\\}"));
  rx.setMinimal(true);
  // do NOT call recursively!
  for(int pos = m_desc.find(rx); pos > -1; pos = m_desc.find(rx, pos+3)) {
    list << rx.cap(1);
  }
  return list;
}

TQString Field::format(const TQString& value_, FormatFlag flag_) {
  if(value_.isEmpty()) {
    return value_;
  }

  TQString text;
  switch(flag_) {
    case FormatTitle:
      text = formatTitle(value_);
      break;
    case FormatName:
      text = formatName(value_);
      break;
    case FormatDate:
      text = formatDate(value_);
      break;
    case FormatPlain:
      text = Config::autoCapitalization() ? capitalize(value_) : value_;
      break;
    default:
      text = value_;
      break;
  }
  return text;
}

TQString Field::formatTitle(const TQString& title_) {
  TQString newTitle = title_;
  // special case for multi-column tables, assume user never has '::' in a value
  const TQString colonColon = TQString::fromLatin1("::");
  TQString tail;
  if(newTitle.find(colonColon) > -1) {
    tail = colonColon + newTitle.section(colonColon, 1);
    newTitle = newTitle.section(colonColon, 0, 0);
  }

  if(Config::autoCapitalization()) {
    newTitle = capitalize(newTitle);
  }

  if(Config::autoFormat()) {
    const TQString lower = newTitle.lower();
    // TODO if the title has ",the" at the end, put it at the front
    for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
      // assume white space is already stripped
      // the articles are already in lower-case
      if(lower.startsWith(*it + TQChar(' '))) {
        TQRegExp regexp(TQChar('^') + TQRegExp::escape(*it) + TQString::fromLatin1("\\s*"), false);
        // can't just use *it since it's in lower-case
        TQString article = newTitle.left((*it).length());
        newTitle = newTitle.replace(regexp, TQString())
                           .append(TQString::fromLatin1(", "))
                           .append(article);
        break;
      }
    }
  }

  // also, arbitrarily impose rule that a space must follow every comma
  newTitle.replace(comma_split, TQString::fromLatin1(", "));
  return newTitle + tail;
}

TQString Field::formatName(const TQString& name_, bool multiple_/*=true*/) {
  static const TQRegExp spaceComma(TQString::fromLatin1("[\\s,]"));
  static const TQString colonColon = TQString::fromLatin1("::");
  // the ending look-ahead is so that a space is not added at the end
  static const TQRegExp periodSpace(TQString::fromLatin1("\\.\\s*(?=.)"));

  TQStringList entries;
  if(multiple_) {
    // split by semi-colon, optionally preceded or followed by white spacee
    entries = TQStringList::split(s_delimiter, name_, false);
  } else {
    entries << name_;
  }

  TQRegExp lastWord;
  lastWord.setCaseSensitive(false);

  TQStringList names;
  for(TQStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
    TQString name = *it;
    // special case for 2-column tables, assume user never has '::' in a value
    TQString tail;
    if(name.find(colonColon) > -1) {
      tail = colonColon + name.section(colonColon, 1);
      name = name.section(colonColon, 0, 0);
    }
    name.replace(periodSpace, TQString::fromLatin1(". "));
    if(Config::autoCapitalization()) {
      name = capitalize(name);
    }

    // split the name by white space and commas
    TQStringList words = TQStringList::split(spaceComma, name, false);
    lastWord.setPattern(TQChar('^') + TQRegExp::escape(words.last()) + TQChar('$'));

    // if it contains a comma already and the last word is not a suffix, don't format it
    if(!Config::autoFormat() || (name.find(',') > -1 && Config::nameSuffixList().grep(lastWord).isEmpty())) {
      // arbitrarily impose rule that no spaces before a comma and
      // a single space after every comma
      name.replace(comma_split, TQString::fromLatin1(", "));
      names << name + tail;
      continue;
    }
    // otherwise split it by white space, move the last word to the front
    // but only if there is more than one word
    if(words.count() > 1) {
      // if the last word is a suffix, it has to be kept with last name
      if(Config::nameSuffixList().grep(lastWord).count() > 0) {
        words.prepend(words.last().append(TQChar(',')));
        words.remove(words.fromLast());
      }

      // now move the word
      // adding comma here when there had been a suffix is because it was originally split with space or comma
      words.prepend(words.last().append(TQChar(',')));
      words.remove(words.fromLast());

      // update last word regexp
      lastWord.setPattern(TQChar('^') + TQRegExp::escape(words.last()) + TQChar('$'));

      // this is probably just something for me, limited to english
      while(Config::surnamePrefixList().grep(lastWord).count() > 0) {
        words.prepend(words.last());
        words.remove(words.fromLast());
        lastWord.setPattern(TQChar('^') + TQRegExp::escape(words.last()) + TQChar('$'));
      }

      names << words.join(TQChar(' ')) + tail;
    } else {
      names << name + tail;
    }
  }

  return names.join(TQString::fromLatin1("; "));
}

TQString Field::formatDate(const TQString& date_) {
  // internally, this is "year-month-day"
  // any of the three may be empty
  // if they're not digits, return the original string
  bool empty = true;
  // for empty year, use current
  // for empty month or date, use 1
  TQStringList s = TQStringList::split('-', date_, true);
  bool ok = true;
  int y = s.count() > 0 ? s[0].toInt(&ok) : TQDate::currentDate().year();
  if(ok) {
    empty = false;
  } else {
    y = TQDate::currentDate().year();
  }
  int m = s.count() > 1 ? s[1].toInt(&ok) : 1;
  if(ok) {
    empty = false;
  } else {
    m = 1;
  }
  int d = s.count() > 2 ? s[2].toInt(&ok) : 1;
  if(ok) {
    empty = false;
  } else {
    d = 1;
  }
  // rather use ISO date formatting than locale formatting for now. Primarily, it makes sorting just work.
  return empty ? date_ : TQString(TQDate(y, m, d).toString(TQt::ISODate));
  // use short form
//  return TDEGlobal::locale()->formatDate(date, true);
}

TQString Field::capitalize(TQString str_) {
  // regexp to split words
  static const TQRegExp rx(TQString::fromLatin1("[-\\s,.;]"));

  if(str_.isEmpty()) {
    return str_;
  }
  // first letter is always capitalized
  str_.replace(0, 1, str_.at(0).upper());

  // special case for french words like l'espace

  int pos = str_.find(rx, 1);
  int nextPos;

  TQRegExp wordRx;
  wordRx.setCaseSensitive(false);

  TQStringList notCap = Config::noCapitalizationList();
  // don't capitalize the surname prefixes
  // does this hold true everywhere other than english?
  notCap += Config::surnamePrefixList();

  TQString word = str_.mid(0, pos);
  // now check to see if words starts with apostrophe list
  for(TQStringList::ConstIterator it = s_articlesApos.constBegin(); it != s_articlesApos.constEnd(); ++it) {
    if(word.lower().startsWith(*it)) {
      uint l = (*it).length();
      str_.replace(l, 1, str_.at(l).upper());
      break;
    }
  }

  while(pos > -1) {
    // also need to compare against list of non-capitalized words
    nextPos = str_.find(rx, pos+1);
    if(nextPos == -1) {
      nextPos = str_.length();
    }
    word = str_.mid(pos+1, nextPos-pos-1);
    bool aposMatch = false;
    // now check to see if words starts with apostrophe list
    for(TQStringList::ConstIterator it = s_articlesApos.constBegin(); it != s_articlesApos.constEnd(); ++it) {
      if(word.lower().startsWith(*it)) {
        uint l = (*it).length();
        str_.replace(pos+l+1, 1, str_.at(pos+l+1).upper());
        aposMatch = true;
        break;
      }
    }

    if(!aposMatch) {
      wordRx.setPattern(TQChar('^') + TQRegExp::escape(word) + TQChar('$'));
      if(notCap.grep(wordRx).isEmpty() && nextPos-pos > 1) {
        str_.replace(pos+1, 1, str_.at(pos+1).upper());
      }
    }

    pos = str_.find(rx, pos+1);
  }
  return str_;
}

TQString Field::sortKeyTitle(const TQString& title_) {
  const TQString lower = title_.lower();
  for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
    // assume white space is already stripped
    // the articles are already in lower-case
    if(lower.startsWith(*it + TQChar(' '))) {
      return title_.mid((*it).length() + 1);
    }
  }
  // check apostrophes, too
  for(TQStringList::ConstIterator it = s_articlesApos.constBegin(); it != s_articlesApos.constEnd(); ++it) {
    if(lower.startsWith(*it)) {
      return title_.mid((*it).length());
    }
  }
  return title_;
}

// articles should all be in lower-case
void Field::articlesUpdated() {
  s_articles = Config::articleList();
  s_articlesApos.clear();
  for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
    if((*it).endsWith(TQChar('\''))) {
      s_articlesApos += (*it);
    }
  }
}

// if these are changed, then CollectionFieldsDialog should be checked since it
// checks for equality against some of these strings
Field::FieldMap Field::typeMap() {
  FieldMap map;
  map[Field::Line]      = i18n("Simple Text");
  map[Field::Para]      = i18n("Paragraph");
  map[Field::Choice]    = i18n("Choice");
  map[Field::Bool]      = i18n("Checkbox");
  map[Field::Number]    = i18n("Number");
  map[Field::URL]       = i18n("URL");
  map[Field::Table]     = i18n("Table");
  map[Field::Image]     = i18n("Image");
  map[Field::Dependent] = i18n("Dependent");
//  map[Field::ReadOnly] = i18n("Read Only");
  map[Field::Date]      = i18n("Date");
  map[Field::Rating]    = i18n("Rating");
  return map;
}

// just for formatting's sake
TQStringList Field::typeTitles() {
  const FieldMap& map = typeMap();
  TQStringList list;
  list.append(map[Field::Line]);
  list.append(map[Field::Para]);
  list.append(map[Field::Choice]);
  list.append(map[Field::Bool]);
  list.append(map[Field::Number]);
  list.append(map[Field::URL]);
  list.append(map[Field::Date]);
  list.append(map[Field::Table]);
  list.append(map[Field::Image]);
  list.append(map[Field::Rating]);
  list.append(map[Field::Dependent]);
  return list;
}

TQStringList Field::split(const TQString& string_, bool allowEmpty_) {
  return string_.isEmpty() ? TQStringList() : TQStringList::split(s_delimiter, string_, allowEmpty_);
}

void Field::addAllowed(const TQString& value_) {
  if(m_type != Choice) {
    return;
  }
  if(m_allowed.findIndex(value_) == -1) {
    m_allowed += value_;
  }
}

void Field::setProperty(const TQString& key_, const TQString& value_) {
  m_properties.insert(key_, value_);
}

void Field::setPropertyList(const StringMap& props_) {
  m_properties = props_;
}

void Field::convertOldRating(Data::FieldPtr field_) {
  if(field_->type() != Data::Field::Choice) {
    return; // nothing to do
  }

  if(field_->name() != Latin1Literal("rating")
     && field_->property(TQString::fromLatin1("rating")) != Latin1Literal("true")) {
    return; // nothing to do
  }

  int min = 10;
  int max = 1;
  bool ok;
  const TQStringList& allow = field_->allowed();
  for(TQStringList::ConstIterator it = allow.begin(); it != allow.end(); ++it) {
    int n = Tellico::toUInt(*it, &ok);
    if(!ok) {
      return; // no need to convert
    }
    min = TQMIN(min, n);
    max = TQMAX(max, n);
  }
  max = TQMIN(max, 10);
  if(min >= max) {
    min = 1;
    max = 5;
  }
  field_->setProperty(TQString::fromLatin1("minimum"), TQString::number(min));
  field_->setProperty(TQString::fromLatin1("maximum"), TQString::number(max));
  field_->setProperty(TQString::fromLatin1("rating"), TQString());
  field_->setType(Rating);
}

// static
long Field::getID() {
  static long id = 0;
  return ++id;
}

void Field::stripArticles(TQString& value) {
  if(s_articles.isEmpty()) {
    return;
  }
  for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
    TQRegExp rx(TQString::fromLatin1("\\b") + *it + TQString::fromLatin1("\\b"));
    value.remove(rx);
  }
  value = value.stripWhiteSpace();
  value.remove(TQRegExp(TQString::fromLatin1(",$")));
}
