/* File: pencil_classifier_2d_layouter.c; Copyright and License: see below */

#include "pencil_classifier_2d_layouter.h"
#include "layout/layout_quality.h"
#include "layout/layout_relationship_iter.h"
#include "geometry/geometry_non_linear_scale.h"
#include "u8/u8_trace.h"
#include <pango/pangocairo.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void pencil_classifier_2d_layouter_init( pencil_classifier_2d_layouter_t *this_,
                                         layout_visible_set_t *layout_data,
                                         const data_profile_part_t *profile,
                                         const pencil_size_t *pencil_size,
                                         geometry_dimensions_t *default_classifier_size,
                                         const geometry_grid_t *grid,
                                         pencil_feature_layouter_t *feature_layouter )
{
    U8_TRACE_BEGIN();
    assert( NULL != layout_data );
    assert( NULL != profile );
    assert( NULL != pencil_size );
    assert( NULL != default_classifier_size );
    assert( NULL != grid );
    assert( NULL != feature_layouter );

    (*this_).layout_data = layout_data;
    (*this_).profile = profile;

    (*this_).pencil_size = pencil_size;
    (*this_).default_classifier_size = default_classifier_size;
    (*this_).grid = grid;
    (*this_).feature_layouter = feature_layouter;
    pencil_classifier_composer_init( &((*this_).classifier_composer) );

    /* get draw area */
    {
        const layout_diagram_t *const diagram_layout
            = layout_visible_set_get_diagram_ptr( (*this_).layout_data );
        (*this_).diagram_draw_area = layout_diagram_get_draw_area_const( diagram_layout );
    }

    U8_TRACE_END();
}

void pencil_classifier_2d_layouter_destroy( pencil_classifier_2d_layouter_t *this_ )
{
    U8_TRACE_BEGIN();

    pencil_classifier_composer_destroy( &((*this_).classifier_composer) );

    U8_TRACE_END();
}

/* ================================ INITIAL LAYOUT ================================ */

void pencil_classifier_2d_layouter_estimate_bounds( pencil_classifier_2d_layouter_t *this_, PangoLayout *font_layout )
{
    U8_TRACE_BEGIN();

    /* store the classifier bounds into input_data_layouter_t */
    const uint32_t count_clasfy = layout_visible_set_get_visible_classifier_count ( (*this_).layout_data );
    for ( uint32_t index = 0; index < count_clasfy; index ++ )
    {
        layout_visible_classifier_t *const classifier_layout
            = layout_visible_set_get_visible_classifier_ptr ( (*this_).layout_data, index );

        /* trace */
        {
            const data_visible_classifier_t *const visible_classifier
                = layout_visible_classifier_get_data_const( classifier_layout );
            const data_classifier_t *const classifier
                = data_visible_classifier_get_classifier_const( visible_classifier );
            U8_TRACE_INFO_STR( "classifier:", data_classifier_get_name_const( classifier ) );
        }

        /* set the bounds, space and label_box of the classifier layout */
        {
            const bool shows_contained_children = false;  /* if classifier has children, this will be updated later */
                                                          /* when calling pencil_classifier_composer_set_space_and_label */

            /* init by default size */
            {
                geometry_rectangle_t envelope;
                geometry_rectangle_init( &envelope,
                                         0.0,
                                         0.0,
                                         geometry_dimensions_get_width( (*this_).default_classifier_size ),
                                         geometry_dimensions_get_height( (*this_).default_classifier_size ) );

                pencil_classifier_composer_set_envelope_box( &((*this_).classifier_composer),
                                                             &envelope,
                                                             shows_contained_children,
                                                             (*this_).profile,
                                                             (*this_).pencil_size,
                                                             font_layout,
                                                             classifier_layout
                                                           );

                geometry_rectangle_destroy( &envelope );
            }

            /* check if inner space is big enough for contained features */
            {
                geometry_dimensions_t features_dim;
                geometry_dimensions_init_empty( &features_dim );
                pencil_feature_layouter_calculate_features_bounds( (*this_).feature_layouter,
                                                                   layout_visible_classifier_get_diagramelement_id( classifier_layout ),
                                                                   font_layout,
                                                                   &features_dim
                                                                 );

                const geometry_rectangle_t *const space_rect
                    = layout_visible_classifier_get_space_const( classifier_layout );
                const geometry_dimensions_t space_dim = geometry_rectangle_get_dimensions( space_rect );

                if ( ! geometry_dimensions_can_contain( &space_dim, &features_dim ) )
                {
                    geometry_rectangle_t new_space;
                    geometry_rectangle_copy( &new_space, space_rect );
                    const double delta_width
                        = geometry_dimensions_get_width( &features_dim ) - geometry_rectangle_get_width( space_rect );
                    const double delta_height
                        = geometry_dimensions_get_height( &features_dim ) - geometry_rectangle_get_height( space_rect );
                    geometry_rectangle_expand_4dir( &new_space,
                                                  (delta_width<0.0) ? 0.0 : 0.5*delta_width,
                                                  (delta_height<0.0) ? 0.0 : 0.5*delta_height );

                    pencil_classifier_composer_expand_space( &((*this_).classifier_composer),
                                                             &new_space,
                                                             shows_contained_children,
                                                             (*this_).profile,
                                                             (*this_).pencil_size,
                                                             font_layout,
                                                             classifier_layout
                                                           );

                    geometry_rectangle_destroy( &new_space );
                }

                geometry_dimensions_destroy( &features_dim );
            }
        }

        /* move the classifier rectangles to the target location */
        {
            const data_visible_classifier_t *const visible_classifier2
                = layout_visible_classifier_get_data_const( classifier_layout );
            const data_classifier_t *const classifier2
                = data_visible_classifier_get_classifier_const( visible_classifier2 );
            const geometry_rectangle_t *const classifier_symbol_box
                = layout_visible_classifier_get_symbol_box_const( classifier_layout );

            const double act_center_x = geometry_rectangle_get_center_x( classifier_symbol_box );
            const double act_center_y = geometry_rectangle_get_center_y( classifier_symbol_box );
            const int32_t order_x = data_classifier_get_x_order( classifier2 );
            const int32_t order_y = data_classifier_get_y_order( classifier2 );
            const geometry_non_linear_scale_t *const x_scale = geometry_grid_get_x_scale_const( (*this_).grid );
            const geometry_non_linear_scale_t *const y_scale = geometry_grid_get_y_scale_const( (*this_).grid );
            const double center_x = geometry_non_linear_scale_get_location( x_scale, order_x );
            const double center_y = geometry_non_linear_scale_get_location( y_scale, order_y );
            const geometry_offset_t offset = geometry_offset_new( center_x - act_center_x, center_y - act_center_y );
            layout_visible_classifier_shift( classifier_layout, &offset );
        }
    }

    U8_TRACE_END();
}

/* ================================ MOVE TO AVOID OVERLAPS ================================ */

void pencil_classifier_2d_layouter_move_to_avoid_overlaps ( pencil_classifier_2d_layouter_t *this_ )
{
    U8_TRACE_BEGIN();
    assert ( (unsigned int) UNIVERSAL_ARRAY_INDEX_SORTER_MAX_ARRAY_SIZE >= (unsigned int) LAYOUT_VISIBLE_SET_MAX_CLASSIFIERS );

    universal_array_index_sorter_t sorted_classifiers;
    universal_array_index_sorter_init( &sorted_classifiers );

    /* sort the classifiers by their movement-needs */
    pencil_classifier_2d_layouter_private_propose_move_processing_order ( this_, &sorted_classifiers );

    /* move the classifiers */
    layout_visible_classifier_iter_t classifier_iterator;
    layout_visible_classifier_iter_init( &classifier_iterator, (*this_).layout_data, &sorted_classifiers );
    while ( layout_visible_classifier_iter_has_next( &classifier_iterator ) )
    {
        /* initialize the already processed classifier iterator - it is needed by called methods */
        layout_visible_classifier_iter_t already_processed;
        layout_visible_classifier_iter_init_from_processed( &already_processed, &classifier_iterator );

        /* determine pointer to classifier */
        layout_visible_classifier_t *const layouted_classifier
            = layout_visible_classifier_iter_next_ptr( &classifier_iterator );

        /* trace */
        {
            const data_visible_classifier_t *const visible_classifier
                = layout_visible_classifier_get_data_const( layouted_classifier );
            const data_classifier_t *const classifier
                = data_visible_classifier_get_classifier_const( visible_classifier );
            U8_TRACE_INFO_STR( "classifier:", data_classifier_get_name_const( classifier ) );
        }

        /* declaration of list of options */
        uint32_t solution_count = 0;
        static const uint32_t SOLUTION_MAX = 6;
        geometry_offset_t solution[6];

        /* propose options of moving left/right/up/down */
        pencil_classifier_2d_layouter_private_propose_4dir_move_solutions( this_,
                                                                           layouted_classifier,
                                                                           already_processed,  /* copy */
                                                                           SOLUTION_MAX-1,
                                                                           &solution,
                                                                           &solution_count
                                                                         );
        assert( solution_count < SOLUTION_MAX );
        /* propose options of moving close at origin-area */
        pencil_classifier_2d_layouter_private_propose_anchored_solution( this_,
                                                                         layouted_classifier,
                                                                         already_processed,  /* copy */
                                                                         &(solution[solution_count])
                                                                       );
        solution_count ++;

        /* select best option */
        uint32_t index_of_best;
        if ( 1 == solution_count )
        {
            index_of_best = 0;
        }
        else
        {
            pencil_classifier_2d_layouter_private_select_move_solution( this_,
                                                                        layouted_classifier,
                                                                        &sorted_classifiers,
                                                                        solution_count,
                                                                        &solution,
                                                                        &index_of_best
                                                                      );
        }

        /* move the classifier */
        layout_visible_classifier_shift( layouted_classifier, &(solution[index_of_best]) );
        U8_TRACE_INFO_INT_INT( "classifier moved:",
                               geometry_offset_get_dx( &(solution[index_of_best]) ),
                               geometry_offset_get_dy( &(solution[index_of_best]) )
                             );

        layout_visible_classifier_iter_destroy( &already_processed );
    }
    layout_visible_classifier_iter_destroy( &classifier_iterator );
    universal_array_index_sorter_destroy( &sorted_classifiers );

    U8_TRACE_END();
}

void pencil_classifier_2d_layouter_private_propose_move_processing_order ( pencil_classifier_2d_layouter_t *this_, universal_array_index_sorter_t *out_sorted )
{
    U8_TRACE_BEGIN();
    assert ( NULL != out_sorted );
    assert ( (unsigned int) UNIVERSAL_ARRAY_INDEX_SORTER_MAX_ARRAY_SIZE >= (unsigned int) DATA_VISIBLE_SET_MAX_CLASSIFIERS );

    /* sort the classifiers by their movement-needs */
    uint32_t count_clasfy;
    count_clasfy = layout_visible_set_get_visible_classifier_count ( (*this_).layout_data );
    for ( uint32_t index = 0; index < count_clasfy; index ++ )
    {
        const layout_visible_classifier_t *const the_classifier
            = layout_visible_set_get_visible_classifier_ptr( (*this_).layout_data, index );
        const geometry_rectangle_t *const classifier_envelope_box
            = layout_visible_classifier_get_envelope_box_const( the_classifier );

        int64_t simpleness = 0;  /* the lower the number, the ealier the classifier will be processed. Unit is area(=square-length). */

        /* reduce simpleness by area outside the diagram border: the more outside diagram area, the earlier it should be moved */
        {
            geometry_rectangle_t border_intersect;
            int intersect_error2;
            intersect_error2 = geometry_rectangle_init_by_intersect( &border_intersect, classifier_envelope_box, (*this_).diagram_draw_area );
            if ( 0 != intersect_error2 )
            {
                U8_LOG_WARNING( "a rectangle to be drawn is completely outside the diagram area" );
            }

            simpleness += 16.0 * geometry_rectangle_get_area( &border_intersect );
            simpleness -= 16.0 * geometry_rectangle_get_area( classifier_envelope_box );

            geometry_rectangle_destroy( &border_intersect );
        }

        /* reduce simpleness by intersects with other rectangles: the more intersects, the earlier it should be moved */
        for ( uint32_t probe_index = 0; probe_index < count_clasfy; probe_index ++ )
        {
            layout_visible_classifier_t *probe_classifier;
            probe_classifier = layout_visible_set_get_visible_classifier_ptr( (*this_).layout_data, probe_index );
            const geometry_rectangle_t *const probe_envelope_box
                = layout_visible_classifier_get_envelope_box_const( probe_classifier );

            geometry_rectangle_t intersect;
            int intersect_error;
            intersect_error = geometry_rectangle_init_by_intersect( &intersect, classifier_envelope_box, probe_envelope_box );

            if ( 0 == intersect_error )
            {
                simpleness -= geometry_rectangle_get_area( &intersect );
            }

            geometry_rectangle_destroy( &intersect );
        }

        /* reduce simpleness by own size: the bigger the object, the earlier it should be moved */
        {
            const double default_classifier_area = geometry_dimensions_get_area( (*this_).default_classifier_size );
            const double classifier_area = geometry_rectangle_get_area( classifier_envelope_box );
            if (( default_classifier_area > 0.000000001 )&&( classifier_area > 0.000000001 ))
            {
                simpleness -= default_classifier_area * ( classifier_area / ( classifier_area + default_classifier_area ));
            }
        }

        /* increase simpleness if contained children: if embracing children later, layouting problems might get solved */
        {
            const double default_classifier_area = geometry_dimensions_get_area( (*this_).default_classifier_size );
            const uint32_t descendant_count
                = layout_visible_set_count_descendants( (*this_).layout_data, the_classifier );
            if ( descendant_count != 0 )
            {
                simpleness += default_classifier_area;
            }
        }

        const u8_error_t insert_error = universal_array_index_sorter_insert( out_sorted, index, simpleness );
        if ( U8_ERROR_NONE != insert_error )
        {
            U8_LOG_WARNING( "not all rectangles are moved" );
        }
    }

    U8_TRACE_END();
}

/*!
 *  \brief constants for directions of moving objects
 */
enum pencil_classifier_2d_layouter_private_move_enum {
    PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_NOT = 0,  /*!< only move to visible arey - nothing more */
    PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_UP_MIN = 1,  /*!< moves up the minimum distance (up means smaller y-values) */
    PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_DOWN_MIN = 2,
    PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_LEFT_MIN = 3,
    PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_RIGHT_MIN = 4,
    PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_MAX = 5,  /*!< constant defining the total number of available options */
};

void pencil_classifier_2d_layouter_private_propose_4dir_move_solutions( pencil_classifier_2d_layouter_t *this_,
                                                                        const layout_visible_classifier_t *the_classifier,
                                                                        layout_visible_classifier_iter_t already_processed,
                                                                        uint32_t solutions_max,
                                                                        geometry_offset_t (*out_solution)[],
                                                                        uint32_t *out_solution_count )
{
    U8_TRACE_BEGIN();
    assert ( NULL != the_classifier );
    assert ( NULL != out_solution );
    assert ( NULL != out_solution_count );
    assert ( 1 <= solutions_max );  /* general requirement to report at least one option */
    assert ( (unsigned int) PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_MAX <= solutions_max );  /* current implementation requires at least 5 options */

    /* get classifier to move properties */
    const geometry_rectangle_t *const classifier_envelope_box
        = layout_visible_classifier_get_envelope_box_const( the_classifier );
    double top = geometry_rectangle_get_top ( classifier_envelope_box );
    double bottom = geometry_rectangle_get_bottom ( classifier_envelope_box );
    double left = geometry_rectangle_get_left ( classifier_envelope_box );
    double right = geometry_rectangle_get_right ( classifier_envelope_box );

    /* choose distance */
    double shift_x = 0.0;
    double shift_y = 0.0;

    /* initial check of overlaps to diagram boundary */
    {
        if ( bottom > geometry_rectangle_get_bottom( (*this_).diagram_draw_area ) )
        {
            shift_y = geometry_rectangle_get_bottom( (*this_).diagram_draw_area ) - bottom;
        }
        if ( top < geometry_rectangle_get_top( (*this_).diagram_draw_area ) )
        {
            shift_y = geometry_rectangle_get_top( (*this_).diagram_draw_area ) - top;
        }
        if ( right > geometry_rectangle_get_right( (*this_).diagram_draw_area ) )
        {
            shift_x = geometry_rectangle_get_right( (*this_).diagram_draw_area ) - right;
        }
        if ( left < geometry_rectangle_get_left( (*this_).diagram_draw_area ) )
        {
            shift_x = geometry_rectangle_get_left( (*this_).diagram_draw_area ) - left;
        }
    }

    *out_solution_count = 1;
    (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_NOT] = geometry_offset_new( shift_x, shift_y );

    /* determine minimum and comfort distances between classifiers */
    const double gap = pencil_size_get_standard_object_border( (*this_).pencil_size );

    (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_UP_MIN] = geometry_offset_new( shift_x, shift_y );
    (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_DOWN_MIN] = geometry_offset_new( shift_x, shift_y );
    (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_LEFT_MIN] = geometry_offset_new( shift_x, shift_y );
    (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_RIGHT_MIN] = geometry_offset_new( shift_x, shift_y );

    /* adjust information on current rectangle */
    top += shift_y;
    bottom += shift_y;
    left += shift_x;
    right += shift_x;

    /* check overlap to already moved classifiers */
    while ( layout_visible_classifier_iter_has_next( &already_processed ) )
    {
        /* get classifier to check overlaps */
        const layout_visible_classifier_t *const the_probe
            = layout_visible_classifier_iter_next_ptr( &already_processed );
        const geometry_rectangle_t *const probe_envelope_box
            = layout_visible_classifier_get_envelope_box_const( the_probe );
        const double probe_top = geometry_rectangle_get_top ( probe_envelope_box );
        const double probe_bottom = geometry_rectangle_get_bottom ( probe_envelope_box );
        const double probe_left = geometry_rectangle_get_left ( probe_envelope_box );
        const double probe_right = geometry_rectangle_get_right ( probe_envelope_box );

        if ( probe_right < left )
        {
            /* no overlap, finished. */
        }
        else if ( probe_left > right )
        {
            /* no overlap, finished. */
        }
        else if ( probe_bottom < top )
        {
            /* no overlap, finished. */
        }
        else if ( probe_top > bottom )
        {
            /* no overlap, finished. */
        }
        else if ( layout_visible_set_is_ancestor( (*this_).layout_data, the_probe, the_classifier ) )
        {
            /* overlapping the parent is ok, finished */
        }
        else if ( layout_visible_set_is_ancestor( (*this_).layout_data, the_classifier, the_probe ) )
        {
            /* overlapping the child is ok, finished */
        }
        else
        {
            /* there is an overlap - at least when considering the comfort zone */

            double my_shift_x_left_min;
            my_shift_x_left_min = probe_left - right - gap;
            if ( my_shift_x_left_min < geometry_offset_get_dx( &((*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_LEFT_MIN]) ) )
            {
                (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_LEFT_MIN] = geometry_offset_new( my_shift_x_left_min, shift_y );
            }

            double my_shift_x_right_min;
            my_shift_x_right_min = probe_right - left + gap;
            if ( my_shift_x_right_min > geometry_offset_get_dx( &((*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_RIGHT_MIN]) ) )
            {
                (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_RIGHT_MIN] = geometry_offset_new( my_shift_x_right_min, shift_y );
            }

            double my_shift_y_up_min;
            my_shift_y_up_min = probe_top - bottom - gap;
            if ( my_shift_y_up_min < geometry_offset_get_dy( &((*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_UP_MIN]) ) )
            {
                (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_UP_MIN] = geometry_offset_new( shift_x, my_shift_y_up_min );
            }

            double my_shift_y_down_min;
            my_shift_y_down_min = probe_bottom - top + gap;
            if ( my_shift_y_down_min > geometry_offset_get_dy( &((*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_DOWN_MIN]) ) )
            {
                (*out_solution)[PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_DOWN_MIN] = geometry_offset_new( shift_x, my_shift_y_down_min );
            }

            *out_solution_count = PENCIL_CLASSIFIER_LAYOUTER_PRIVATE_MOVE_MAX;

            /* trace */
            const data_visible_classifier_t *visible_classifier_data;
            visible_classifier_data = layout_visible_classifier_get_data_const( the_probe );
            if (( visible_classifier_data != NULL ) && ( data_visible_classifier_is_valid( visible_classifier_data ) ))
            {
                const data_classifier_t *classifier_p;
                classifier_p = data_visible_classifier_get_classifier_const( visible_classifier_data );
                U8_TRACE_INFO_STR( "- overlaps:", data_classifier_get_name_const( classifier_p ) );
            }
        }
    }

    U8_TRACE_END();
}

void pencil_classifier_2d_layouter_private_propose_anchored_solution( pencil_classifier_2d_layouter_t *this_,
                                                                      const layout_visible_classifier_t *the_classifier,
                                                                      layout_visible_classifier_iter_t already_processed,
                                                                      geometry_offset_t *out_solution )
{
    U8_TRACE_BEGIN();
    assert ( NULL != the_classifier );
    assert ( NULL != out_solution );

    /* determine the space needed for the solution */
    const geometry_rectangle_t *const classifier_envelope_box
        = layout_visible_classifier_get_envelope_box_const( the_classifier );
    const double width = geometry_rectangle_get_width( classifier_envelope_box );
    const double height = geometry_rectangle_get_height( classifier_envelope_box );
    const double gap = pencil_size_get_preferred_object_distance( (*this_).pencil_size );

    /* wish a solution area */
    geometry_rectangle_t classifier_solution_area;
    geometry_rectangle_init( &classifier_solution_area,
                             geometry_rectangle_get_left( classifier_envelope_box ) - 0.5*width - gap,
                             geometry_rectangle_get_top( classifier_envelope_box ) - 0.5*height - gap,
                             2.0*width + 2.0*gap,
                             2.0*height + 2.0*gap
                           );

    /* shrink solution area to diagram_draw_area */
    geometry_rectangle_init_by_intersect( &classifier_solution_area,
                                          &classifier_solution_area,
                                          (*this_).diagram_draw_area
                                        );

    /* check overlap to already moved classifiers */
    while ( layout_visible_classifier_iter_has_next( &already_processed ) )
    {
        /* get classifier to check overlaps */
        const layout_visible_classifier_t *const the_probe
        = layout_visible_classifier_iter_next_ptr( &already_processed );

        geometry_rectangle_t probe_total_bounds;
        geometry_rectangle_init_by_bounds( &probe_total_bounds,
                                           layout_visible_classifier_get_label_box_const( the_probe ),
                                           layout_visible_classifier_get_envelope_box_const( the_probe )
                                         );

        geometry_rectangle_init_by_difference_max( &classifier_solution_area,
                                                   &classifier_solution_area,
                                                   &probe_total_bounds
                                                 );
    }

    /* reduce the biggest free/unoccupied box by gap */
    geometry_rectangle_shift ( &classifier_solution_area, gap, gap );
    geometry_rectangle_enlarge ( &classifier_solution_area, -2.0*gap, -2.0*gap );

    /* move - but not to eager - only the minumum distance */
    double solution_move_dx = 0.0;
    double solution_move_dy = 0.0;
    const bool is_x_contained
        = ( geometry_rectangle_get_left( &classifier_solution_area ) < geometry_rectangle_get_left( classifier_envelope_box ) )
        && ( geometry_rectangle_get_right( classifier_envelope_box ) < geometry_rectangle_get_right( &classifier_solution_area ) );
    const bool is_y_contained
        = ( geometry_rectangle_get_top( &classifier_solution_area ) < geometry_rectangle_get_top( classifier_envelope_box ) )
        && ( geometry_rectangle_get_bottom( classifier_envelope_box ) < geometry_rectangle_get_bottom( &classifier_solution_area ) );
    if ( is_x_contained )
    {
        solution_move_dx = 0.0;
    }
    else
    {
        const double sol_center_x = geometry_rectangle_get_center_x( &classifier_solution_area );
        const double cur_center_x = geometry_rectangle_get_center_x( classifier_envelope_box );
        solution_move_dx = ( sol_center_x < cur_center_x )
            ? geometry_rectangle_get_right( &classifier_solution_area )
            - geometry_rectangle_get_right( classifier_envelope_box )
            : geometry_rectangle_get_left( &classifier_solution_area )
            - geometry_rectangle_get_left( classifier_envelope_box );
        geometry_rectangle_trace( &classifier_solution_area );
        geometry_rectangle_trace( classifier_envelope_box );
    }
    if ( is_y_contained )
    {
        solution_move_dy = 0.0;
    }
    else
    {
        const double sol_center_y = geometry_rectangle_get_center_y( &classifier_solution_area );
        const double cur_center_y = geometry_rectangle_get_center_y( classifier_envelope_box );
        solution_move_dy = ( sol_center_y < cur_center_y )
            ? geometry_rectangle_get_bottom( &classifier_solution_area )
            - geometry_rectangle_get_bottom( classifier_envelope_box )
            : geometry_rectangle_get_top( &classifier_solution_area )
            - geometry_rectangle_get_top( classifier_envelope_box );
    }
    *out_solution = geometry_offset_new( solution_move_dx, solution_move_dy );

    /* trace */
    const data_visible_classifier_t *visible_classifier;
    visible_classifier = layout_visible_classifier_get_data_const( the_classifier );
    const data_classifier_t *classifier;
    classifier = data_visible_classifier_get_classifier_const( visible_classifier );
    U8_TRACE_INFO_STR( "classifier:", data_classifier_get_name_const( classifier ) );

    U8_TRACE_END();
}

void pencil_classifier_2d_layouter_private_select_move_solution( pencil_classifier_2d_layouter_t *this_,
                                                                 const layout_visible_classifier_t *the_classifier,
                                                                 const universal_array_index_sorter_t *sorted,
                                                                 uint32_t solution_count,
                                                                 geometry_offset_t (*solution)[],
                                                                 uint32_t *out_index_of_best )
{
    U8_TRACE_BEGIN();
    assert ( NULL != sorted );
    assert ( NULL != the_classifier );
    assert ( NULL != solution );
    assert ( NULL != out_index_of_best );
    assert ( 1 <= solution_count );

    /* define potential solution and rating */
    uint32_t index_of_best = 0;  /* in case of doubts, take the first solution */
    double debts_of_best = DBL_MAX;

    /* check all solutions */
    for ( uint32_t solution_index = 0; solution_index < solution_count; solution_index ++ )
    {
        /* calculate the solution classifier */
        layout_visible_classifier_t moved_solution;
        layout_visible_classifier_copy( &moved_solution, the_classifier );
        layout_visible_classifier_shift( &moved_solution, &((*solution)[solution_index]) );

        /* evalute the debts of this solution */
        double debts_of_current = 0.0;

        const layout_diagram_t *const diagram_layout
            = layout_visible_set_get_diagram_ptr( (*this_).layout_data );

        const layout_quality_t quality = layout_quality_new( (*this_).pencil_size );
        debts_of_current += layout_quality_debts_class_diag( &quality, &moved_solution, &((*solution)[solution_index]), diagram_layout );

        /* check overlap to other classifiers */
        bool self_passed = false;
        layout_visible_classifier_iter_t classifer_iterator;
        layout_visible_classifier_iter_init( &classifer_iterator, (*this_).layout_data, sorted );
        while( layout_visible_classifier_iter_has_next( &classifer_iterator ) )
        {
            /* get classifier to check overlaps */
            layout_visible_classifier_t *const the_probe
                = layout_visible_classifier_iter_next_ptr( &classifer_iterator );

            if ( the_probe != the_classifier )  /* skip self */
            {
                /* already processed classifiers have 4x higher severity because these do not move anymore */
                const double severity = self_passed ? 0.25 : 1.0;

                debts_of_current += severity * layout_quality_debts_class_class( &quality, &moved_solution, the_probe, (*this_).layout_data );
            }
            else
            {
                self_passed = true;
            }
        }
        layout_visible_classifier_iter_destroy( &classifer_iterator );

        /* finish evaluating this solution */
        layout_visible_classifier_destroy( &moved_solution );
        U8_TRACE_INFO_FLT( "classifier solution debts", debts_of_current );
        if ( debts_of_current < debts_of_best )
        {
            debts_of_best = debts_of_current;
            index_of_best = solution_index;
        }
    }

    *out_index_of_best = index_of_best;

    U8_TRACE_END();
}

/* ================================ EMBRACE CHILDREN STEP BY STEP ================================ */

void pencil_classifier_2d_layouter_embrace_children( pencil_classifier_2d_layouter_t *this_, PangoLayout *font_layout )
{
    U8_TRACE_BEGIN();
    assert( (unsigned int) UNIVERSAL_ARRAY_INDEX_SORTER_MAX_ARRAY_SIZE >= (unsigned int) LAYOUT_VISIBLE_SET_MAX_RELATIONSHIPS );

    universal_array_index_sorter_t sorted_relationships;
    universal_array_index_sorter_init( &sorted_relationships );

    /* sort the relationships by their number of descendants */
    pencil_classifier_2d_layouter_private_propose_embracing_order ( this_, &sorted_relationships );

    /* init the set of classifiers that has embraced children */
    data_small_set_t has_embraced_children;
    data_small_set_init( &has_embraced_children );

    /* move the classifiers */
    layout_relationship_iter_t relationship_iterator;
    layout_relationship_iter_init( &relationship_iterator, (*this_).layout_data, &sorted_relationships );
    while( layout_relationship_iter_has_next( &relationship_iterator ) )
    {
        layout_relationship_t *const the_relationship
            = layout_relationship_iter_next_ptr( &relationship_iterator );
        assert ( the_relationship != NULL );
        const data_relationship_t *const rel_data = layout_relationship_get_data_const ( the_relationship );
        assert ( rel_data != NULL );
        const data_id_t rel_from_id = data_relationship_get_from_classifier_data_id ( rel_data );

        const pencil_error_t failure
            = pencil_classifier_2d_layouter_private_try_embrace_child( this_,
                                                                       the_relationship,
                                                                       ! data_small_set_contains( &has_embraced_children, rel_from_id ),
                                                                       font_layout
                                                                     );
        if ( failure == PENCIL_ERROR_NONE )
        {
            /* only in case of success, children are counted as embraced */
            data_small_set_add_obj( &has_embraced_children, rel_from_id );
        }
    }

    data_small_set_destroy( &has_embraced_children );

    layout_relationship_iter_destroy( &relationship_iterator );
    universal_array_index_sorter_destroy( &sorted_relationships );

    U8_TRACE_END();
}

void pencil_classifier_2d_layouter_private_propose_embracing_order ( pencil_classifier_2d_layouter_t *this_, universal_array_index_sorter_t *out_sorted )
{
    U8_TRACE_BEGIN();
    assert( NULL != out_sorted );

    const uint32_t rel_count = layout_visible_set_get_relationship_count( (*this_).layout_data );
    for ( uint32_t rel_idx = 0; rel_idx < rel_count; rel_idx ++ )
    {
        const layout_relationship_t *const the_relationship
            = layout_visible_set_get_relationship_ptr( (*this_).layout_data, rel_idx );

        /* count the descendants */
        const layout_visible_classifier_t *const from_classifier
            = layout_relationship_get_from_classifier_ptr( the_relationship );
        const uint32_t from_descendant_count
            = layout_visible_set_count_descendants( (*this_).layout_data, from_classifier );

        /* sort it into the array by the number of decendants: */
        /* the less descendants the earlier it shall be processed. */
        const u8_error_t err = universal_array_index_sorter_insert( out_sorted, rel_idx, (double)from_descendant_count );
        if ( U8_ERROR_NONE != err )
        {
            U8_LOG_ERROR ( "universal_array_index_sorter_t list is full." );
        }
    }

    U8_TRACE_END();
}

pencil_error_t pencil_classifier_2d_layouter_private_try_embrace_child( pencil_classifier_2d_layouter_t *this_,
                                                                        layout_relationship_t *the_relationship,
                                                                        bool move,
                                                                        PangoLayout *font_layout )
{
    U8_TRACE_BEGIN();
    assert( NULL != the_relationship );
    pencil_error_t result_err = PENCIL_ERROR_OUT_OF_BOUNDS;

    const data_relationship_type_t the_type
        = data_relationship_get_main_type ( layout_relationship_get_data_const( the_relationship ));

    if ( DATA_RELATIONSHIP_TYPE_UML_CONTAINMENT == the_type )
    {
        layout_visible_classifier_t *const from_classifier
            = layout_relationship_get_from_classifier_ptr( the_relationship );
        const layout_visible_classifier_t *const to_classifier
            = layout_relationship_get_to_classifier_ptr( the_relationship );
        if ( from_classifier != to_classifier )
        {
            layout_visible_classifier_t probe_parent_layout;
            layout_visible_classifier_copy( &probe_parent_layout, from_classifier );
            const geometry_rectangle_t * parent_space
                = layout_visible_classifier_get_space_const( &probe_parent_layout );
            const geometry_rectangle_t child_envelope = layout_visible_classifier_get_envelope_box( to_classifier );
            geometry_rectangle_t probe_space;
            if ( move )
            {
                geometry_rectangle_copy( &probe_space, &child_envelope );
            }
            else
            {
                geometry_rectangle_init_by_bounds( &probe_space, parent_space, &child_envelope );
            }

            pencil_classifier_composer_expand_space( &((*this_).classifier_composer),
                                                     &probe_space,
                                                     true,  /* = shows_contained_children */
                                                     (*this_).profile,
                                                     (*this_).pencil_size,
                                                     font_layout,
                                                     &probe_parent_layout
                                                   );

            const geometry_rectangle_t probe_parent_envelope
                = layout_visible_classifier_get_envelope_box( &probe_parent_layout );

            /* check what else would be embraced */
            bool illegal_overlap = false;
            const uint32_t count_clasfy
                = layout_visible_set_get_visible_classifier_count ( (*this_).layout_data );
            for ( uint32_t c_index = 0; c_index < count_clasfy; c_index ++ )
            {
                layout_visible_classifier_t *probe_classifier;
                probe_classifier = layout_visible_set_get_visible_classifier_ptr( (*this_).layout_data, c_index );

                if (( probe_classifier != from_classifier )&&( probe_classifier != to_classifier ))
                {
                    if ( layout_visible_set_is_ancestor( (*this_).layout_data, from_classifier, probe_classifier ) )
                    {
                        /* it is ok to embrace also other children, no illegal_overlap */
                    }
                    else if ( layout_visible_set_is_ancestor( (*this_).layout_data, probe_classifier, from_classifier ) )
                    {
                        /* it is ok if parent is already contained in grand-parent classifier, no illegal_overlap */
                    }
                    else
                    {
                        const geometry_rectangle_t *const current_envelope_box
                            = layout_visible_classifier_get_envelope_box_const ( probe_classifier );
                        illegal_overlap |= geometry_rectangle_is_intersecting( current_envelope_box, &probe_parent_envelope );
                    }
                }
            }
            /* check overlap to diagram boundary */
            if ( ! geometry_rectangle_is_containing ( (*this_).diagram_draw_area, &probe_parent_envelope ) )
            {
                illegal_overlap = true;
            }

            /* cancel or commit */
            if ( ! illegal_overlap )
            {
                /* trace */
                {
                    const data_visible_classifier_t *const visible_classifier
                        = layout_visible_classifier_get_data_const( &probe_parent_layout );
                    const data_classifier_t *const classifier
                        = data_visible_classifier_get_classifier_const( visible_classifier );
                    U8_TRACE_INFO_STR( "parent classifier:", data_classifier_get_name_const( classifier ) );
                }

                layout_visible_classifier_replacemove( from_classifier, &probe_parent_layout );
                result_err = PENCIL_ERROR_NONE;
            }
            else
            {
                layout_visible_classifier_destroy( &probe_parent_layout );
            }

            /* cleanup */
            geometry_rectangle_destroy( &probe_space );
        }
        else
        {
            U8_TRACE_INFO( "Classifier contains itself" );
        }
    }
    /* else this is not a parent child relationship */

    U8_TRACE_END_ERR( result_err );
    return result_err;
}

/* ================================ EMBRACE CHILDREN COMMON ================================ */

void pencil_classifier_2d_layouter_hide_relations_of_embraced_children( pencil_classifier_2d_layouter_t *this_ )
{
    U8_TRACE_BEGIN();

    /* search containment relations */
    const uint32_t rel_count = layout_visible_set_get_relationship_count( (*this_).layout_data );
    for ( uint32_t rel_idx = 0; rel_idx < rel_count; rel_idx ++ )
    {
        const layout_relationship_t *const the_relationship
            = layout_visible_set_get_relationship_const( (*this_).layout_data, rel_idx );
        const data_relationship_t *const the_rel_data
            = layout_relationship_get_data_const( the_relationship );

        const data_relationship_type_t the_type = data_relationship_get_main_type ( the_rel_data );
        const pencil_visibility_t visibility = layout_relationship_get_visibility( the_relationship );

        if (( DATA_RELATIONSHIP_TYPE_UML_CONTAINMENT == the_type )
            && (( PENCIL_VISIBILITY_SHOW == visibility )||(PENCIL_VISIBILITY_GRAY_OUT == visibility) ))
        {
            const layout_visible_classifier_t *const from_classifier
                = layout_relationship_get_from_classifier_ptr( the_relationship );
            const layout_visible_classifier_t *const to_classifier
                = layout_relationship_get_to_classifier_ptr( the_relationship );
            if ( from_classifier != to_classifier )
            {
                const geometry_rectangle_t *const parent_space
                    = layout_visible_classifier_get_space_const ( from_classifier );
                const geometry_rectangle_t *const child_symbol_box
                    = layout_visible_classifier_get_symbol_box_const ( to_classifier );

                /* hide if parent embraced child(symbol) completely */
                if ( geometry_rectangle_is_containing( parent_space, child_symbol_box ) )
                {
                    layout_visible_set_set_relationship_visibility( (*this_).layout_data, rel_idx, PENCIL_VISIBILITY_IMPLICIT );
                    U8_TRACE_INFO( "Containment relation is PENCIL_VISIBILITY_IMPLICIT" );
                }
            }
        }
    }

    U8_TRACE_END();
}

/* ================================ EMBRACE AND MOVE CHILDREN TOGETHER ================================ */

void pencil_classifier_2d_layouter_move_and_embrace_children( pencil_classifier_2d_layouter_t *this_, PangoLayout *font_layout )
{
    U8_TRACE_BEGIN();
    assert( (unsigned int) UNIVERSAL_ARRAY_INDEX_SORTER_MAX_ARRAY_SIZE >= (unsigned int) LAYOUT_VISIBLE_SET_MAX_CLASSIFIERS );

    const double TAKE_RATIO = (1.0/3.0);
    const double LEAVE_RATIO = (1.0-TAKE_RATIO);
    /* const double gap = pencil_size_get_preferred_object_distance( (*this_).pencil_size ); */

    universal_array_index_sorter_t sorted_classifiers;
    universal_array_index_sorter_init( &sorted_classifiers );

    /* sort the classifiers by their need to move and to embrace */
    pencil_classifier_2d_layouter_private_propose_move_embrace_order ( this_, &sorted_classifiers );

    /* small-move and embrace the child classifiers */
    layout_visible_classifier_iter_t classifer_iterator;
    layout_visible_classifier_iter_init( &classifer_iterator, (*this_).layout_data, &sorted_classifiers );
    while( layout_visible_classifier_iter_has_next( &classifer_iterator ) )
    {
        layout_visible_classifier_t *const the_classifier
            = layout_visible_classifier_iter_next_ptr( &classifer_iterator );

        /* only if the classifier has children */
        const uint32_t child_count = layout_visible_set_count_descendants( (*this_).layout_data, the_classifier );
        if ( child_count > 0 )
        {
            /* trace */
            {
                const data_visible_classifier_t *const visible_classifier
                    = layout_visible_classifier_get_data_const( the_classifier );
                const data_classifier_t *const classifier
                    = data_visible_classifier_get_classifier_const( visible_classifier );
                U8_TRACE_INFO_STR( "parent classifier:", data_classifier_get_name_const( classifier ) );
            }

            /* get envelope rectangle of all children */
            const geometry_rectangle_t children_envelope
                = pencil_classifier_2d_layouter_private_calc_descendant_envelope( this_, the_classifier );

            /* determine outer space around children envelope rectangle */
            const geometry_rectangle_t outer_space
                = pencil_classifier_2d_layouter_private_calc_outer_space( this_, &children_envelope, the_classifier );

            /* place the children into the (probe-)parent */
            layout_visible_classifier_t probe_parent_layout;
            layout_visible_classifier_copy( &probe_parent_layout, the_classifier );
            pencil_classifier_composer_expand_space( &((*this_).classifier_composer),
                                                     &children_envelope,
                                                     true,  /* = shows_contained_children */
                                                     (*this_).profile,
                                                     (*this_).pencil_size,
                                                     font_layout,
                                                     &probe_parent_layout
                                                   );
            const geometry_rectangle_t probe_parent_envelope
                = layout_visible_classifier_get_envelope_box( &probe_parent_layout );

            /* check if parent fits into into outer_space */
            const double outer_border_x
                = (geometry_rectangle_get_width( &outer_space ) - geometry_rectangle_get_width(&probe_parent_envelope))/2.0;
            const double outer_border_y
                = (geometry_rectangle_get_height( &outer_space ) - geometry_rectangle_get_height(&probe_parent_envelope))/2.0;
            if (( outer_border_x > 0.0 )&&( outer_border_y > 0.0 ))
            {
                /* prepare to move+expand the parent */
                geometry_rectangle_t new_envelope;
                geometry_rectangle_copy( &new_envelope, &outer_space );
                geometry_rectangle_shift( &new_envelope, (LEAVE_RATIO*outer_border_x), (LEAVE_RATIO*outer_border_y) );
                geometry_rectangle_enlarge( &new_envelope, -2.0*(LEAVE_RATIO*outer_border_x), -2.0*(LEAVE_RATIO*outer_border_y) );

                /* move+expand the parent */
                pencil_classifier_composer_set_envelope_box( &((*this_).classifier_composer),
                                                             &new_envelope,
                                                             true,  /* = shows_contained_children */
                                                             (*this_).profile,
                                                             (*this_).pencil_size,
                                                             font_layout,
                                                             the_classifier
                                                           );

                /* cleanup move+expand the parent */
                geometry_rectangle_destroy( &new_envelope );

                /* determine the descendants move deltas */
                const geometry_rectangle_t *const parent_new_space = layout_visible_classifier_get_space_const( the_classifier );
                const double descendant_add_dx = geometry_rectangle_get_center_x( parent_new_space ) - geometry_rectangle_get_center_x( &children_envelope );
                const double descendant_add_dy = geometry_rectangle_get_center_y( parent_new_space ) - geometry_rectangle_get_center_y( &children_envelope );
                const geometry_offset_t offset = geometry_offset_new( descendant_add_dx, descendant_add_dy );

                /* move the descendants */
                pencil_classifier_2d_layouter_private_move_descendants( this_, the_classifier, &offset );
            }

            /* cleanup */
            layout_visible_classifier_destroy( &probe_parent_layout );
        }
    }

    layout_visible_classifier_iter_destroy( &classifer_iterator );
    universal_array_index_sorter_destroy( &sorted_classifiers );

    U8_TRACE_END();
}

void pencil_classifier_2d_layouter_private_propose_move_embrace_order ( pencil_classifier_2d_layouter_t *this_, universal_array_index_sorter_t *out_sorted )
{
    U8_TRACE_BEGIN();
    assert ( NULL != out_sorted );
    assert ( (unsigned int) UNIVERSAL_ARRAY_INDEX_SORTER_MAX_ARRAY_SIZE >= (unsigned int) DATA_VISIBLE_SET_MAX_CLASSIFIERS );

    /* sort the classifiers by their movement-needs */
    const uint32_t count_classifiers = layout_visible_set_get_visible_classifier_count ( (*this_).layout_data );
    for ( uint32_t index = 0; index < count_classifiers; index ++ )
    {
        const layout_visible_classifier_t *const the_classifier = layout_visible_set_get_visible_classifier_ptr( (*this_).layout_data, index );

        int64_t lazy_move = 0;

        /* grand-parents must be moved after parents (becasue parents are not yet well layouted) */
        const uint32_t child_count = layout_visible_set_count_descendants ( (*this_).layout_data, the_classifier );
        lazy_move = child_count;

        const u8_error_t insert_error = universal_array_index_sorter_insert( out_sorted, index, lazy_move );
        if ( U8_ERROR_NONE != insert_error )
        {
            U8_LOG_WARNING( "not all rectangles are grown" );
        }
    }

    U8_TRACE_END();
}


/*
Copyright 2017-2026 Andreas Warnke

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
