/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsIURI.h"
#include "nsMaiHyperlink.h"
#include "mozilla/a11y/RemoteAccessible.h"

using namespace mozilla::a11y;

/* MaiAtkHyperlink */

#define MAI_TYPE_ATK_HYPERLINK (mai_atk_hyperlink_get_type())
#define MAI_ATK_HYPERLINK(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj), MAI_TYPE_ATK_HYPERLINK, MaiAtkHyperlink))
#define MAI_ATK_HYPERLINK_CLASS(klass)                      \
  (G_TYPE_CHECK_CLASS_CAST((klass), MAI_TYPE_ATK_HYPERLINK, \
                           MaiAtkHyperlinkClass))
#define MAI_IS_ATK_HYPERLINK(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAI_TYPE_ATK_HYPERLINK))
#define MAI_IS_ATK_HYPERLINK_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_TYPE((klass), MAI_TYPE_ATK_HYPERLINK))
#define MAI_ATK_HYPERLINK_GET_CLASS(obj)                    \
  (G_TYPE_INSTANCE_GET_CLASS((obj), MAI_TYPE_ATK_HYPERLINK, \
                             MaiAtkHyperlinkClass))

/**
 * This MaiAtkHyperlink is a thin wrapper, in the MAI namespace,
 * for AtkHyperlink
 */

struct MaiAtkHyperlink {
  AtkHyperlink parent;

  /*
   * The MaiHyperlink whose properties and features are exported via this
   * hyperlink instance.
   */
  MaiHyperlink* maiHyperlink;
};

struct MaiAtkHyperlinkClass {
  AtkHyperlinkClass parent_class;
};

GType mai_atk_hyperlink_get_type(void);

G_BEGIN_DECLS
/* callbacks for AtkHyperlink */
static void classInitCB(AtkHyperlinkClass* aClass);
static void finalizeCB(GObject* aObj);

/* callbacks for AtkHyperlink virtual functions */
static gchar* getUriCB(AtkHyperlink* aLink, gint aLinkIndex);
static AtkObject* getObjectCB(AtkHyperlink* aLink, gint aLinkIndex);
static gint getEndIndexCB(AtkHyperlink* aLink);
static gint getStartIndexCB(AtkHyperlink* aLink);
static gboolean isValidCB(AtkHyperlink* aLink);
static gint getAnchorCountCB(AtkHyperlink* aLink);
G_END_DECLS

static gpointer parent_class = nullptr;

static MaiHyperlink* GetMaiHyperlink(AtkHyperlink* aHyperlink) {
  NS_ENSURE_TRUE(MAI_IS_ATK_HYPERLINK(aHyperlink), nullptr);
  MaiHyperlink* maiHyperlink = MAI_ATK_HYPERLINK(aHyperlink)->maiHyperlink;
  NS_ENSURE_TRUE(maiHyperlink != nullptr, nullptr);
  NS_ENSURE_TRUE(maiHyperlink->GetAtkHyperlink() == aHyperlink, nullptr);
  return maiHyperlink;
}

GType mai_atk_hyperlink_get_type(void) {
  static GType type = 0;

  if (!type) {
    static const GTypeInfo tinfo = {
        sizeof(MaiAtkHyperlinkClass),
        (GBaseInitFunc) nullptr,
        (GBaseFinalizeFunc) nullptr,
        (GClassInitFunc)classInitCB,
        (GClassFinalizeFunc) nullptr,
        nullptr,                 /* class data */
        sizeof(MaiAtkHyperlink), /* instance size */
        0,                       /* nb preallocs */
        (GInstanceInitFunc) nullptr,
        nullptr /* value table */
    };

    type = g_type_register_static(ATK_TYPE_HYPERLINK, "MaiAtkHyperlink", &tinfo,
                                  GTypeFlags(0));
  }
  return type;
}

MaiHyperlink::MaiHyperlink(Accessible* aHyperLink)
    : mHyperlink(aHyperLink), mMaiAtkHyperlink(nullptr) {
  mMaiAtkHyperlink = reinterpret_cast<AtkHyperlink*>(
      g_object_new(mai_atk_hyperlink_get_type(), nullptr));
  NS_ASSERTION(mMaiAtkHyperlink, "OUT OF MEMORY");
  if (!mMaiAtkHyperlink) return;

  MAI_ATK_HYPERLINK(mMaiAtkHyperlink)->maiHyperlink = this;
}

MaiHyperlink::~MaiHyperlink() {
  if (mMaiAtkHyperlink) {
    MAI_ATK_HYPERLINK(mMaiAtkHyperlink)->maiHyperlink = nullptr;
    g_object_unref(mMaiAtkHyperlink);
  }
}

/* static functions for ATK callbacks */

void classInitCB(AtkHyperlinkClass* aClass) {
  GObjectClass* gobject_class = G_OBJECT_CLASS(aClass);

  parent_class = g_type_class_peek_parent(aClass);

  aClass->get_uri = getUriCB;
  aClass->get_object = getObjectCB;
  aClass->get_end_index = getEndIndexCB;
  aClass->get_start_index = getStartIndexCB;
  aClass->is_valid = isValidCB;
  aClass->get_n_anchors = getAnchorCountCB;

  gobject_class->finalize = finalizeCB;
}

void finalizeCB(GObject* aObj) {
  NS_ASSERTION(MAI_IS_ATK_HYPERLINK(aObj), "Invalid MaiAtkHyperlink");
  if (!MAI_IS_ATK_HYPERLINK(aObj)) return;

  MaiAtkHyperlink* maiAtkHyperlink = MAI_ATK_HYPERLINK(aObj);
  maiAtkHyperlink->maiHyperlink = nullptr;

  /* call parent finalize function */
  if (G_OBJECT_CLASS(parent_class)->finalize) {
    G_OBJECT_CLASS(parent_class)->finalize(aObj);
  }
}

gchar* getUriCB(AtkHyperlink* aLink, gint aLinkIndex) {
  MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
  if (!maiLink) {
    return nullptr;
  }

  Accessible* acc = maiLink->Acc();
  if (!acc) {
    return nullptr;
  }

  nsAutoCString cautoStr;
  nsCOMPtr<nsIURI> uri = acc->AnchorURIAt(aLinkIndex);
  if (!uri) return nullptr;

  nsresult rv = uri->GetSpec(cautoStr);
  NS_ENSURE_SUCCESS(rv, nullptr);

  return g_strdup(cautoStr.get());
}

AtkObject* getObjectCB(AtkHyperlink* aLink, gint aLinkIndex) {
  MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
  if (!maiLink) {
    return nullptr;
  }

  Accessible* acc = maiLink->Acc();
  if (!acc) {
    return nullptr;
  }

  Accessible* anchor = acc->AnchorAt(aLinkIndex);
  return anchor ? GetWrapperFor(anchor) : nullptr;
}

gint getEndIndexCB(AtkHyperlink* aLink) {
  MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
  if (!maiLink) return false;

  return static_cast<gint>(maiLink->Acc()->EndOffset());
}

gint getStartIndexCB(AtkHyperlink* aLink) {
  MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
  if (!maiLink) return -1;

  return static_cast<gint>(maiLink->Acc()->StartOffset());
}

gboolean isValidCB(AtkHyperlink* aLink) {
  MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
  if (!maiLink) return false;

  Accessible* acc = maiLink->Acc();
  if (!acc) {
    return false;
  }

  return static_cast<gboolean>(acc->IsLinkValid());
}

gint getAnchorCountCB(AtkHyperlink* aLink) {
  MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
  if (!maiLink) return -1;

  Accessible* acc = maiLink->Acc();
  if (!acc) {
    return -1;
  }

  return static_cast<gint>(acc->AnchorCount());
}