/* 
 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "db_helpers.h"
#include "base/string_utilities.h"
#include "grtpp_util.h"
#include "base/log.h"

std::string bec::get_host_identifier_for_connection(const db_mgmt_ConnectionRef &connection)
{
  grt::DictRef params(connection->parameterValues());
  std::string host_id;
  
  if (connection->driver().is_valid())
  {
    std::string host_identifier = *connection->driver()->hostIdentifierTemplate();
    for (grt::DictRef::const_iterator par = params.begin(); par != params.end(); ++par)
    {
      base::replace(host_identifier, "%"+par->first+"%", par->second.repr());
    }
    return host_identifier;
  }
  else
    return connection->name();
}


std::string bec::get_description_for_connection(const db_mgmt_ConnectionRef &connection)
{
  std::string conn_type;
  std::string driver, server;
  grt::DictRef params(connection->parameterValues());
  
  if (connection->driver().is_valid())
  {
    driver = connection->driver()->name();
    server = db_mgmt_RdbmsRef::cast_from(connection->driver()->owner())->caption();
  }
  else
    return "Invalid Connection Description";

  std::string user = params.get_string("userName");
  
  if (g_str_has_suffix(driver.c_str(), "Socket"))
  {
    std::string path = base::trim(params.get_string("socket"));
    conn_type = base::strfmt("%s using local socket/pipe at \"%s\" with user %s", 
                             server.c_str(),
                             path.empty()?"default path":path.c_str(),
                             user.c_str());
  }
  else if (g_str_has_suffix(driver.c_str(), "SSH"))
  {    
    conn_type = base::strfmt("%s at %s:%i through SSH tunnel at %s with user %s",
                             server.c_str(),
                             params.get_string("hostName").c_str(),
                             (int) params.get_int("port"),
                             params.get_string("sshHost").c_str(),
                             user.c_str());
  }
  else if (g_str_has_suffix(driver.c_str(), "Fabric"))
  {
    conn_type = base::strfmt("%s at %s:%i fabric node with user %s",
                             server.c_str(),
                             params.get_string("hostName").c_str(),
                             (int) params.get_int("port"),
                             user.c_str());
  }
  else // TCP
  {
    conn_type = base::strfmt("%s at %s:%i with user %s",
                             server.c_str(),
                             params.get_string("hostName").c_str(),
                             (int) params.get_int("port"),
                             user.c_str());
  }

  return conn_type;  
}


std::string bec::sanitize_server_version_number(const std::string &version)
{
  int major, minor, release, patch;
  if (sscanf(version.c_str(), "%i.%i.%i-%i", &major, &minor, &release, &patch) == 4)
  {
    return base::strfmt("%i.%i.%i-%i", major, minor, release, patch);
  }
  else if (sscanf(version.c_str(), "%i.%i.%i", &major, &minor, &release) == 3)
  {
    return base::strfmt("%i.%i.%i", major, minor, release);
  }
  return version;
}

//--------------------------------------------------------------------------------------------------

/**
 * Parses the given version string into its components and returns a GRT version class.
 * Unspecified components are set to -1 to allow for fuzzy comparisons.
 */
GrtVersionRef bec::parse_version(grt::GRT *grt, const std::string &target_version)
{
  int major = 0, minor = -1, release = -1, build = -1;
  
  sscanf(target_version.c_str(), "%i.%i.%i.%i", &major, &minor, &release, &build);
  
  GrtVersionRef version(grt);
  version->name("Version");
  version->majorNumber(major);
  version->minorNumber(minor);
  version->releaseNumber(release);
  version->buildNumber(build);
  
  return version;
}

//--------------------------------------------------------------------------------------------------

/**
* Parses the given version string into its components and returns a GRT version class.
* Unspecified components are set to -1 to allow for fuzzy comparisons.
*/
GrtVersionRef bec::int_to_version(grt::GRT *grt, int version)
{
  int major = version / 10000, minor = (version / 100) % 100, release = version % 100, build = -1;

  GrtVersionRef version_(grt);
  version_->name("Version");
  version_->majorNumber(major);
  version_->minorNumber(minor);
  version_->releaseNumber(release);
  version_->buildNumber(build);

  return version_;
}

//--------------------------------------------------------------------------------------------------

/**
 * Compares the given version numbers to see if a is equal to b.
 * In order to support wildcards, missing values are interpreted as matching, e.g. 5 is equal to 5.1
 * and 5.1 is equal to 5.1.1.
 *
 * Do not use for comparing supported MySQL server versions.
 */
bool bec::version_equal(GrtVersionRef a, GrtVersionRef b)
{
  // Major version number is always there.
  if (a->majorNumber() != b->majorNumber())
    return false;
  
  if (a->minorNumber() == -1 || b->minorNumber() == -1)
    return true;
  
  if (a->minorNumber() != b->minorNumber())
    return false;

  if (a->releaseNumber() == -1 || b->releaseNumber() == -1)
    return true;
  
  if (a->releaseNumber() != b->releaseNumber())
    return false;

  if (a->buildNumber() == -1 || b->buildNumber() == -1)
    return true;

  if (a->buildNumber() != b->buildNumber())
    return false;

  return true;
}

/**
 * This test is similar to the one above (except that it tests for greater-than relations).
 * For this however we have to treat unset values differently to handle cases like
 * 5 > 5.1 (false) or 5.1 > 5 (true) correctly.
 *
 * Do not use for comparing supported MySQL server versions.
 */
bool bec::version_greater(GrtVersionRef a, GrtVersionRef b)
{
  if (a->majorNumber() > b->majorNumber()) // Major number should always be set.
    return true;

  if (a->majorNumber() == b->majorNumber())
  {
    if (a->minorNumber() == -1) // An unset value can never be larger than anything else.
      return false;
    
    if (b->minorNumber() == -1) // An unset value is always smaller than any other set value.
        return true;
    
    if (a->minorNumber() > b->minorNumber())
      return true;
    
    // Same approach for release + build numbers.
    if (a->minorNumber() == b->minorNumber())
    {
      if (a->releaseNumber() == -1)
        return false;

      if (b->releaseNumber() == -1)
        return true;

      if (a->releaseNumber() > b->releaseNumber())
        return true;

      if (a->releaseNumber() == b->releaseNumber())
      {
        if (a->buildNumber() == -1)
          return false;

        if (b->buildNumber() == -1)
          return true;

        if (a->buildNumber() > b->buildNumber())
          return true;
      }
      return false;
    }
    return false;
  }

  return false;
}


/** Checks if the given server version numbers is in the set of supported MySQL servers
 */
bool bec::is_supported_mysql_version(int mysql_major, int mysql_minor, int mysql_release)
{
  return (mysql_major == 5 &&
          (mysql_minor == 1 || mysql_minor == 5 || mysql_minor == 6 || mysql_minor == 7));
}


bool bec::is_supported_mysql_version(const std::string &mysql_version)
{
  int my_major = 0, my_minor = -1, my_release = -1, my_build = -1;
  
  sscanf(mysql_version.c_str(), "%i.%i.%i.%i", &my_major, &my_minor, &my_release, &my_build);

  return is_supported_mysql_version(my_major, my_minor, my_release);
}


/** Checks whether the version number supplied is in the known set of versions larger than it.
 Use for server version checks for features.
 */
bool bec::is_supported_mysql_version_at_least(int mysql_major, int mysql_minor, int mysql_release,
                                              int major, int minor, int release)
{
  // if the version required is older (<) than 5.6, then any server that matches is fine
  // if the version required is newer (>=) than 5.6, then we can only guarantee that known servers versions have the feature

  assert(mysql_major < 100 && mysql_minor < 100 && mysql_release < 1000);
  assert(major < 100 && minor < 100 && release < 1000);

  // assemble MMNNRRR
  unsigned int required = major * 100000 + minor * 1000 + (release < 0 ? 0 : release);
  // if the available release number is negative, that's meant to signify "any release number", so we make it the max value possible
  unsigned int available = mysql_major * 100000 + mysql_minor * 1000 + (mysql_release < 0 ? 999 : mysql_release);

  if (major < 5 || (major == 5 && minor < 6))
  {
    return (required <= available);
  }
  else if (is_supported_mysql_version(mysql_major, mysql_minor, mysql_release))
  {
    return (required <= available);
  }
  return false;
}


bool bec::is_supported_mysql_version_at_least(const std::string &mysql_version,
                                              int major, int minor, int release)
{
  int my_major = 0, my_minor = -1, my_release = -1, my_build = -1;
  
  sscanf(mysql_version.c_str(), "%i.%i.%i.%i", &my_major, &my_minor, &my_release, &my_build);

  return is_supported_mysql_version_at_least(my_major, my_minor, my_release, major, minor, release);
}


bool bec::is_supported_mysql_version_at_least(const GrtVersionRef &mysql_version, int major, int minor, int release)
{
  if (mysql_version.is_valid())
    return is_supported_mysql_version_at_least((int)mysql_version->majorNumber(),
      (int)mysql_version->minorNumber(), (int)mysql_version->releaseNumber(), major, minor, release);
  return false;
}
