#include "stdafx.h"

#include <grts/structs.db.h>

#include <grtpp_util.h>
#include <grtpp_undo_manager.h>

#include "grt/parse_utils.h"
#include "grt/common.h"
#include "grtdb/db_object_helpers.h"
#include <pcre.h>


//================================================================================
// db_Column

void db_Column::init()
{
}

db_Column::~db_Column()
{
}


template<class T>
class auto_array_ptr
{
  T *_ptr;

public:
  auto_array_ptr(T* ptr) : _ptr(ptr) {}
  ~auto_array_ptr() { delete[] _ptr; }

  operator T* () { return _ptr; }
};


void db_Column::name(const grt::StringRef &value)
{
  grt::ValueRef ovalue(_name);
  if (_owner.is_valid() && _name != value)
    db_TableRef::cast_from(_owner)->signal_refreshDisplay().emit("column");
  _name= value;
  member_changed("name", ovalue, value);
}


grt::StringRef db_Column::formattedRawType() const
{
  if (this->userType().is_valid())
  {
    std::string arguments;
    
    // if no simple or structured datatype is set,
    // simply take the parameters
    if (this->length() != bec::EMPTY_COLUMN_LENGTH)
    {
      arguments= strfmt("(%i)", (int)this->length());
    }
    else if (this->precision() != bec::EMPTY_COLUMN_PRECISION)
    {
      std::string tmp;
      if (this->scale() != bec::EMPTY_COLUMN_SCALE)
        tmp= strfmt("(%i,%i)", (int)this->precision(), (int)this->scale());
      else
        tmp= strfmt("(%i)", (int)this->precision());
      arguments= tmp;
    }
    else if (this->datatypeExplicitParams().is_valid() && *this->datatypeExplicitParams() != "")
    {
      arguments= *this->datatypeExplicitParams();      
    }
    
    return std::string(this->userType()->name()).append(arguments);
  }
  else
    return formattedType();
}


grt::StringRef db_Column::formattedType() const
{
  db_SimpleDatatypeRef simpleType(this->simpleType());
  db_StructuredDatatypeRef structuredType(this->structuredType());
  std::string caption;

  if (simpleType.is_valid())
  {
    caption= simpleType->name();

    if (simpleType->numericPrecision() != bec::EMPTY_TYPE_PRECISION)
    {
      std::string tmp;
      if (this->scale() != bec::EMPTY_COLUMN_SCALE)
        tmp= strfmt("(%i,%i)", (int)this->precision(), (int)this->scale());
      else if (this->precision() != bec::EMPTY_COLUMN_PRECISION)
        tmp= strfmt("(%i)", (int)this->precision());
      caption.append(tmp);
    }
    else
    {
      if (*simpleType->characterMaximumLength() != bec::EMPTY_TYPE_MAXIMUM_LENGTH 
          || *simpleType->characterOctetLength() != bec::EMPTY_TYPE_OCTET_LENGTH)
      {
        if (this->length() != bec::EMPTY_COLUMN_LENGTH)
        {
          caption.append(strfmt("(%i)", (int)this->length()));
        }
      }
      else if (this->datatypeExplicitParams().is_valid() && *this->datatypeExplicitParams() != "")
        caption.append(*this->datatypeExplicitParams());
    }
  }
  else if (structuredType.is_valid())
  {
  }
  else
  {
    std::string arguments;
    
    if (this->userType().is_valid())
      caption= this->userType()->sqlDefinition();

    // if no simple or structured datatype is set,
    // simply take the parameters
    if (this->length() != bec::EMPTY_COLUMN_LENGTH)
    {
      arguments= strfmt("(%i)", (int)this->length());
    }
    else if (this->precision() != bec::EMPTY_COLUMN_PRECISION)
    {
      std::string tmp;
      if (this->scale() != bec::EMPTY_COLUMN_SCALE)
        tmp= strfmt("(%i,%i)", (int)this->precision(), (int)this->scale());
      else
        tmp= strfmt("(%i)", (int)this->precision());
      arguments= tmp;
    }
    else if (this->datatypeExplicitParams().is_valid() && *this->datatypeExplicitParams() != "")
    {
      arguments= *this->datatypeExplicitParams();      
    }
    
    if (!arguments.empty())
    {
      std::string::size_type p;
      if ((p= caption.find('(')) != std::string::npos)
        caption= caption.substr(0, p);
      
      caption.append(arguments);
    }
  }

  return caption;
}


void db_Column::formattedType(const grt::StringRef &value)
{
  // add code here
  g_warning("got a request to change %s.formattedType() from '%s' to '%s'",
            owner().is_valid() ? owner()->name().c_str() : "<null>", formattedType().c_str(), value.c_str());
}



static int pcre_compile_exec(const char *pattern, const char *str, int *patres, int patresnum)
{
  const char *errptr;
  int erroffs;
  int c;
  pcre *patre= pcre_compile(pattern, 0, &errptr, &erroffs, NULL);
  if (!patre)
    throw std::logic_error("error compiling regex "+std::string(errptr));

  c= pcre_exec(patre, NULL, str, strlen(str), 0, 0, patres, patresnum);
  pcre_free(patre);

  return c;
}


static db_SimpleDatatypeRef findType(const grt::ListRef<db_SimpleDatatype> &types, const std::string &name)
{
  for (size_t c= types.count(), i= 0; i < c; i++)
  {
    if (g_strcasecmp(types[i]->name().c_str(), name.c_str())==0)
      return types[i];
    //for (size_t d= types[i]->synonyms().count(), j= 0; j < d; j++)
    //{
    //  if (g_strcasecmp(types[i]->synonyms().get(j).c_str(), name.c_str())==0)
    //    return types[i];
    //}
  }
  return db_SimpleDatatypeRef();
}


static bool parseTypeDefinition(const std::string &type,
                                const grt::ListRef<db_SimpleDatatype> &typeList,
                                db_SimpleDatatypeRef &simpleType,
                                int &precision,
                                int &scale,
                                int &length,
                                std::string &explicitParams)
{
  auto_array_ptr<char> buffer(new char[type.size()+1]);
  int patres[21];
  int c;
    
  /* split the datatype name from the parameters
   * (\w+)\s*(\(.*\))?
   *
   * 1.. TYPE
   * 2.. params (optional)
   */
  
  c= pcre_compile_exec("^(\\w+)\\s*(\\(.*\\))?\\s*$", type.c_str(), patres, sizeof(patres)/sizeof(int));
  
  if (c > 0 &&
      pcre_copy_substring(type.c_str(), patres, c, 1, buffer, static_cast<int>(type.size()+1)) > 0)
  {
    // find the simple datatype with this type_name
    simpleType= findType(typeList, std::string(buffer));
    
    if (simpleType.is_valid())
    {
      std::string params;
      std::string param1;
      std::string param2;
      
      // split the parameter into its components
      
      if (pcre_copy_substring(type.c_str(), patres, c, 2, buffer, static_cast<int>(type.size()+1)) > 0)
        params= buffer;
      
      switch (*simpleType->parameterFormatType())
      {
        case 0: // no params
          if (!params.empty())
            return false;
          break;
          
        case 1: // (n)
          c= pcre_compile_exec("^\\((\\d+)\\)$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          if (pcre_copy_substring(params.c_str(), patres, c, 1, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            param1= buffer;
          }
          else
            return false;
          break;
          
        case 2: // [(n)]
          c= pcre_compile_exec("^(\\((\\d+)\\))?$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          if (pcre_copy_substring(params.c_str(), patres, c, 2, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            param1= buffer;
          }
          break;
          
        case 3: // (m, n)
          c= pcre_compile_exec("^\\((\\d+),\\s*(\\d+)\\)$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          if (pcre_copy_substring(params.c_str(), patres, c, 1, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            param1= buffer;
            
            if (pcre_copy_substring(params.c_str(), patres, c, 2, buffer, static_cast<int>(params.size()+1)) > 0)
              param2= buffer;
            else
              return false;
          }
          else
            return false;
          break;
          
        case 4: // (m[,n])
          c= pcre_compile_exec("^\\((\\d+)(,\\s*(\\d+))?\\)$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          if (pcre_copy_substring(params.c_str(), patres, c, 1, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            param1= buffer;
            if (pcre_copy_substring(params.c_str(), patres, c, 3, buffer, static_cast<int>(params.size()+1)) > 0)
              param2= buffer;
          }
          else
            return false;
          break;
          
        case 5: // [(m,n)]
          c= pcre_compile_exec("^(\\((\\d+)(,\\s*(\\d+))\\))?$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          if (pcre_copy_substring(params.c_str(), patres, c, 2, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            param1= buffer;
            if (pcre_copy_substring(params.c_str(), patres, c, 4, buffer, static_cast<int>(params.size()+1)) > 0)
              param2= buffer;
            else
              return false;
          }
          break;
          
        case 6: // [(m[,n])]
          c= pcre_compile_exec("^(\\((\\d+)(,\\s*(\\d+))?\\))?$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          if (pcre_copy_substring(params.c_str(), patres, c, 2, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            param1= buffer;
            if (pcre_copy_substring(params.c_str(), patres, c, 4, buffer, static_cast<int>(params.size()+1)) > 0)
              param2= buffer;
          }
          break;
          
        case 10: // ('a','b','c')
          if (params.empty())
            return false;
                
          c= pcre_compile_exec("^\\((.*?)\\)?$", params.c_str(), patres, sizeof(patres)/sizeof(int));
          if (c < 0)
            return false;
          // For now we just keep the parameter list as a whole. If necessary this can be split later for other uses.
          param1= buffer;
          // validate format
          if (pcre_copy_substring(params.c_str(), patres, c, 1, buffer, static_cast<int>(params.size()+1)) > 0)
          {
            std::list<std::string> tokens;
            if (!bec::tokenize_string_list(std::string(buffer), '\'', true, tokens))
              return false;
          }
          else
            return false;
          break;
      }
      
      // get precision
      if (*simpleType->numericPrecision() != bec::EMPTY_TYPE_PRECISION)
      {
        if (!param1.empty())
          precision= atoi(param1.c_str());
        
        // get scale
        if (*simpleType->numericScale() != bec::EMPTY_TYPE_SCALE)
        {
          if (!param2.empty())
            scale= atoi(param2.c_str());
        }
      }
      // get length
      else if (simpleType->characterMaximumLength() != bec::EMPTY_TYPE_MAXIMUM_LENGTH 
               || simpleType->characterOctetLength()   != bec::EMPTY_TYPE_OCTET_LENGTH)
      {
        if (!param1.empty())
          length= atoi(param1.c_str());
      }
      else if (*simpleType->parameterFormatType() == 10)
        explicitParams= param1;
    }
    else
      return false; // unknown type
  }
  else
    return false; // bad format
  
  return true;
}


/** Sets the datatype defined by a string to the column.
 *
 * Assigns a datatype defined by a string like NUMERIC(7, 2) to the
 * given column.
 *
 * @param rdbms
 * @param type
 * 
 * @return 1 on success or 0 on parse error or invalid type/invalid params
 */
grt::IntegerRef db_Column::setParseType(const std::string &type, const grt::ListRef<db_SimpleDatatype> &typeList)
{
  grt::ListRef<db_UserDatatype> user_types;
  if (!owner().is_valid() || !owner()->owner().is_valid() || !owner()->owner()->owner().is_valid())
    ;
  else
  {
    db_CatalogRef catalog= db_CatalogRef::cast_from(owner()->owner()->owner());
    if (catalog.is_valid())
      user_types= catalog->userDatatypes();
  }

  db_UserDatatypeRef userType;
  db_SimpleDatatypeRef simpleType;
  int precision= bec::EMPTY_COLUMN_PRECISION;
  int scale= bec::EMPTY_COLUMN_SCALE;
  int length= bec::EMPTY_COLUMN_LENGTH;
  std::string datatypeExplicitParams;
  grt::UndoManager *um= 0;
  
  if (is_global() && get_grt()->tracking_changes())
    um= get_grt()->get_undo_manager();

  if (user_types.is_valid())
  {
    std::string::size_type argp= type.find('(');
    std::string typeName= type;
    
    if (argp != std::string::npos)
      typeName= type.substr(0, argp);
    
    // 1st check if this is a user defined type
    for (size_t c= user_types.count(), i= 0; i < c; i++)
    {
      db_UserDatatypeRef utype(user_types[i]);

      if (g_strcasecmp(utype->name().c_str(), typeName.c_str()) == 0)
      {
        userType= utype;
        break;
      }
    }
  }

  grt::AutoUndo undo(get_grt(), !is_global());

  if (userType.is_valid())
  {
    // if the type spec has an argument, we replace the arguments from the type definition
    // with the one provided by the user
    std::string finalType= userType->sqlDefinition();
    std::string::size_type tp;
    bool overriden_args= false;
    
    if ((tp= type.find('(')) != std::string::npos) // has user specified args
    {
      std::string::size_type p= finalType.find('('); 
      if (p != std::string::npos) // strip the original args
        finalType= finalType.substr(0, p);
      
      // extract the user spcified args and append to the specification
      finalType.append(type.substr(tp));
      
      overriden_args= true;
    }
    
    // parse usertype definition    
    if (!parseTypeDefinition(finalType, typeList, simpleType, 
                             precision, scale, length, datatypeExplicitParams))
      return false;
    
    this->simpleType(db_SimpleDatatypeRef());
    this->userType(userType);
    if (overriden_args)
    {
      this->precision(precision);
      this->scale(scale);
      this->length(length);
      this->datatypeExplicitParams(datatypeExplicitParams);
    }
    else
    {
      this->precision(bec::EMPTY_COLUMN_PRECISION);
      this->scale(bec::EMPTY_COLUMN_SCALE);
      this->length(bec::EMPTY_COLUMN_LENGTH);
      this->datatypeExplicitParams("");
    }
  }
  else
  {
    if (!parseTypeDefinition(type, typeList, simpleType, 
                             precision, scale, length, datatypeExplicitParams))
      return false;
    
    this->userType(db_UserDatatypeRef());
    this->simpleType(simpleType);
    this->precision(precision);
    this->scale(scale);
    this->length(length);
    this->datatypeExplicitParams(datatypeExplicitParams);
  }

  if (_owner.is_valid())
    db_TableRef::cast_from(_owner)->signal_refreshDisplay().emit("column");

  undo.end(_("Change Column Type"));

  return true;
}





