/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1998-2016. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * %CopyrightEnd%
 *

 */
#include <stdlib.h>
#include <string.h>
#include "eidef.h"
#include "eiext.h"
#include "reg.h"
#include "eisend.h"
#include "eirecv.h"
#include "ei_connect_int.h"

static int mn_start_restore(int fd, const erlang_pid *self, erlang_pid *mnesia, const char *mntab, long *count, long *maxkey,long *maxobj)
{
  char buf[EISMALLBUF];
  char *bufp=buf;
  char tmpbuf[64];
  int index = 0;
  erlang_msg msg;
  int arity;
  int version;
  int i;
  int needlink;
  int needmsg;
  int msglen;
  
  /* set up rpc arguments */
  /* { PidFrom, { call, Mod, Fun, Args, user }}  */
  ei_encode_version(buf,&index);
  ei_encode_tuple_header(buf,&index,2);
  ei_encode_pid(buf,&index,self);               /* PidFrom */
  ei_encode_tuple_header(buf,&index,5);
  ei_encode_atom(buf,&index,"call");            /* call */
  ei_encode_atom(buf,&index,EI_MNESIA_MODULE);  /* Mod */
  ei_encode_atom(buf,&index,EI_MNESIA_RESTORE); /* Fun */
  ei_encode_list_header(buf,&index,2);          /* Args: [ table, self() ] */
  ei_encode_atom(buf,&index,mntab);
  ei_encode_pid(buf,&index,self); 
  ei_encode_empty_list(buf,&index);
  ei_encode_atom(buf,&index,"user");            /* user */

  /* make the rpc call */
  if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1;

  /* get the reply: expect link and message (not sure which will come first though) */
  needlink = needmsg = 1;
  while (needlink || needmsg) {
    /* get message */
    index = EISMALLBUF;
    while (!(i = ei_recv_internal(fd,&bufp,&index,&msg,&msglen,1,0))) index = EISMALLBUF;

    switch (i) {
    case ERL_LINK:
      /* got link */
      if (!needlink) return -1;
      needlink = 0;
      break;
      
    case ERL_SEND:
      /* got message - is it the right one? */
      if (!needmsg) return -1;
      else {
	/* expecting { rex, { size, Pid, Count, MaxKey, MaxObj }} */
	index = 0;
	if (ei_decode_version(buf,&index,&version) 
	    || ei_decode_tuple_header(buf,&index,&arity)
	    || (arity != 2) 
	    || ei_decode_atom(buf,&index,tmpbuf)
	    || strcmp(tmpbuf,"rex")
	    || ei_decode_tuple_header(buf,&index,&arity)
	    || (arity != 5) 
	    || ei_decode_atom(buf,&index,tmpbuf)
	    || strcmp(tmpbuf,EI_MNESIA_SIZE)
	    || ei_decode_pid(buf,&index,mnesia)
	    || ei_decode_long(buf,&index,count)
	    || ei_decode_long(buf,&index,maxkey)
	    || ei_decode_long(buf,&index,maxobj))
	  return -1; /* bad response from other side */
      
	/* got msg */
	needmsg = 0;
      }
      break;
      
    default:
      return -1; /* wasn't link or pid */
    }
  }
  return 0;
}

static int mn_unlink(int fd)
{
  erlang_msg msg;
  char buf[EISMALLBUF];
  char *bufp=buf;
  int index;
  int msglen;
  
  /* wait for unlink or exit */
  while (1) {
    index = EISMALLBUF;
    switch (ei_recv_internal(fd,&bufp,&index,&msg,&msglen,1,0)) {
    case 0: continue;
    case ERL_UNLINK: return 0;
    default: return -1;
    }
  }
  return 0;
}

/* decode an object and insert it into the table */
static int mn_decode_insert(ei_reg *reg, const char *msgbuf, int *index, char *key)
{
  long keylen;
  long objlen;
  long objtype;
  void *objbuf = NULL;
  long i;
  double f;

  if (ei_decode_long(msgbuf,index,&keylen) 
      || ei_decode_long(msgbuf,index,&objlen) 
      || ei_decode_long(msgbuf,index,&objtype)) 
    return -1;


  /* decode key */
  if (ei_decode_string(msgbuf,index,key)) {
    if (objbuf) free(objbuf);
    return -1;
  }

  /* finally! decode object and insert in table */
  /* don't forget to fix attributes (dirty bit for example) */

  /* FIXME: added cast but 64 bit trouble I think */
  switch ((int)objtype & EI_REG_TYPEMASK) {
  case EI_INT:
    if (ei_decode_long(msgbuf,index,&i)) return -1;
    ei_reg_setival(reg,key,i);
    break;

  case EI_FLT:
    if (ei_decode_double(msgbuf,index,&f)) return -1;
    ei_reg_setfval(reg,key,f);
    break;

  case EI_STR:
    objbuf = NULL;
    if (objlen > 0) {
      if (!(objbuf = malloc(objlen))) return -1;
      if (ei_decode_string(msgbuf,index,objbuf)) {
	free(objbuf);
	return -1;
      }
      ei_reg_setsval(reg,key,objbuf);
    }
    else {
      /* just a pointer to nothing */
      if (ei_decode_long(msgbuf,index,&i)) return -1;
      ei_reg_setsval(reg,key,NULL);
    }
    break;

  case EI_BIN:
    objbuf = NULL;
    if (objlen > 0) {
      if (!(objbuf = malloc(objlen))) return -1;
      if (ei_decode_binary(msgbuf,index,objbuf,&i)) {
	free(objbuf);
	return -1;
      }
      /* assert(i == objlen) */
      ei_reg_setpval(reg,key,objbuf,objlen);
    }
    else {
      /* just a pointer to nothing */
      if (ei_decode_long(msgbuf,index,&i)) return -1;
      ei_reg_setpval(reg,key,(void *)i,0);
    }
    break;

  default:
    /* unknown type */
    if (objbuf) free(objbuf);
    return -1;
  } /* switch */

  return 0;
}

/* help function passed to hash_foreach, to clear dirty bits */
/* use after successful restore */
static int clean_obj(const char *key, const void *p)
{
  ei_reg_obj *obj = (ei_reg_obj *)p;

  if (obj) obj->attr &= ~EI_DIRTY;

  return 0;
}

int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
{
  int i,j;
  char tag[32];
  char sbuf[EISMALLBUF];
  char *dbuf = NULL;
  char *msgbuf = NULL;
  char *keybuf = NULL;
  erlang_pid *self;
  erlang_pid mnesia = {"",0,0,0};
  erlang_msg msg;
  int index = 0;
  int len = 0;
  int msglen;
  int version = 0;
  int arity = 0;
  long count = 0;
  long maxkey = 0;
  long maxobj = 0;
  ei_cnode *ec;

  if (!reg || !mntab) return -1; /* return EI_BADARG; */

  /* make a self pid */

  if ((ec = ei_fd_to_cnode(fd)) == NULL) {
      return -1;
  }

  self = ei_self(ec);

  
  if (mn_start_restore(fd,self,&mnesia,mntab,&count,&maxkey,&maxobj)) {
    /* send exit *only* if we have pid */
    if (mnesia.node[0]) ei_send_exit(fd,self,&mnesia,"bad response from rpc start");
    return -1;
  }

  if (count <= 0) {
    ei_send_exit(fd,self,&mnesia,"nothing to do");
    return 0;
  }
  
  /* make sure receive buffer can handle largest expected message */
  len = maxkey + maxobj + 512; 
  if (len > EISMALLBUF)
    if (!(dbuf = malloc(len))) {
      ei_send_exit(fd,self,&mnesia,"cannot allocate space for incoming data");
      return -1;
    }
  msgbuf = (dbuf ? dbuf : sbuf);

  /* allocate space for largest key */
  if (!(keybuf = malloc(maxkey+1))) goto restore_failure;
  
  /* get this ball rolling */
  index = 0;
  ei_encode_version(msgbuf,&index);
  ei_encode_tuple_header(msgbuf,&index,2);
  ei_encode_atom(msgbuf,&index,"send_records");
  ei_encode_pid(msgbuf,&index,self);
  if (ei_send_encoded(fd,&mnesia,msgbuf,index)) goto restore_failure;

  /* read as much as possible, until count or EXIT */
  for (i=0; i<count; i++) {
    index = len;
    while ((j = ei_recv_internal(fd,&msgbuf,&index,&msg,&msglen,1,0)) == 0) index = len;
    if (j<0) goto restore_failure;
    
    /* decode the first part of the message */
    index = 0;
    if ((msg.msgtype != ERL_SEND) 
	|| ei_decode_version(msgbuf,&index,&version) 
	|| ei_decode_tuple_header(msgbuf,&index,&arity) 
	|| (arity != 6) 
	|| ei_decode_atom(msgbuf,&index,tag) 
	|| strcmp(tag,EI_MNESIA_RECV)) 
      goto restore_failure;

    /* decode the rest of the message and insert data into table */
    if (mn_decode_insert(reg,msgbuf,&index,keybuf)) goto restore_failure;
  }
  
  if (keybuf) free(keybuf);
  if (dbuf) free(dbuf);

  /* wait for unlink */
  if (mn_unlink(fd)) return -1;

  /* clear all the dirty bits */
  ei_hash_foreach(reg->tab,clean_obj);

  /* success */
  return 0;

restore_failure:
  ei_send_exit(fd,self,&mnesia,"restore failure");
  if (keybuf) free(keybuf);
  if (dbuf) free(dbuf);
  return -1;
}

