/*

  sshadt_assoc.c

  Author: Antti Huima <huima@ssh.fi>

  Copyright (c) 1999 SSH Communications Security, Finland
  All rights reserved.

  Created Fri Sep 24 15:28:22 1999.

  */

#include "sshincludes.h"
#include "sshadt_i.h"
#include "sshadt_assoc.h"
#include "sshadt_map.h"
#include "sshdebug.h"

#define SSH_DEBUG_MODULE "SshADTAssoc"

typedef struct {
  SshADTContainer domain;
  SshADTContainer range;
} SshADTAssociationContext;

static void hook_domain_unmap(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  SshADTHandle image = ssh_adt_map_map(a->domain, h);

  if (image != SSH_ADT_INVALID)
    {
      ssh_adt_delete(a->range, image);
    }
}

static void hook_range_unmap2(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  SshADTHandle image = ssh_adt_map_map(a->range, h);

  if (image != SSH_ADT_INVALID)
    {
      ssh_adt_map_set_map(a->domain, image, SSH_ADT_INVALID);
    }
}

static void hook_domain_unmap2(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  SshADTHandle image = ssh_adt_map_map(a->domain, h);

  if (image != SSH_ADT_INVALID)
    {
      ssh_adt_map_set_map(a->range, image, SSH_ADT_INVALID);
    }
}

static void hook_domain_detach(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  ssh_adt_map_set_map(a->domain, h, SSH_ADT_INVALID);
}

static void hook_range_detach(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  ssh_adt_map_set_map(a->range, h, SSH_ADT_INVALID);
}

static void hook_destroy(void *ctx)
{
  SshADTAssociationContext *a = ctx;
  ssh_adt_unassociate(a->domain, a->range);
}

static void hook_domain_map2(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  SshADTHandle image, revimage;
  image = ssh_adt_map_map(a->domain, h);
  if (image != SSH_ADT_INVALID)
    {
      revimage = ssh_adt_map_map(a->range, image);
      if (revimage != h)
        {
          ssh_adt_map_set_map(a->range, image, h);
        }
    }
}

static void hook_range_map2(SshADTHandle h, void *ctx)
{
  SshADTAssociationContext *a = ctx;
  SshADTHandle image, revimage;
  image = ssh_adt_map_map(a->range, h);
  if (image != SSH_ADT_INVALID)
    {
      revimage = ssh_adt_map_map(a->domain, image);
      if (revimage != h)
        {
          ssh_adt_map_set_map(a->domain, image, h);
        }
    }
}

void ssh_adt_associate_unimap(SshADTContainer domain, SshADTContainer range)
{
  SshADTAssociationContext *ctx;

  ctx = ssh_xmalloc(sizeof(*ctx));

  ctx->domain = domain;
  ctx->range  = range;

  ssh_adt_initialize_hooks(domain);
  ssh_adt_initialize_hooks(range);

  domain->hooks->unmap = hook_domain_unmap;
  domain->hooks->unmap_ctx = ctx;

  domain->hooks->detach = hook_domain_detach;
  domain->hooks->detach_ctx = ctx;

  domain->hooks->destr = hook_destroy;
  domain->hooks->destr_ctx = ctx;

  range->hooks->destr = hook_destroy;
  range->hooks->destr_ctx = ctx;
}

void ssh_adt_associate_bimap(SshADTContainer domain, SshADTContainer range)
{
  SshADTAssociationContext *ctx;

  ctx = ssh_xmalloc(sizeof(*ctx));

  ctx->domain = domain;
  ctx->range  = range;

  ssh_adt_initialize_hooks(domain);
  ssh_adt_initialize_hooks(range);

  domain->hooks->unmap = hook_domain_unmap2;
  domain->hooks->unmap_ctx = ctx;

  domain->hooks->map = hook_domain_map2;
  domain->hooks->map_ctx = ctx;

  domain->hooks->detach = hook_domain_detach;
  domain->hooks->detach_ctx = ctx;


  range->hooks->unmap = hook_range_unmap2;
  range->hooks->unmap_ctx = ctx;

  range->hooks->map = hook_range_map2;
  range->hooks->map_ctx = ctx;

  range->hooks->detach = hook_range_detach;
  range->hooks->detach_ctx = ctx;


  domain->hooks->destr = hook_destroy;
  domain->hooks->destr_ctx = ctx;

  range->hooks->destr = hook_destroy;
  range->hooks->destr_ctx = ctx;
}

void ssh_adt_unassociate(SshADTContainer c1, SshADTContainer c2)
{
  /* Delete the association context. */
  ssh_xfree(c1->hooks->destr_ctx);

  ssh_adt_uninitialize_hooks(c1);
  ssh_adt_uninitialize_hooks(c2);
}
