// Test the goin, goout, and goargout typemaps.

%module go_inout

%{
#include <string>
#include <vector>
#include <stdint.h>
%}

%inline
%{

struct MyStruct {
  std::string str;
};

struct RetStruct {
  std::string str;
};

%}

// Write a typemap that calls C++ by converting in and out of JSON.

%go_import("encoding/json", "bytes", "encoding/binary", "reflect", "unsafe")

%insert(go_header)
%{

type In json.Marshaler

%}

%typemap(gotype) MyStruct "In"

%typemap(imtype) MyStruct "string"

%typemap(goin) MyStruct
%{
	{
		b, err := $input.MarshalJSON()
		if err != nil {
			panic(err)
		}
		$result = string(b)
	}
%}

%typemap(in) MyStruct
%{
  $1.str.assign($input.p, $input.n);
%}

%typemap(gotype) RetStruct "map[string]interface{}"

%typemap(imtype) RetStruct "string"

%typemap(out,fragment="AllocateString") RetStruct
%{
  $result = Swig_AllocateString($1.str.data(), $1.str.length());
%}

%typemap(goout,fragment="CopyString") RetStruct
%{
	if err := json.Unmarshal([]byte(swigCopyString($1)), &$result); err != nil {
		panic(err)
	}
%}

%typemap(gotype) RetStruct* "*map[string]interface{}"

%typemap(imtype) RetStruct* "*string"

%typemap(out,fragment="AllocateString") RetStruct*
%{
  $result = (_gostring_*)malloc(sizeof(_gostring_));
  *$result = Swig_AllocateString($1->str.data(), $1->str.length());
%}

%typemap(goout,fragment="CopyString") RetStruct*
%{
	defer Swig_free(uintptr(unsafe.Pointer($1)))
	var rm map[string]interface{}
	if err := json.Unmarshal([]byte(swigCopyString(*$1)), &rm); err != nil {
		panic(err)
	}
	$result = &rm
%}

%inline
%{

RetStruct Same(MyStruct s)
{
  RetStruct r;
  r.str = s.str;
  return r;
}

%}

%inline
%{

struct MyArray {
  std::vector<std::string> strings;
};

void* Allocate(int n) {
  return new char[n];
}

static uint64_t getuint64(const char* s) {
  uint64_t ret = 0;
  for (int i = 0; i < 8; i++, s++) {
    ret |= static_cast<uint64_t>(*s) << i * 8;
  }
  return ret;
}

static void putuint64(std::string *s, size_t off, uint64_t v) {
  for (int i = 0; i < 8; i++) {
    (*s)[off + i] = (v >> (i * 8)) & 0xff;
  }
}

%}

%typemap(gotype) MyArray* "*[]string"

%typemap(imtype) MyArray* "*string"

// Encode the slice as a single string, with length prefixes.
%typemap(goin) MyArray*
%{
	{
		var buf bytes.Buffer
		bin := binary.LittleEndian
		var b [8]byte
		bin.PutUint64(b[:], uint64(len(*$input)))
		buf.Write(b[:])
		for _, s := range *$input {
			bin.PutUint64(b[:], uint64(len(s)))
			buf.Write(b[:])
			buf.WriteString(s)
		}
		bb := buf.Bytes()
		p := Allocate(len(bb))
		copy((*[1<<15]byte)(unsafe.Pointer(p))[:len(bb)], bb)
		var str string
		(*reflect.StringHeader)(unsafe.Pointer(&str)).Data = uintptr(unsafe.Pointer(p))
		(*reflect.StringHeader)(unsafe.Pointer(&str)).Len = len(bb)
		$result = &str
	}
%}

// Unpack the string holding the packed slice.
%typemap(in) MyArray* (MyArray t)
%{
  {
    _gostring_ *s = $input;
    const char *p = static_cast<const char *>(s->p);
    uint64_t len = getuint64(p);
    p += 8;
    t.strings.resize(len);
    for (uint64_t i = 0; i < len; i++) {
      uint64_t slen = getuint64(p);
      p += 8;
      t.strings[i].assign(p, slen);
      p += slen;
    }
    $1 = &t;
  }
%}

// Pack the vector into a string.
%typemap(argout,fragment="AllocateString") MyArray*
%{
  {
    size_t tot = 8;
    std::vector<std::string>::const_iterator p;
    for (p = $1->strings.begin(); p != $1->strings.end(); ++p) {
      tot += 8 + p->size();
    }
    std::string str;
    str.resize(tot);
    putuint64(&str, 0, $1->strings.size());
    size_t off = 8;
    for (p = $1->strings.begin(); p != $1->strings.end(); ++p) {
      putuint64(&str, off, p->size());
      off += 8;
      str.replace(off, p->size(), *p);
      off += p->size();
    }
    *$input = Swig_AllocateString(str.data(), str.size());
  }
%}

// Unpack the string into a []string.
%typemap(goargout,fragment="CopyString") MyArray*
%{
	{
		str := swigCopyString(*$input)
		bin := binary.LittleEndian
		size := bin.Uint64([]byte(str[:8]))
		str = str[8:]
		r := make([]string, size)
		for i := range r {
			len := bin.Uint64([]byte(str[:8]))
			str = str[8:]
			r[i] = str[:len]
			str = str[len:]
		}
		*$1 = r
	}
  
%}

%inline
%{
void DoubleArray(MyArray* v) {
  size_t size = v->strings.size();
  for (size_t i = 0; i < size; i++) {
    v->strings.push_back(v->strings[i] + v->strings[i]);
  }
}
%}

%inline
%{
class C1 {
 public:
  RetStruct* M() {
    RetStruct* r = new RetStruct;
    r->str = "{\"ID\":1}";
    return r;
  }
};

class C2 : public C1 {
 public:
  void M2(C1*) {}
};
%}

%typemap(gotype) (char *ps[], int cs) "[]string"

%typemap(in) (char *ps[], int cs)
%{
  {
    int i;
    _gostring_* a;

    $2 = $input.len;
    a = (_gostring_*) $input.array;
    $1 = (char **) malloc (($2 + 1) * sizeof (char *));
    for (i = 0; i < $2; i++) {
      _gostring_ *ps = &a[i];
      $1[i] = (char *) malloc(ps->n + 1);
      memcpy($1[i], ps->p, ps->n);
      $1[i][ps->n] = '\0';
    }
    $1[i] = NULL;
  }
%}

%typemap(freearg) (char *ps[], int cs)
%{
  {
    int i;

    for (i = 0; i < $2; i++) {
      free($1[i]);
    }
    free($1);
  }
%}

%inline
%{
bool Strings(char *ps[], int cs) {
  return cs == 2 && strcmp(ps[0], "1") == 0 && strcmp(ps[1], "2") == 0 && ps[2] == NULL;
}
%}
