// File:       rts.c++
// Version:    1.00
// Author:     (c) Miles Sabin, 1996
// Purpose:    replaces Acorn LibC++ and parts of SCL

// Change log:
//  11/10/96   v. 1.00
//   3/12/96   Bug fix: environment exit handler should have been restored
//               *before* calling rts_lib_final_hook().
//             rts_exit_handler now called through veneer which sets up
//               stack sensibly.
//  15/01/97   rts_lib_init() and rts_lib_final() now called directly, and
//               default implementations added.
//             Moved allocator registration here from <new>.
//  20/02/97   Added stream initialization/finalization.
//             Changed rts_lib_init and rts_lib_final back to weak links.
//  24/02/97   Added __pure_virtual_called().
//  25/02/97   Bug fix: forgot to initialize clog.
//   8/03/97   Moved __pure_virtual_called() to separate translation unit.
//  14/04/97   New atexit() allows proper interleaving of atexit fns and
//               static dtors.
//  18/04/98   Added definitions for calloc and realloc.
//               (by Alexander Thoukydides)

#include "rts.h"

#include "os.h"             // for os_exit(), xos_change_environment()
#include "new.h"
#include "newcasts.h"
#include <string.h>         // for memset()


// atexit

extern "C" int scl_atexit(void (*fn)());

struct atexit_link
{
  atexit_link(atexit_link* prev, void (*fn)())
    : prev_(prev),
      fn_(fn)
    {}

  atexit_link* prev_;
  void (*fn_)();
};

static atexit_link* atexit_list = 0;
static bool atexit_registered = false;

static void call_atexit_fns()
{
  while(atexit_list != 0)
  {
    // no need to delete the links here ... we're on the way out
    // and there's no point risking errors

    void (*fn)() = atexit_list->fn_;
    atexit_list = atexit_list->prev_;
    fn();
  }
}

extern "C"
int atexit(void (*fn)())
{
  if(!atexit_registered)
  {
    if(scl_atexit(call_atexit_fns) != 0)
      return 1;

    atexit_registered = true;
  }

  // CFront doesn't seem to like new(nothrow()) atexit_link(...)
  atexit_link* new_link = reinterpret_cast(atexit_link*, :: operator new(sizeof(atexit_link), nothrow()));
  if(new_link == 0)
    return 1;

  new(new_link) atexit_link(atexit_list, fn);

  atexit_list = new_link;
  return 0;
}


// abnormal termination

extern "C" void scl_abort();

static bool abnormal_termination = false;

extern "C"
void abort()
{
  abnormal_termination = true;
  scl_abort();
}


// initialization/finalization of client libraries

extern "C" void (*rts_lib_init_link)();
extern "C" void (*rts_lib_final_link)(bool /*in_exit_handler*/, bool /*abnormal_termination*/);

static bool library_initialization_done = false;
static bool library_finalization_done = false;

static void finalize_library()
{
  if(!library_finalization_done && rts_lib_final_link != 0)
  {
    library_finalization_done = true;
    rts_lib_final_link(false, abnormal_termination);
  }
}

static void initialize_library()
{
  if(!library_initialization_done && rts_lib_init_link != 0)
  {
    library_initialization_done = true;
    rts_lib_init_link();
    atexit(finalize_library);
  }
}


// initialization/finalization of standard streams

extern "C" void (*iostream_initialize_cout_link)();
extern "C" void (*iostream_initialize_cerr_link)();
extern "C" void (*iostream_initialize_clog_link)();
extern "C" void (*iostream_initialize_cin_link)();

extern "C" void (*iostream_finalize_cout_link)();
extern "C" void (*iostream_finalize_cerr_link)();
extern "C" void (*iostream_finalize_clog_link)();

static bool iostreams_initialization_done = false;
static bool iostreams_finalization_done = false;

static void finalize_iostreams()
{
  if(iostreams_initialization_done && !iostreams_finalization_done)
  {
    iostreams_finalization_done = true;

    if(iostream_finalize_clog_link != 0)
      iostream_finalize_clog_link();

    if(iostream_finalize_cerr_link != 0)
      iostream_finalize_cerr_link();

    if(iostream_finalize_cout_link != 0)
      iostream_finalize_cout_link();
  }
}

static void initialize_iostreams()
{
  if(!iostreams_initialization_done)
  {
    iostreams_initialization_done = true;

    if(iostream_initialize_cout_link != 0)
      iostream_initialize_cout_link();

    if(iostream_initialize_cerr_link != 0)
      iostream_initialize_cerr_link();

    if(iostream_initialize_clog_link != 0)
      iostream_initialize_clog_link();

    if(iostream_initialize_cin_link != 0)
      iostream_initialize_cin_link();

    atexit(finalize_iostreams);
  }
}


// static ctors/dtors

struct linkl_
{
  struct linkl_ * next;
  char (*ctor)();
  char (*dtor)();
};


struct
{
  linkl_* next;
  bool dtors_registered;
} __head;


static void do_dtor()
{
  if(!abnormal_termination)
  {
    linkl_* current = __head.next;

    if(current != 0)
    {
      __head.next = current->next;
      if(current->dtor != 0)
        current->dtor();
    }
  }
}

static void do_ctors()
{
  linkl_* prev = 0;
  linkl_* current = __head.next;
  linkl_* next;

  while(current != 0)
  {
    if(current->ctor != 0)
      current->ctor();

    next = current->next;
    current->next = prev;
    prev = current;
    current = next;

    atexit(do_dtor);
  }

  __head.next = prev;
}


// start

struct ExitHandler
{
  void (*handler)();
  void* workspace;
};

extern ExitHandler _stub_oldExitHandler;
static ExitHandler saved_exit_handler;
extern "C" void rts_exit_handler_veneer();

extern "C"
void ______main()
{
  saved_exit_handler = _stub_oldExitHandler;
  _stub_oldExitHandler.handler = rts_exit_handler_veneer;
  _stub_oldExitHandler.workspace = 0;

  initialize_library();
  initialize_iostreams();
  do_ctors();
}


// exit handler

extern int _stub_returnCode;

extern "C"
void rts_exit_handler()
{
  xos_change_environment(os_HANDLER_EXIT,
                         reinterpret_cast(void*, saved_exit_handler.handler),
                         reinterpret_cast(byte*, saved_exit_handler.workspace),
                         0, 0, 0, 0);

  if(rts_lib_final_link != 0)
    rts_lib_final_link(true, abnormal_termination);

  os_exit(0, _stub_returnCode);
}


// default allocator registration

extern "C" void* scl_malloc(size_t size);
extern "C" void  scl_free(void* p);
extern "C" void* scl_calloc(size_t n, size_t sz);
extern "C" void* scl_realloc(void* p, size_t sz);

static rts_alloc_fn_t alloc_fn = (rts_alloc_fn_t)scl_malloc;
static rts_free_fn_t free_fn   = (rts_free_fn_t)scl_free;

static rts_alloc_fn_t stack_alloc_fn = (rts_alloc_fn_t)scl_malloc;
static rts_free_fn_t stack_free_fn   = (rts_free_fn_t)scl_free;

extern "C"
char* malloc(size_t sz)
{
  return reinterpret_cast(char*, alloc_fn(sz));
}

extern "C"
void free(void* p)
{
  free_fn(p);
}

extern "C"
char* calloc(size_t n, size_t sz)
{
    if ((alloc_fn == (rts_alloc_fn_t)scl_malloc)
        && (free_fn == (rts_free_fn_t)scl_free))
    {
        return reinterpret_cast(char*, scl_calloc(n, sz));
    }
    else
    {
        size_t t = n*sz;
        char* p = reinterpret_cast(char*, alloc_fn(t));
        if (p) memset(p, 0, t);
        return p;
    }
}

extern "C"
char* realloc(void* p, size_t sz)
{
    if ((alloc_fn == (rts_alloc_fn_t)scl_malloc)
        && (free_fn == (rts_free_fn_t)scl_free))
    {
        return reinterpret_cast(char*, scl_realloc(p, sz));
    }
    else
    {
        // Do not know what the old size is
        return NULL;
    }
}

void rts_set_alloc(rts_alloc_fn_t alloc, rts_free_fn_t free)
{
  alloc_fn = alloc;
  free_fn = free;
}

void rts_set_stack_alloc(rts_alloc_fn_t alloc, rts_free_fn_t free)
{
  stack_alloc_fn = alloc;
  stack_free_fn = free;
}

void* rts_stack_alloc(size_t sz)
{
  return stack_alloc_fn(sz);
}

void rts_stack_free(void* p)
{
  stack_free_fn(p);
}
