// @(#)root/cintex:$Id$
// Author: Pere Mato 2005

// Copyright CERN, CH-1211 Geneva 23, 2004-2005, All rights reserved.
//
// Permission to use, copy, modify, and distribute this software for any
// purpose is hereby granted without fee, provided that this copyright and
// permissions notice appear in all copies and derivatives.
//
// This software is provided "as is" without express or implied warranty.

#include "Reflex/Reflex.h"
#include "Reflex/Tools.h"
#include "Cintex/Cintex.h"
#include "CINTdefs.h"
#include "CINTVariableBuilder.h"
#include "CINTScopeBuilder.h"
#include "CINTCommentBuffer.h"

#include "Api.h"

#include <iomanip>
#include <sstream>

using namespace ROOT::Reflex;
using namespace std;

namespace ROOT { namespace Cintex {
  

   CINTVariableBuilder::CINTVariableBuilder(const ROOT::Reflex::Member& m)
      : fVariable(m) { 
      // Default constructor.
   }
   
   CINTVariableBuilder::~CINTVariableBuilder() {
      // Destructor.
   }

   void CINTVariableBuilder::Setup() {
      // Setup variable info.
      CINTScopeBuilder::Setup(fVariable.TypeOf());

      Scope scope = fVariable.DeclaringScope();
      CINTScopeBuilder::Setup(scope);
    
      bool global = scope.IsTopScope();

      if ( global ) {
         ::G__resetplocal();
      }    
      else {
         string sname = scope.Name(SCOPED);
         int stagnum = ::G__defined_tagname(sname.c_str(), 2);
         ::G__tag_memvar_setup(stagnum);      
      }

      Setup(fVariable);
    
      if ( global ) {
         ::G__resetglobalenv();
      }
      else {
         ::G__tag_memvar_reset();
      }
      return;
   }

   static void writeArrayIndex(ostream &ost, const Type& array)
   {
      Type toArray = array.ToType();
      ost << "[" << array.ArrayLength() << "]";
      if ( toArray.IsArray() ) writeArrayIndex(ost,toArray);
   }
 
   void CINTVariableBuilder::Setup(const Member& dm ) {
      // Setup variable info.
      char* comment = NULL;
    
      const char* ref_t = "pool::Reference";
      const char* tok_t = "pool::Token";

      Type fClass = Type::ByName(dm.DeclaringScope().Name(SCOPED));
      string fName(CintName(fClass));

      std::string cm = dm.Properties().HasProperty("comment") ? 
         dm.Properties().PropertyAsString("comment") : std::string("");

      Type dmType = dm.TypeOf();
      if (dm.Properties().HasProperty("iotype")) {
         dmType = Reflex::Type::ByName(dm.Properties().PropertyAsString("iotype"));
      }
      while ( dmType.IsTypedef() ) dmType = dmType.ToType();
      if ( !dmType && dm.IsTransient() )  {
         // Before giving up, let's ask CINT:
         int tagnum = G__defined_tagname(dmType.Name().c_str(), 2);
         
         if (tagnum < 0) {
            if( Cintex::Debug() ) cout << "Cintex: Ignore transient member: " 
               << dm.Name(SCOPED) << " [No valid reflection class]"
               << " " << dmType.Name() << endl;
            return;
         }
      }
      else if ( !dmType )  {
         if( Cintex::Debug() > 0 )  {
            cout << "Cintex: WARNING: Member: " 
                 << dm.Name(SCOPED) << " [No valid reflection class]" << endl;
         }
         //throw std::runtime_error("Member: "+fName+"::"+dm.Name()+" [No valid reflection class]");
      }
      //--- Add the necessary artificial comments ------ 
      if ( dm.IsTransient() ||
           IsSTL(fName) || 
           IsTypeOf(fClass, ref_t) || 
           IsTypeOf(fClass, tok_t) )  {
         char* com = new char[cm.length()+4];
         ::sprintf(com,"! %s",cm.c_str());
         comment = com;
         CommentBuffer::Instance().Add(comment);
      }
      else if ( (dmType.IsClass() || dmType.IsStruct()) && 
                (IsTypeOf(dmType,ref_t) || IsTypeOf(dmType,tok_t)) )  {
         char* com = new char[cm.length()+4];
         ::sprintf(com,"|| %s",cm.c_str());
         comment = com;
         CommentBuffer::Instance().Add(comment);
      }
      else if ( !cm.empty() )  {
         char* com = new char[cm.length()+4];
         ::strcpy(com, cm.c_str());
         comment = com;
         CommentBuffer::Instance().Add(comment);
      }

      Indirection  indir = IndirectionGet(dmType);
      CintTypeDesc type = CintType(indir.second);
      string dname = dm.Properties().HasProperty("ioname") ? dm.Properties().PropertyAsString("ioname") : dm.Name();
      ostringstream ost;
      if ( dmType.IsArray() ) {
         ost << dname;
         writeArrayIndex(ost,dmType);
         ost << "=";
      } else                    ost << dname << "=";
      string expr = ost.str();
      int member_type     = type.first;
      int member_indir    = 0;
      int member_tagnum   = -1;
      int member_typnum   = -1;
      int member_isstatic = G__AUTO;
      if (dm.IsStatic() || dm.DeclaringScope().IsNamespace())
         member_isstatic = G__LOCALSTATIC;
      switch(indir.first)  {
      case 0: 
         break;
      case 1:
         member_type -= 'a'-'A';            // if pointer: 'f' -> 'F' etc.
         break;
      default:
         member_type -= 'a'-'A';            // if pointer: 'f' -> 'F' etc.
         member_indir = indir.first;
         break;
      }

      if ( type.first == 'u' )  {
         // Large integer definition depends of the platform
#if defined(_WIN32) && !defined(__CINT__)
         typedef __int64 longlong;
         typedef unsigned __int64 ulonglong;
#else
         typedef long long int longlong; /* */
         typedef unsigned long long int /**/ ulonglong;
#endif

         //dependencies.push_back(indir.second);
         member_tagnum = dm.Properties().HasProperty("iotype") ? CintTag(dm.Properties().PropertyAsString("iotype")) : CintTag(type.second);
         if ( typeid(longlong) == indir.second.TypeInfo() )
            ::G__loadlonglong(&member_tagnum, &member_typnum, G__LONGLONG);
         else if ( typeid(ulonglong) == indir.second.TypeInfo() )
            ::G__loadlonglong(&member_tagnum, &member_typnum, G__ULONGLONG);
         else if ( typeid(long double) == indir.second.TypeInfo() )
            ::G__loadlonglong(&member_tagnum, &member_typnum, G__LONGDOUBLE);
      }

      int member_access = 0;
      if ( dm.IsPrivate() )        member_access = G__PRIVATE;
      else if ( dm.IsProtected() ) member_access = G__PROTECTED;
      else if ( dm.IsPublic() )    member_access = G__PUBLIC;

      if ( Cintex::Debug() > 2 )  {
         std::cout 
            << std::setw(24) << std::left << "Cintex: declareField>"
            << "  [" << char(member_type) 
            << "," << std::right << std::setw(3) << dm.Offset()
            << "," << std::right << std::setw(2) << member_indir 
            << "," << std::right << std::setw(3) << member_tagnum
            << "] " 
            << (dmType.IsConst() ? "const " : "")
            << std::left << std::setw(7)
            << (G__AUTO==member_isstatic ? "auto " : "static ")
            << std::left << std::setw(24) << dname
            << " \"" << (char*)(comment ? comment : "(None)") << "\""
            << std::endl
            << std::setw(24) << std::left << "Cintex: declareField>"
            << "  Type:" 
            << std::left << std::setw(24) << ("[" + (dm.Properties().HasProperty("iotype") ? dm.Properties().PropertyAsString("iotype") : dmType.Name(SCOPED)) + "]")
            << " DeclBy:" << fClass.Name(SCOPED)
            << std::endl;
      }
      ::G__memvar_setup((void*)dm.Offset(),                         // p
                        member_type,                                // type
                        member_indir,                               // indirection
                        dmType.IsConst(),                        // const
                        member_tagnum,                              // tagnum
                        member_typnum,                              // typenum
                        member_isstatic,                            // statictype
                        member_access,                              // accessin
                        expr.c_str(),                               // expression
                        0,                                          // define macro
                        comment                                     // comment
                        ); 
   }

}}
