/* gswe-aspect-data.c: Aspect related data
 *
 * Copyright © 2013  Gergely Polonkai
 *
 * SWE-GLib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * SWE-GLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
#include <math.h>
#include <glib-object.h>

#include "swe-glib-private.h"
#include "swe-glib.h"
#include "gswe-planet-data-private.h"
#include "gswe-planet-info-private.h"
#include "gswe-aspect-info.h"
#include "gswe-aspect-info-private.h"
#include "gswe-aspect-data.h"
#include "gswe-aspect-data-private.h"

/**
 * SECTION:gswe-aspect-data
 * @short_description: a structure representing an aspect between two planets
 * @title: GsweAspectData
 * @stability: Stable
 * @include: swe-glib.h
 * @see_also: #GsweAspectInfo
 *
 * #GsweAspectData is a structure that represents two planets relation to each
 * other, like their aspect and the aspect's difference from an exact aspect.
 */
G_DEFINE_BOXED_TYPE(GsweAspectData, gswe_aspect_data, (GBoxedCopyFunc)gswe_aspect_data_ref, (GBoxedFreeFunc)gswe_aspect_data_unref);

static void
gswe_aspect_data_free(GsweAspectData *aspect_data)
{
    if (aspect_data->planet1) {
        gswe_planet_data_unref(aspect_data->planet1);
    }

    if (aspect_data->planet2) {
        gswe_planet_data_unref(aspect_data->planet2);
    }

    if (aspect_data->aspect_info) {
        gswe_aspect_info_unref(aspect_data->aspect_info);
    }

    g_free(aspect_data);
}

/*
 * find_aspect:
 * @aspect_p: a pointer made with GINT_TO_POINTER(), holding the aspect ID
 * @aspect_info: a GsweAspectInfo, which will be checked against @aspect_data
 * @aspect_data: a GsweAspectData, whose planets' positions will be checked against @aspect_info
 *
 * This function is called internally by gswe_aspect_data_calculate() to check
 * if the two planets in @aspect_data are in aspect according to @aspect_info
 */
static gboolean
find_aspect(gpointer aspect_p, GsweAspectInfo *aspect_info, GsweAspectData *aspect_data)
{
    gdouble diff,
            planet_orb,
            aspect_orb;

    diff = fabs(aspect_info->size - aspect_data->distance);
    planet_orb = fmin(aspect_data->planet1->planet_info->orb, aspect_data->planet2->planet_info->orb);
    aspect_orb = fmax(1.0, planet_orb - aspect_info->orb_modifier);

    if (diff < aspect_orb) {
        aspect_data->aspect_info = gswe_aspect_info_ref(aspect_info);

        if (aspect_info->size == 0) {
            aspect_data->difference = (1 - ((360.0 - diff) / 360.0)) * 100.0;
        } else {
            aspect_data->difference = (1 - ((aspect_info->size - diff) / aspect_info->size)) * 100.0;
        }

        return TRUE;
    }

    return FALSE;
}

void
gswe_aspect_data_calculate(GsweAspectData *aspect_data)
{
    if ((aspect_data->distance = fabs(aspect_data->planet1->position - aspect_data->planet2->position)) > 180.0) {
        aspect_data->distance = 360.0 - aspect_data->distance;
    }

    if ((aspect_data->aspect_info = g_hash_table_find(gswe_aspect_info_table, (GHRFunc)find_aspect, aspect_data)) == NULL) {
        aspect_data->aspect_info = gswe_aspect_info_ref(g_hash_table_lookup(gswe_aspect_info_table, GINT_TO_POINTER(GSWE_ASPECT_NONE)));
    } else {
        gswe_aspect_info_ref(aspect_data->aspect_info);
    }
}

/**
 * gswe_aspect_data_new:
 *
 * Creates a new #GsweAspectData with reference count set to 1.
 *
 * Returns: (transfer full): a new #GsweAspectData
 */
GsweAspectData *
gswe_aspect_data_new(void)
{
    GsweAspectData *ret;

    gswe_init();

    ret = g_new0(GsweAspectData, 1);
    ret->refcount = 1;

    return ret;
}

/**
 * gswe_aspect_data_new_with_planets:
 * @planet1: (in): a #GswePlanetData
 * @planet2: (in): a #GswePlanetData
 *
 * Creates a new #GsweAspectData with a reference count of 1, and both planets
 * initially set. Also calculates the aspect between them.
 *
 * Returns: (transfer full): a new #GsweAspectData with all data set.
 */
GsweAspectData *
gswe_aspect_data_new_with_planets(GswePlanetData *planet1, GswePlanetData *planet2)
{
    GsweAspectData *ret;

    ret = gswe_aspect_data_new();
    ret->planet1 = gswe_planet_data_ref(planet1);
    ret->planet2 = gswe_planet_data_ref(planet2);

    gswe_aspect_data_calculate(ret);

    return ret;
}

/**
 * gswe_aspect_data_ref:
 * @aspect_data: a #GsweAspectData
 *
 * Increases reference count of @aspect_data.
 *
 * Returns: (transfer none): the same #GsweAspectData
 */
GsweAspectData *
gswe_aspect_data_ref(GsweAspectData *aspect_data)
{
    aspect_data->refcount++;

    return aspect_data;
}

/**
 * gswe_aspect_data_unref:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Decreases reference count on @aspect_data. If reference count reaches zero, @aspect_data is freed.
 */
void
gswe_aspect_data_unref(GsweAspectData *aspect_data)
{
    if (--aspect_data->refcount == 0) {
        gswe_aspect_data_free(aspect_data);
    }
}

/**
 * gswe_aspect_data_set_planet1:
 * @aspect_data: (in): a #GsweAspectData
 * @planet1: (in): a #GswePlanetData
 *
 * Sets @planet1 as the first planet of the aspect.
 */
void
gswe_aspect_data_set_planet1(GsweAspectData *aspect_data, GswePlanetData *planet1)
{
    if (aspect_data->planet1) {
        gswe_planet_data_unref(aspect_data->planet1);
    }

    aspect_data->planet1 = gswe_planet_data_ref(planet1);

    if (aspect_data->planet2) {
        gswe_aspect_data_calculate(aspect_data);
    }
}

/**
 * gswe_aspect_data_get_planet1:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Gets the first planet in the aspect.
 *
 * Returns: (transfer none): The #GswePlanetData associated with the first planet
 */
GswePlanetData *
gswe_aspect_data_get_planet1(GsweAspectData *aspect_data)
{
    return aspect_data->planet1;
}

/**
 * gswe_aspect_data_set_planet2:
 * @aspect_data: (in): a #GsweAspectData
 * @planet2: (in): a #GswePlanetData
 *
 * Sets @planet2 as the second planet of the aspect.
 */
void
gswe_aspect_data_set_planet2(GsweAspectData *aspect_data, GswePlanetData *planet2)
{
    if (aspect_data->planet2) {
        gswe_planet_data_unref(aspect_data->planet2);
    }

    aspect_data->planet2 = gswe_planet_data_ref(planet2);

    if (aspect_data->planet1) {
        gswe_aspect_data_calculate(aspect_data);
    }
}

/**
 * gswe_aspect_data_get_planet2:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Gets the second planet in the aspect.
 *
 * Returns: (transfer none): The #GswePlanetData associated with the second planet
 */
GswePlanetData *
gswe_aspect_data_get_planet2(GsweAspectData *aspect_data)
{
    return aspect_data->planet2;
}

/**
 * gswe_aspect_data_get_distance:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Gets the exact distance between the two planets in the aspect.
 *
 * Returns: the distance, in degrees
 */
gdouble
gswe_aspect_data_get_distance(GsweAspectData *aspect_data)
{
    return aspect_data->distance;
}

/**
 * gswe_aspect_data_get_aspect:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Gets the actual aspect between the two planets.
 *
 * Returns: the aspect ID
 */
GsweAspect
gswe_aspect_data_get_aspect(GsweAspectData *aspect_data)
{
    if (aspect_data->aspect_info) {
        return aspect_data->aspect_info->aspect;
    } else {
        return GSWE_ASPECT_NONE;
    }
}

/**
 * gswe_aspect_data_get_aspect_info:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Gets the the #GsweAspectInfo object for this aspect.
 *
 * Returns: (transfer none): a #GsweAspectInfo
 */
GsweAspectInfo *
gswe_aspect_data_get_aspect_info(GsweAspectData *aspect_data)
{
    return aspect_data->aspect_info;
}

/**
 * gswe_aspect_data_get_difference:
 * @aspect_data: (in): a #GsweAspectData
 *
 * Gets the difference between an exact aspect and this one.
 *
 * Returns: the difference in percent.
 */
gdouble
gswe_aspect_data_get_difference(GsweAspectData *aspect_data)
{
    return aspect_data->difference;
}

