/* -----------------------------------------------------------------------------
 * std_vector.i
 * ----------------------------------------------------------------------------- */

%include <std_common.i>

// ------------------------------------------------------------------------
// std::vector
// 
// The aim of all that follows would be to integrate std::vector with 
// Tcl as much as possible, namely, to allow the user to pass and 
// be returned Tcl lists.
// const declarations are used to guess the intent of the function being
// exported; therefore, the following rationale is applied:
// 
//   -- f(std::vector< T >), f(const std::vector< T >&), f(const std::vector< T >*):
//      the parameter being read-only, either a Tcl list or a
//      previously wrapped std::vector< T > can be passed.
//   -- f(std::vector< T >&), f(std::vector< T >*):
//      the parameter must be modified; therefore, only a wrapped std::vector
//      can be passed.
//   -- std::vector< T > f():
//      the vector is returned by copy; therefore, a Tcl list of T:s 
//      is returned which is most easily used in other Tcl functions procs
//   -- std::vector< T >& f(), std::vector< T >* f(), const std::vector< T >& f(),
//      const std::vector< T >* f():
//      the vector is returned by reference; therefore, a wrapped std::vector
//      is returned
// ------------------------------------------------------------------------

%{
#include <vector>
#include <algorithm>
#include <stdexcept>
#include <string>

Tcl_Obj* SwigString_FromString(const std::string &s) {
    return Tcl_NewStringObj(s.data(), (int)s.length());
}

int Tcl_GetBoolFromObj(Tcl_Interp *interp, Tcl_Obj *o, bool *val) {
  int v;
  int res = Tcl_GetBooleanFromObj(interp, o, &v);
  if (res == TCL_OK) {
    *val = v ? true : false;
  }
  return res;  
}
 
int SwigString_AsString(Tcl_Interp *interp, Tcl_Obj *o, std::string *val) {
    int len;
    const char* temp = Tcl_GetStringFromObj(o, &len);
    if (temp == NULL)
        return TCL_ERROR;
    val->assign(temp, len);
    return TCL_OK;
}

// behaviour of this is such as the real Tcl_GetIntFromObj
template <typename Type>
int SwigInt_As(Tcl_Interp *interp, Tcl_Obj *o, Type *val) {
    int temp_val, return_val;
    return_val = Tcl_GetIntFromObj(interp, o, &temp_val);
    *val = (Type) temp_val;
    return return_val;
}

// behaviour of this is such as the real Tcl_GetDoubleFromObj
template <typename Type>
int SwigDouble_As(Tcl_Interp *interp, Tcl_Obj *o, Type *val) {
    int return_val;
    double temp_val;
    return_val = Tcl_GetDoubleFromObj(interp, o, &temp_val);
    *val = (Type) temp_val;
    return return_val;
}

%}

// exported class

namespace std {
    
    template<class T> class vector {
        %typemap(in) vector< T > (std::vector< T > *v) {
            Tcl_Obj **listobjv;
            int       nitems;
            int       i;
            T*        temp;

            if (SWIG_ConvertPtr($input, (void **) &v, \
                                $&1_descriptor, 0) == 0){
                $1 = *v;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, \
                                          &nitems, &listobjv) == TCL_ERROR)
                    return TCL_ERROR;
                $1 = std::vector< T >();
                for (i = 0; i < nitems; i++) {
                    if ((SWIG_ConvertPtr(listobjv[i],(void **) &temp,
                                         $descriptor(T *),0)) != 0) {
                        char message[] = 
                            "list of " #T " expected";
                        Tcl_SetResult(interp, message, TCL_VOLATILE);
                        return TCL_ERROR;
                    }
                    $1.push_back(*temp);
                } 
            }
        }

        %typemap(in) const vector< T >* (std::vector< T > *v, std::vector< T > w),
                     const vector< T >& (std::vector< T > *v, std::vector< T > w) {
            Tcl_Obj **listobjv;
            int       nitems;
            int       i;
            T*        temp;

            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $&1_descriptor, 0) == 0) {
                $1 = v;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    return TCL_ERROR;
                w = std::vector< T >();
                for (i = 0; i < nitems; i++) {
                    if ((SWIG_ConvertPtr(listobjv[i],(void **) &temp,
                                         $descriptor(T *),0)) != 0) {
                        char message[] = 
                            "list of " #T " expected";
                        Tcl_SetResult(interp, message, TCL_VOLATILE);
                        return TCL_ERROR;
                    }
                    w.push_back(*temp);
                } 
                $1 = &w;
            }
        }

        %typemap(out) vector< T > {
            for (unsigned int i=0; i<$1.size(); i++) {
                T* ptr = new T((($1_type &)$1)[i]);
                Tcl_ListObjAppendElement(interp, $result, \
                                         SWIG_NewInstanceObj(ptr, 
                                                             $descriptor(T *), 
                                                             0));
            }
        }

        %typecheck(SWIG_TYPECHECK_VECTOR) vector< T > {
            Tcl_Obj **listobjv;
            int       nitems;
            T*        temp;
            std::vector< T > *v;
            
            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $&1_descriptor, 0) == 0) {
                /* wrapped vector */
                $1 = 1;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    $1 = 0;
                else
                    if (nitems == 0)
                        $1 = 1;
                //check the first value to see if it is of correct type
                    else if ((SWIG_ConvertPtr(listobjv[0],
                                              (void **) &temp, 
                                              $descriptor(T *),0)) != 0)
                        $1 = 0;
                    else
                        $1 = 1;
            }
        }
        
        %typecheck(SWIG_TYPECHECK_VECTOR) const vector< T >&,
                                          const vector< T >* {
            Tcl_Obj **listobjv;
            int       nitems;
            T*         temp;
            std::vector< T > *v;

            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $1_descriptor, 0) == 0){
                /* wrapped vector */
                $1 = 1;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    $1 = 0;
                else
                    if (nitems == 0)
                        $1 = 1;
                //check the first value to see if it is of correct type
                    else if ((SWIG_ConvertPtr(listobjv[0],
                                              (void **) &temp,
                                              $descriptor(T *),0)) != 0)
                        $1 = 0;
                    else
                        $1 = 1;
            }
        }
      
      public:
        vector(unsigned int size = 0);
        vector(unsigned int size, const T& value);
        vector(const vector< T > &);

        unsigned int size() const;
        bool empty() const;
        void clear();
        %rename(push) push_back;
        void push_back(const T& x);
        %extend {
            T pop() throw (std::out_of_range) {
                if (self->size() == 0)
                    throw std::out_of_range("pop from empty vector");
                T x = self->back();
                self->pop_back();
                return x;
            }
            T& get(int i) throw (std::out_of_range) {
                int size = int(self->size());
                if (i<0) i += size;
                if (i>=0 && i<size)
                    return (*self)[i];
                else
                    throw std::out_of_range("vector index out of range");
            }
            void set(int i, const T& x) throw (std::out_of_range) {
                int size = int(self->size());
                if (i<0) i+= size;
                if (i>=0 && i<size)
                    (*self)[i] = x;
                else
                    throw std::out_of_range("vector index out of range");
            }
        }
    };


    // specializations for built-ins

    %define specialize_std_vector(T, CONVERT_FROM, CONVERT_TO)
    template<> class vector< T > {

        %typemap(in) vector< T > (std::vector< T > *v){
            Tcl_Obj **listobjv;
            int       nitems;
            int       i;
            T         temp;

            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $&1_descriptor, 0) == 0) {
                $1 = *v;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    return TCL_ERROR;					      
                $1 = std::vector< T >();
                for (i = 0; i < nitems; i++) {
                    if (CONVERT_FROM(interp, listobjv[i], &temp) == TCL_ERROR)
                        return TCL_ERROR;
                    $1.push_back(temp);
                } 
            }
        }
      
        %typemap(in) const vector< T >& (std::vector< T > *v,std::vector< T > w),
                     const vector< T >* (std::vector< T > *v,std::vector< T > w) {
            Tcl_Obj **listobjv;
            int       nitems;
            int       i;
            T         temp;

            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $1_descriptor, 0) == 0) {
                $1 = v;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    return TCL_ERROR;
                w = std::vector< T >();
                for (i = 0; i < nitems; i++) {
                    if (CONVERT_FROM(interp, listobjv[i], &temp) == TCL_ERROR)
                        return TCL_ERROR;
                    w.push_back(temp);
                } 
                $1 = &w;
            }
        }

        %typemap(out) vector< T > {
            for (unsigned int i=0; i<$1.size(); i++) {
                Tcl_ListObjAppendElement(interp, $result, \
                                         CONVERT_TO((($1_type &)$1)[i]));
            }
        }
       
        %typecheck(SWIG_TYPECHECK_VECTOR) vector< T > {
            Tcl_Obj **listobjv;
            int       nitems;
            T         temp;
            std::vector< T > *v;

            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $&1_descriptor, 0) == 0){
                /* wrapped vector */
                $1 = 1;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    $1 = 0;
                else
                    if (nitems == 0)
                        $1 = 1;
                //check the first value to see if it is of correct type
                if (CONVERT_FROM(interp, listobjv[0], &temp) == TCL_ERROR)
                    $1 = 0;
                else
                    $1 = 1;
            }
        }      

        %typecheck(SWIG_TYPECHECK_VECTOR) const vector< T >&,
	                                      const vector< T >*{
            Tcl_Obj **listobjv;
            int       nitems;
            T         temp;
            std::vector< T > *v;

            if(SWIG_ConvertPtr($input, (void **) &v, \
                               $1_descriptor, 0) == 0){
                /* wrapped vector */
                $1 = 1;
            } else {
                // It isn't a vector< T > so it should be a list of T's
                if(Tcl_ListObjGetElements(interp, $input, 
                                          &nitems, &listobjv) == TCL_ERROR)
                    $1 = 0;
                else
                    if (nitems == 0)
                        $1 = 1;
                //check the first value to see if it is of correct type
                if (CONVERT_FROM(interp, listobjv[0], &temp) == TCL_ERROR)
                    $1 = 0;
                else
                    $1 = 1;
            }
        }
        
      public:
        vector(unsigned int size = 0);
        vector(unsigned int size, const T& value);
        vector(const vector< T > &);

        unsigned int size() const;
        bool empty() const;
        void clear();
        %rename(push) push_back;
        void push_back(T x);
        %extend {
            T pop() throw (std::out_of_range) {
                if (self->size() == 0)
                    throw std::out_of_range("pop from empty vector");
                T x = self->back();
                self->pop_back();
                return x;
            }
            T get(int i) throw (std::out_of_range) {
                int size = int(self->size());
                if (i<0) i += size;
                if (i>=0 && i<size)
                    return (*self)[i];
                else
                    throw std::out_of_range("vector index out of range");
            }
            void set(int i, T x) throw (std::out_of_range) {
                int size = int(self->size());
                if (i<0) i+= size;
                if (i>=0 && i<size)
                    (*self)[i] = x;
                else
                    throw std::out_of_range("vector index out of range");
            }
        }
    };
    %enddef

    specialize_std_vector(bool, Tcl_GetBoolFromObj, Tcl_NewBooleanObj);
    specialize_std_vector(char, SwigInt_As<char>,Tcl_NewIntObj);
    specialize_std_vector(int, Tcl_GetIntFromObj,Tcl_NewIntObj);
    specialize_std_vector(short, SwigInt_As<short>, Tcl_NewIntObj);
    specialize_std_vector(long, SwigInt_As<long>, Tcl_NewIntObj);
    specialize_std_vector(unsigned char, 
                          SwigInt_As<unsigned char>, Tcl_NewIntObj);
    specialize_std_vector(unsigned int, 
                          SwigInt_As<unsigned int>, Tcl_NewIntObj);
    specialize_std_vector(unsigned short, 
                          SwigInt_As<unsigned short>, Tcl_NewIntObj);
    specialize_std_vector(unsigned long, 
                          SwigInt_As<unsigned long>, Tcl_NewIntObj);
    specialize_std_vector(double, Tcl_GetDoubleFromObj, Tcl_NewDoubleObj);
    specialize_std_vector(float, SwigDouble_As<float>, Tcl_NewDoubleObj);
    specialize_std_vector(std::string, 
                          SwigString_AsString, SwigString_FromString);

}