/***************************************************************************
    Copyright (C) 2007-2009 Robby Stephenson <robby@periapsis.org>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as        *
 *   published by the Free Software Foundation; either version 2 of        *
 *   the License or (at your option) version 3 or any later version        *
 *   accepted by the membership of KDE e.V. (or its successor approved     *
 *   by the membership of KDE e.V.), which shall act as a proxy            *
 *   defined in Section 14 of version 3 of the license.                    *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 *                                                                         *
 ***************************************************************************/

#include "stringcomparison.h"
#include "../fieldformat.h"
#include "../tellico_debug.h"

#include <QDateTime>

namespace {
  int compareFloat(const QString& s1, const QString& s2) {
    bool ok1, ok2;
    float n1 = s1.toFloat(&ok1);
    if(!ok1) {
      return 0;
    }
    float n2 = s2.toFloat(&ok2);
    if(!ok2) {
      return 0;
    }
    return n1 > n2 ? 1 : (n1 < n2 ? -1 : 0);
  }
}

Tellico::StringComparison::StringComparison() {
}

int Tellico::StringComparison::compare(const QString& str1_, const QString& str2_) {
  return str1_.localeAwareCompare(str2_);
}

Tellico::BoolComparison::BoolComparison() : StringComparison() {
}

int Tellico::BoolComparison::compare(const QString& str1_, const QString& str2_) {
  return str1_.compare(str2_);
}


Tellico::TitleComparison::TitleComparison() : StringComparison() {
}

int Tellico::TitleComparison::compare(const QString& str1_, const QString& str2_) {
  const QString title1 = FieldFormat::sortKeyTitle(str1_).toLower();
  const QString title2 = FieldFormat::sortKeyTitle(str2_).toLower();
  return title1.localeAwareCompare(title2);
}

Tellico::NumberComparison::NumberComparison() : StringComparison() {
}

int Tellico::NumberComparison::compare(const QString& str1_, const QString& str2_) {
  // by default, an empty string would get sorted before "1" because toFloat() turns it into "0"
  // I want the empty strings to be at the end
  bool ok1, ok2;
  // use section in case of multiple values
  float num1 = str1_.section(QLatin1Char(';'), 0, 0).toFloat(&ok1);
  float num2 = str2_.section(QLatin1Char(';'), 0, 0).toFloat(&ok2);
  if(ok1 && ok2) {
    return static_cast<int>(num1 - num2);
  } else if(ok1 && !ok2) {
    return -1;
  } else if(!ok1 && ok2) {
    return 1;
  }
  return 0;
}

// for details on the LCC comparison, see
// http://www.mcgees.org/2001/08/08/sort-by-library-of-congress-call-number-in-perl/
// http://library.dts.edu/Pages/RM/Helps/lc_call.shtml

Tellico::LCCComparison::LCCComparison() : StringComparison(),
  m_regexp(QLatin1String("^([A-Z]+)"
                         "(\\d+(?:\\.\\d+)?)"
                         "\\.?([A-Z]*)"
                         "(\\d*)"
                         "\\.?([A-Z]*)"
                         "(\\d*)"
                         "(?: (.+))?")) {
}

int Tellico::LCCComparison::compare(const QString& str1_, const QString& str2_) {
//  myDebug() << str1_ << " to " << str2_;
  int pos1 = m_regexp.indexIn(str1_);
  const QStringList cap1 = m_regexp.capturedTexts();
  int pos2 = m_regexp.indexIn(str2_);
  const QStringList cap2 = m_regexp.capturedTexts();
  if(pos1 > -1 && pos2 > -1) {
    int res = compareLCC(cap1, cap2);
//    myLog() << "...result = " << res;
    return res;
  } else {
    if(pos1 == -1) {
      myDebug() << "no regexp match:" << str1_;
    }
    if(pos2 == -1) {
      myDebug() << "no regexp match:" << str2_;
    }
  }
  return StringComparison::compare(str1_, str2_);
}

int Tellico::LCCComparison::compareLCC(const QStringList& cap1, const QStringList& cap2) const {
  // the first item in the list is the full match, so start array index at 1
  int res = 0;
  return (res = cap1[1].compare(cap2[1]))                    != 0 ? res :
         (res = compareFloat(cap1[2], cap2[2]))              != 0 ? res :
         (res = cap1[3].compare(cap2[3]))                    != 0 ? res :
         (res = compareFloat(QLatin1String("0.") + cap1[4],
                             QLatin1String("0.") + cap2[4])) != 0 ? res :
         (res = cap1[5].compare(cap2[5]))                    != 0 ? res :
         (res = compareFloat(QLatin1String("0.") + cap1[6],
                             QLatin1String("0.") + cap2[6])) != 0 ? res :
         (res = cap1[7].compare(cap2[7]))                    != 0 ? res : 0;
}

Tellico::ISODateComparison::ISODateComparison() : StringComparison() {
}

int Tellico::ISODateComparison::compare(const QString& str1, const QString& str2) {
  if(str1.isEmpty()) {
    return str2.isEmpty() ? 0 : -1;
  }
  if(str2.isEmpty()) { // str1 is not
    return 1;
  }
  // modelled after Field::formatDate()
  // so dates would sort as expected without padding month and day with zero
  // and accounting for "current year - 1 - 1" default scheme
  QStringList dlist1 = str1.split(QLatin1Char('-'), QString::KeepEmptyParts);
  bool ok = true;
  int y1 = dlist1.count() > 0 ? dlist1[0].toInt(&ok) : QDate::currentDate().year();
  if(!ok) {
    y1 = QDate::currentDate().year();
  }
  int m1 = dlist1.count() > 1 ? dlist1[1].toInt(&ok) : 1;
  if(!ok) {
    m1 = 1;
  }
  int d1 = dlist1.count() > 2 ? dlist1[2].toInt(&ok) : 1;
  if(!ok) {
    d1 = 1;
  }
  QDate date1(y1, m1, d1);

  QStringList dlist2 = str2.split(QLatin1Char('-'), QString::KeepEmptyParts);
  int y2 = dlist2.count() > 0 ? dlist2[0].toInt(&ok) : QDate::currentDate().year();
  if(!ok) {
    y2 = QDate::currentDate().year();
  }
  int m2 = dlist2.count() > 1 ? dlist2[1].toInt(&ok) : 1;
  if(!ok) {
    m2 = 1;
  }
  int d2 = dlist2.count() > 2 ? dlist2[2].toInt(&ok) : 1;
  if(!ok) {
    d2 = 1;
  }
  QDate date2(y2, m2, d2);

  if(date1 < date2) {
    return -1;
  } else if(date1 > date2) {
    return 1;
  }
  return 0;
}
