/***********************************************************************
 * settings.c : Implementation of the settings of the map.
 ***********************************************************************/

/***********************************************************************
 *  This file is part of SpaceChart.
 *  Copyright (C) 2000 Miguel Coca <e970095@zipi.fi.upm.es>
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 ***********************************************************************/

#include <stdlib.h>
#include <string.h>
#include <gdk/gdk.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include "../include/starmap.h"
#include "../include/star_selection.h"
#include "../include/link_selection.h"
#include "../include/star_draw_rules.h"
#include "../include/link_draw_rules.h"
#include "../include/settings.h"
#include "../include/rcparser.h"

#define FNAME_LENGTH 256

typedef struct
{
        properties_set_t prop_set;
        void (*callback)(settings_t* settings, void* data);
        void *data;
} callback_t;

struct st_settings
{
        int callbacks_disabled;
        int cb_list_length;
        callback_t *callbacks;
        properties_set_t changed_properties;
        char *catalog_filename;
        star_selection_t *star_filter;
        link_selection_t *link_filter;
        double view_radius;
        int show_links;
        int show_link_labels;
        int show_star_labels;
        double labels_color[3];
        char labels_font[FNAME_LENGTH];
        distance_unit_t distance_unit;
        coords_3d_t center;
        coords_3d_t line_of_sight;
        coords_3d_t up;
        double view_distance;
        star_drawing_rules_t* star_draw_rules;
        link_drawing_rules_t* link_draw_rules;
        key_press_t keys[KEY_END_OF_LIST];
};

/* Definition of private functions. */
static int set_default_settings( settings_t *settings );
static void handle_callbacks( settings_t *settings );
static int read_config_files( rcparser_t *parser );

/* Public Functions. */

settings_t* settings_new( void )
{
        settings_t *settings;
        int i;
        
        if( (settings = (settings_t*) malloc( sizeof( struct st_settings ))) &&
            (settings->callbacks = malloc(sizeof(callback_t))) )
        {
                settings->star_draw_rules = NULL;
                settings->link_draw_rules = NULL;
                settings->catalog_filename = NULL;
                settings->callbacks_disabled = 0;
                settings->callbacks[0].prop_set = 0;
                settings->cb_list_length = 0;
                settings->catalog_filename = NULL;
                settings->changed_properties = 0;
                for( i = 0; i < KEY_END_OF_LIST; i++ )
                {
                        settings->keys[i].key = 0;
                        settings->keys[i].mods = 0;
                }
                if( !set_default_settings( settings ) )
                {
                        free( settings );
                        settings = NULL;
                }
        }
        
        return settings;
}

int settings_load_from_files( settings_t *settings )
{
        rcparser_t *parser;

        parser = rcparser_new();

        if( !read_config_files( parser ) )
        {
                emit_error_message( _("Could not load configuration files. "
                                      "Aborting") );
                exit( EXIT_FAILURE );
        }

        settings_disable_callbacks( settings );
        rcparser_apply_to_settings( parser, settings );
        settings_enable_callbacks( settings );
}

int settings_add_callback( settings_t* settings, properties_set_t prop_set,
                           void (*callback)(settings_t* settings, void* data),
                           void* data )
{
        callback_t *list = settings->callbacks;
        if( (settings->callbacks = realloc( settings->callbacks,
                                            (settings->cb_list_length+2) *
                                            (sizeof(callback_t)) )) )
        {
                settings->callbacks[settings->cb_list_length].prop_set = 
                        prop_set;
                settings->callbacks[settings->cb_list_length].callback = 
                        callback;
                settings->callbacks[settings->cb_list_length].data = data;
                settings->callbacks[settings->cb_list_length+1].prop_set = 0;
                settings->cb_list_length++;
                return settings->cb_list_length;
        }
        else
        {
                settings->callbacks = list;
                return 0;
        }
}

void settings_disable_callbacks( settings_t* settings )
{
        settings->callbacks_disabled++;
}

void settings_enable_callbacks( settings_t* settings )
{
        if( settings->callbacks_disabled )
                settings->callbacks_disabled--;
        handle_callbacks( settings );
}

void settings_set_catalog_filename( settings_t *settings, 
                                    const char *filename )
{
        if( !filename )
        {
                if( settings->catalog_filename )
                        free( settings->catalog_filename );
                settings->catalog_filename = NULL;
                return;
        }
        if( !( settings->catalog_filename && 
               !strcmp( filename, settings->catalog_filename )) )
        {
                if( settings->catalog_filename )
                        free( settings->catalog_filename );
                settings->catalog_filename = strdup( filename );
                settings->changed_properties = settings->changed_properties | 
                        PROPERTIES_CATALOG_FILENAME;
                handle_callbacks( settings );
        }
}

char *settings_get_catalog_filename( settings_t *settings )
{
        return settings->catalog_filename;
}

void settings_set_star_filter( settings_t* settings,star_selection_t* filter )
{
        settings->star_filter = filter;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_STAR_FILTER;
        handle_callbacks( settings );
}

star_selection_t* settings_get_star_filter( settings_t* settings )
{
        return settings->star_filter;
}

void settings_set_link_filter( settings_t* settings,link_selection_t* filter )
{
        settings->link_filter = filter;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_LINK_FILTER;
        handle_callbacks( settings );
}

link_selection_t* settings_get_link_filter( settings_t* settings )
{
        return settings->link_filter;
}

void settings_set_view_radius( settings_t* settings, double view_radius )
{
        settings->view_radius = view_radius;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_VIEW_RADIUS;
        handle_callbacks( settings );
}

double settings_get_view_radius( settings_t* settings )
{
        return settings->view_radius;
}

void settings_set_show_links( settings_t* settings, int value )
{
        settings->show_links = value;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_SHOW_LINKS;
        handle_callbacks( settings );
}

int settings_get_show_links( settings_t* settings )
{
        return settings->show_links;
}

void settings_set_show_link_labels( settings_t* settings, int value )
{
        settings->show_link_labels = value;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_SHOW_LINK_LABELS;
        handle_callbacks( settings );
}

int settings_get_show_link_labels( settings_t* settings )
{
        return settings->show_link_labels;
}

void settings_set_show_star_labels( settings_t* settings, int value )
{
        settings->show_star_labels = value;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_SHOW_STAR_LABELS;
        handle_callbacks( settings );
}

int settings_get_show_star_labels( settings_t* settings )
{
        return settings->show_star_labels;
}

void settings_set_labels_color( settings_t* settings, double rgb[] )
{
        settings->labels_color[0] = rgb[0];
        settings->labels_color[1] = rgb[1];
        settings->labels_color[2] = rgb[2];
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_LABELS_COLOR;
        handle_callbacks( settings );
}

void settings_get_labels_color( settings_t* settings, double rgb[] )
{
        rgb[0] = settings->labels_color[0];
        rgb[1] = settings->labels_color[1];
        rgb[2] = settings->labels_color[2];
}

void settings_set_labels_font( settings_t* settings, const char *font )
{
        strncpy( settings->labels_font, font, FNAME_LENGTH );
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_LABELS_FONT;
        handle_callbacks( settings );
}

char *settings_get_labels_font( settings_t* settings )
{
        return settings->labels_font;
}

void settings_set_distance_unit( settings_t* settings, distance_unit_t unit )
{
        settings->distance_unit = unit;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_DISTANCE_UNIT;
        handle_callbacks( settings );
}

distance_unit_t settings_get_distance_unit( settings_t* settings )
{
        return settings->distance_unit;
}

void settings_set_center( settings_t* settings, coords_3d_t* center )
{
        settings->center = *center;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_CENTER;
        handle_callbacks( settings );
}

void settings_get_center( settings_t* settings, coords_3d_t* center )
{
        *center = settings->center;
}

void settings_set_sight_params( settings_t* settings, 
                                coords_3d_t* line_of_sight,
                                coords_3d_t* up )
{
        settings->line_of_sight = *line_of_sight;
        settings->up = *up;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_SIGHT_PARAMS;
        handle_callbacks( settings );
}

void settings_get_sight_params( settings_t* settings, 
                                coords_3d_t* line_of_sight,
                                coords_3d_t* up )
{
        if( line_of_sight )
                *line_of_sight = settings->line_of_sight;
        if( up )
                *up = settings->up;
}

/* FIXME: The math knowledge should be out of these module. How? */
/* Many Thanks to Santiago Cifuentes, <tagore@apdo.com> for working out the
 * math used in this functions. */
void settings_set_sight_params_polar( settings_t* settings,
                                      double longitude, double latitude,
                                      double north )
{
        coords_3d_t los, up;

        los.x = longitude;
        los.y = latitude;
        los.z = 1;
        polar_to_cartesian(&los);

        up.x = -cos(north)*cos(longitude)*sin(latitude)+sin(north)*sin(longitude);
        up.y = -cos(north)*sin(longitude)*sin(latitude)-sin(north)*cos(longitude);
        up.z = cos(north)*cos(latitude);

        settings_set_sight_params(settings, &los, &up);
}

void settings_get_sight_params_polar( settings_t* settings,
                                      double *longitude, double *latitude,
                                      double *north )
{
        coords_3d_t los, up, north_pole;
        double product;
        
        los = settings->line_of_sight;
        up = settings->up;

        cartesian_to_polar(&los);

        *longitude = los.x;
        *latitude = los.y;
        
        north_pole.x = -cos(*longitude)*sin(*latitude);
        north_pole.y =  -sin(*longitude)*sin(*latitude);
        north_pole.z = cos(*latitude);

        product = (north_pole.x * up.x) + (north_pole.y * up.y) + 
                (north_pole.z * up.z);

        /* Should never happen... but this is floating point we are
         * talking  about. */
        if( product > 1.0 )
                product = 1.0;
        else if( product < -1.0 )
                product = -1.0;

        *north = acos(product);
}

void settings_set_star_draw_rules( settings_t* settings, 
                                   star_drawing_rules_t* rules )
{
        if( settings->star_draw_rules )
                star_drawing_rules_destroy( settings->star_draw_rules );
        settings->star_draw_rules = rules;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_STAR_DRAW_RULES;
        handle_callbacks( settings );
}

star_drawing_rules_t* settings_get_star_draw_rules( settings_t* settings )
{
        return settings->star_draw_rules;
}

void settings_find_star_draw( settings_t* settings, star_t* star, int* radius, 
                              double rgb[], int* show_name )
{
        star_drawing_rules_find( settings->star_draw_rules, star, radius, rgb, 
                                 show_name );
}

void settings_set_link_draw_rules( settings_t* settings, 
                                   link_drawing_rules_t* rules )
{
        if( settings->link_draw_rules )
                link_drawing_rules_destroy( settings->link_draw_rules );
        settings->link_draw_rules = rules;
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_LINK_FILTER;
        handle_callbacks( settings );
}

link_drawing_rules_t* settings_get_link_draw_rules( settings_t* settings )
{
        return settings->link_draw_rules;
}

void settings_find_link_draw( settings_t* settings, link_t* link, int* width, 
                              GdkLineStyle *style, double rgb[] )
{
        link_drawing_rules_find( settings->link_draw_rules, link, width,
                                 style, rgb );
}

void settings_set_keybindings( settings_t* settings, key_press_t *keys )
{
        memcpy(settings->keys, keys, sizeof(settings->keys));
        settings->changed_properties = settings->changed_properties | 
                PROPERTIES_KEYBINDINGS;
        handle_callbacks( settings );
}

void settings_get_keybindings( settings_t* settings, key_press_t *keys )
{
        memcpy(keys, settings->keys, sizeof(settings->keys));
}

void settings_destroy( settings_t* settings )
{
        free( settings->callbacks );
        star_selection_destroy( settings->star_filter );
        link_selection_destroy( settings->link_filter );
        star_drawing_rules_destroy( settings->star_draw_rules );
        link_drawing_rules_destroy( settings->link_draw_rules );
}

/* Private Functions */

void handle_callbacks( settings_t* settings )
{
        int i;
        properties_set_t changed;

        /* A callback could change the settings, thus launching another 
         * handle_callbacks(), but if the changed properties are not cleared
         * after the end of this function, the cb would be called again, in an
         * possibly infinite way. So we save the current changes and clear the
         * ones in settings.
         */

        if( !settings->callbacks_disabled )
        {
                changed = settings->changed_properties;        
                settings->changed_properties = 0;
                for( i = 0; settings->callbacks[i].prop_set; i++ )
                {
                        if( (settings->callbacks[i].prop_set & changed) )
                        {
                                settings->callbacks[i].
                                        callback(settings,
                                                 settings->callbacks[i].data);
                        }
                }
        }
}

int read_config_files( rcparser_t *parser )
{
        char filename[256];
        char buffer[256];
        struct stat status;
        int success;

        /* Read the global configuration file */
        snprintf( filename, sizeof(filename), "%s/spacechartrc", SYSCONFDIR );
        if( access( filename, R_OK ) != -1 )
                success = rcparser_parse_file( parser, filename );
        else
        {
                snprintf( buffer, sizeof(buffer), "%s: %s", filename, 
                          strerror(errno) );
                emit_error_message(buffer);
                exit( EXIT_FAILURE );
        }

        if( success == 1 )
                return 0;

        /* Now the user's config file, if it exists */
        snprintf( filename, sizeof(filename), "%s/.spacechartrc",
                  getenv("HOME") );
        if( stat( filename, &status ) == -1 )
        {
                if( errno != ENOENT )
                {
                        snprintf( buffer, sizeof(buffer), "%s: %s",
                                  filename, strerror(errno) );
                        emit_error_message(buffer);
                }
                else
                        /* The file does not exist. Don't worry. */
                        return 1;
        }
        if( (access( filename, R_OK ) != -1) &&
            ( (status.st_mode & S_IFMT) != S_IFDIR ) )
                success = rcparser_parse_file( parser, filename );

        if( success == 1 )
                return 0;
        return 1;
}

int set_default_settings( settings_t *settings )
{
#ifdef undef
        coords_3d_t line_of_sight, center, up;
        star_drawing_rules_t* star_rules;
        link_drawing_rules_t* link_rules;
        gdouble color[3] = { 1.0, 0.0, 0.0 };
        star_selection_t* s_selection;
        link_selection_t* l_selection;
        double radius;

        /* FIXME: Check for memory errors */

        /* Just in case, we disable the callbacks. */
        settings_disable_callbacks( settings );

        /* First, the default coordinates */
        radius = 5.5;

        line_of_sight.x = 1;
        line_of_sight.y = 0;
        line_of_sight.z = 0;

        center.x = 0;
        center.y = 0;
        center.z = 0;

        up.x = 0;
        up.y = 0;
        up.z = 1;

        /* Now, the star drawing rules. */
        star_rules = star_drawing_rules_new( DEFAULT_SIZE, default_color );

        s_selection = star_selection_new();
        star_selection_act_min_lum( s_selection, 100.0 );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_LUMINOSITY, 
                                5, NULL, FALSE );
        
        s_selection = star_selection_new();
        star_selection_act_min_lum( s_selection, 50.0 );
        star_selection_act_max_lum( s_selection, 100.0 );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_LUMINOSITY, 
                                4, NULL, FALSE );

        s_selection = star_selection_new();
        star_selection_act_min_lum( s_selection, 10.0 );
        star_selection_act_max_lum( s_selection, 50.0 );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_LUMINOSITY, 
                                3, NULL, FALSE );

        s_selection = star_selection_new();
        star_selection_act_min_lum( s_selection, 0.4 );
        star_selection_act_max_lum( s_selection, 10.0 );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_LUMINOSITY, 
                                2, NULL, FALSE );

        s_selection = star_selection_new();
        star_selection_act_max_lum( s_selection, 0.4 );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_LUMINOSITY, 
                                1, NULL, FALSE );

        color[0] = 1.0;
        color[1] = 1.0;
        color[2] = 1.0;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_O );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_O, 
                                0, color, FALSE );

        color[0] = 1.0;
        color[1] = 1.0;
        color[2] = 1.0;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_B );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_B, 
                                0, color, FALSE );

        color[0] = 0.90;
        color[1] = 1.0;
        color[2] = 1.0;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_A );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_A, 
                                0, color, FALSE );

        color[0] = 1.0;
        color[1] = 1.0;
        color[2] = 0.75;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_F );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_F, 
                                0, color, FALSE );

        color[0] = 1.0;
        color[1] = 1.0;
        color[2] = 0.0;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_G );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_G, 
                                0, color, FALSE );

        color[0] = 1.0;
        color[1] = 0.5;
        color[2] = 0.0;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_K );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_K, 
                                0, color, FALSE );

        color[0] = 1.0;
        color[1] = 0.0;
        color[2] = 0.0;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_M );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_M, 
                                0, color, FALSE );

        color[0] = 0.75;
        color[1] = 0.75;
        color[2] = 0.75;
        s_selection = star_selection_new();
        star_selection_act_spectrum( s_selection, SPECTRUM_WHITE_DWARF );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SPECTRUM_WD, 
                                0, color, FALSE );

        s_selection = star_selection_new();
        star_selection_act_min_lum( s_selection, 1.00 );
        star_drawing_rules_add( star_rules, s_selection, PRIORITY_SHOW_NAME, 
                                0, NULL, TRUE );

        /* Initialization of the line drawing rules */
        color[0] = 0.0;
        color[1] = 0.36;
        color[2] = 0.6;
        link_rules = link_drawing_rules_new( 0, GDK_LINE_DOUBLE_DASH, color );

        color[0] = 0.0;
        color[1] = 0.7;
        color[2] = 1.0;
        l_selection = link_selection_new();
        link_selection_act_max_length( l_selection, 1.5 );
        link_drawing_rules_add( link_rules, l_selection, 0, GDK_LINE_SOLID,
                                color );

        color[0] = 0.0;
        color[1] = 0.48;
        color[2] = 0.8;
        l_selection = link_selection_new();
        link_selection_act_min_length( l_selection, 1.5 );
        link_selection_act_max_length( l_selection, 2.0 );
        link_drawing_rules_add( link_rules, l_selection, 0, GDK_LINE_SOLID,
                                color );

        /* Now we set the selections */
        s_selection = star_selection_new(); /* Empty selection: all matches */
        l_selection = link_selection_new();
        link_selection_act_max_length( l_selection, 2.5 );

        /* Finally, write everything to the settings. */
        settings_set_center( settings, &center );
        settings_set_sight_params( settings, &line_of_sight, &up );
        settings_set_view_radius( settings, radius );
        settings_set_star_filter( settings, s_selection );
        settings_set_link_filter( settings, l_selection );
        settings_set_star_draw_rules( settings, star_rules );
        settings_set_link_draw_rules( settings, link_rules );
        settings_set_show_links( settings, TRUE );
        settings_set_show_link_labels( settings, FALSE );
        settings_set_show_star_labels( settings, TRUE );
        color[0] = 0.0;
        color[1] = 1.0;
        color[2] = 1.0;
        settings_set_labels_color( settings, color );
        settings_set_labels_font( settings, 
                                  "-misc-fixed-medium-r-semicondensed"
                                  "-*-13-*-*-*-c-*-iso8859-8" );
        settings_set_distance_unit( settings, DISTANCE_PARSECS );

        /* Enable the callbacks */
        settings_enable_callbacks( settings );

#endif
        return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1