package CSS::SpriteMaker::Layout;

use strict;
use warnings;

use List::Util qw(max);

=head1 NAME

CSS::SpriteMaker::Layout - Layout interface for items placed on a 2D grid.

Allows to access coordinates of items laid out on a 2D grid.

Shouldn't be instantiated directly, but subclasses should be instantiated
instead.

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';

=head2 _layout_items

Lays out the items given their properties. These properties can be global or
about individual items, and have the form of the following hashref:

    {
        '<item_id>' : {
            width => <integer>,
            height => <integer>,
            first_pixel_x => <integer>,
            first_pixel_y => <integer>,
            ... other arbitrary properties, if any
        },
        ...
    }

This method should never be called on this class, but on a subclass. It contains
the implementation of the specific layout after all.

=cut

sub _layout_items {
    my $self = shift;
    my $rh_item_info = shift;
    die "you shouldn't be calling layout_items directly on this object, but a subclass should implement it!";
}

=head2 get_item_coord

Gets the coordinates of a specific item within the layout.

    my ($x, $y) = $Layout->get_item_coord("129");

Returns a list containing the x and the y coordinates of the specified element
respectively.

=cut

sub get_item_coord {
    my $self = shift;
    my $id = shift;

    die "finalize() was not called on this class!" if !$self->{_layout_ran};

    if (!defined $self->{items} || !defined $self->{items}{$id}) {
        warn "item id: $id doesn't appear to be part of this layout";
        return;
    }

    my $rh_coords = $self->{items}{$id};
    return ($rh_coords->{x}, $rh_coords->{y});
}

=head2 set_item_coord

Sets the coordinates of a layout item.

    # sets coordinates of item #129 to x: 100 y: 200
    $Layout->set_item_coord("129", 100, 200); 

Sets coordinates of the given element internally and returns undef.

=cut

sub set_item_coord {
    my $self = shift;
    my $id = shift;
    my $x = shift;
    my $y = shift;

    $self->{items} = {} if !defined $self->{items};

    $self->{items}{$id} = {
        x => $x,
        y => $y,
    };

    return;
}

=head2 move_items

Moves all the items in this layout by the given deltay and deltax.
    
=cut

sub move_items {
    my $self = shift;
    my ($dx, $dy) = @_;
    for my $id ($self->get_item_ids) {
        my ($x, $y) = $self->get_item_coord($id);
        $self->set_item_coord($id, $x+$dx, $y+$dy);
    }
}

=head2 delete_item

Deletes the item with the specified id from the internal list of items that
have been layed out. 

WARNING - this doesn't trigger a re-layout: will result in having a hole in
the current layout. Be aware of it.

Triggers a warning if the element with the specified id doesn't exist in
the current layout.

=cut

sub delete_item {
    my $self = shift;
    my $item_id = shift;

    if (!exists $self->{items}{$item_id}) {
        warn "the item with id \"$item_id\" you are trying to delete doesn't exist in the current layout";
        return;
    }
    delete $self->{items}{$item_id};
    return;
}

=head2 merge_with

Merges the current layout with the one specified. For a successful merge to
happen, items in the old and in the new layout must have different ids.

=cut

sub merge_with {
    my $self = shift;
    my $Layout = shift;

    my ($minx, $miny);
    for my $id ($Layout->get_item_ids()) {
        my ($x, $y) = $Layout->get_item_coord($id);

        # check that the id doesn't exist
        if (exists $self->{items}{$id}) {
            warn "the id $id already exists in the target layout!";
        }
        
        # merge
        $self->set_item_coord($id, $x, $y);
    }

    return;
}

=head2 get_item_ids

Returns the id of each item into an array.

    my @ids = $Layout->get_item_ids();

=cut

sub get_item_ids {
    my $self = shift;
    return keys %{$self->{items}};
}

=head2 width

Returns the width of the overall layout in pixels.

    my $width = $Layout->width();

=cut

sub width {
    my $self = shift;
    return $self->{width};
}

=head2 height

Returns the height of the overall layout in pixels.

    my $width = $Layout->height();

=cut

sub height {
    my $self = shift;
    return $self->{height};
}

=head2 finalize

Sets this layout as instantiated. To only be called by a subclass of this base
class once class is instantiated.

    $Layout->finalize();

Once called, some checks are performed on the layout and
warnings are issued emitted if something is wrong.

Always returns undef.

=cut

sub finalize {
    my $self = shift;
    
    for my $attribute (qw/width height/) {
        if (!defined $self->{$attribute}) {
            warn "attribute $attribute should always be set in the layout class!";
        }
    }

    $self->{_layout_ran} = 1;
    return;
}


1;