#ifndef NO_EDITOR
#include <boost/bind.hpp>

#include "animation_preview_widget.hpp"
#include "button.hpp"
#include "foreach.hpp"
#include "formatter.hpp"
#include "pathfinding.hpp"
#include "raster.hpp"
#include "solid_map.hpp"
#include "surface_cache.hpp"
#include "texture.hpp"

namespace {
using graphics::surface;
const unsigned char RedBorder[] = {0xf9, 0x30, 0x3d};
const unsigned char BackgroundColor[] = {0x6f, 0x6d, 0x51};

bool is_pixel_border(const surface& s, int x, int y)
{
	if(x < 0 || y < 0 || x >= s->w || y >= s->h) {
		return false;
	}

	unsigned char* pixel = reinterpret_cast<unsigned char*>(s->pixels) + y*s->pitch + x*4;
	for(int n = 0; n != 3; ++n) {
		if(pixel[n] != RedBorder[n]) {
			return false;
		}
	}

	return true;
}

bool is_pixel_alpha(const surface& s, const point& p)
{
	unsigned char* pixel = reinterpret_cast<unsigned char*>(s->pixels) + p.y*s->pitch + p.x*4;
	if(pixel[3] == 0) {
		return true;
	}
	for(int n = 0; n != 3; ++n) {
		if(pixel[n] != BackgroundColor[n]) {
			return false;
		}
	}

	return true;
}

rect get_border_rect(const surface& s, int x, int y)
{
	int w = 0, h = 0;
	while(is_pixel_border(s, x + w + 1, y)) {
		++w;
	}

	while(is_pixel_border(s, x, y + h + 1) &&
	      is_pixel_border(s, x + w, y + h + 1)) {
		++h;
	}

	if(w == 0 || h == 0) {
		return rect();
	}

	return rect(x+1, y+1, w-1, h-1);
}

int path_cost_fn(const surface& s, const point& p1, const point& p2) {
    bool a1 = is_pixel_alpha(s, p1);
    bool a2 = is_pixel_alpha(s, p2);
    if(a1 && a2) return 2;
    else if(a1 ^ a2) return 1;
	return 0;
}

rect get_border_rect_heuristic_search(const surface& s, int ox, int oy, int max_cost) 
{
	int x1 = INT_MAX, y1 = INT_MAX, x2 = INT_MIN, y2 = INT_MIN;
	if(ox < 0 || oy < 0 || ox >= s->w || oy >= s->h) {
		return rect::from_coordinates(ox,oy,ox+1,oy+1);
	}
	const rect r(0, 0, s->w, s->h);

	if(is_pixel_alpha(s, point(ox, oy))) {
		return rect::from_coordinates(ox,oy,ox+1,oy+1);
	}

	typedef pathfinding::graph_node<point, int> graph_node;
	typedef graph_node::graph_node_ptr graph_node_ptr;
	typedef std::map<point, graph_node_ptr> graph_node_list;
	graph_node_list node_list;
	std::deque<graph_node_ptr> open_list;
	typedef std::pair<point, bool> reachable_node;
	std::vector<reachable_node> reachable;

	bool searching = true;
	try {
		graph_node_ptr current = graph_node_ptr(new graph_node(point(ox, oy)));
		current->set_cost(0, 0);
		current->set_on_open_list(true);
		open_list.push_back(current);
		node_list[point(ox, oy)] = current;

		while(searching && !open_list.empty()) {
			current = open_list.front(); open_list.pop_front();
			current->set_on_open_list(false);
			current->set_on_closed_list(true);
			if(current->G() <= max_cost) {
				reachable.push_back(reachable_node(current->get_node_value(), 
					is_pixel_alpha(s, current->get_node_value())));
			}
			foreach(const point& p, pathfinding::get_neighbours_from_rect(current->get_node_value(), 1, 1, r)) {
				graph_node_list::const_iterator neighbour_node = node_list.find(p);
				int g_cost = path_cost_fn(s, p, current->get_node_value()) + current->G();
				if(neighbour_node == node_list.end()) {
					graph_node_ptr new_node = graph_node_ptr(new graph_node(point(p.x, p.y)));
						new_node->set_parent(current);
						new_node->set_cost(g_cost, 0);
						new_node->set_on_open_list(true);
						node_list[p] = new_node;
						if(g_cost > max_cost) {
							new_node->set_on_closed_list(true);
						} else {
							new_node->set_on_open_list(true);
							open_list.push_back(new_node);
						}
				} else if(neighbour_node->second->on_closed_list() || neighbour_node->second->on_open_list()) {
					if(g_cost < neighbour_node->second->G()) {
						neighbour_node->second->G(g_cost);
						neighbour_node->second->set_parent(current);
					}
				} else {
					throw "Path error node on list, but not on open or closed lists";
				}
			}
		}
	} catch(...) {
		std::cerr << "get_border_rect_heuristic_search(): Caught exception" << std::endl;
	}

	foreach(const reachable_node& rn, reachable) {
		if(rn.second) {
			if(rn.first.x < x1) x1 = rn.first.x;
			if(rn.first.x > x2) x2 = rn.first.x;
			if(rn.first.y < y1) y1 = rn.first.y;
			if(rn.first.y > y2) y2 = rn.first.y;
		}
	}

	std::cerr << "CALC RECT " << x1 << "," << y1 << "," << x2 << "," << y2 << std::endl;
	//std::cerr << "PIXEL: 0x" << std::hex << int(pixel[0]) << ",0x" << int(pixel[1]) << ",0x" << int(pixel[2]) << ",0x" << int(pixel[3]) << std::endl;
	return rect::from_coordinates(x1, y1, x2, y2);
}

rect get_border_rect_around_loc(const surface& s, int ox, int oy)
{
	int x = ox, y = oy;
	std::cerr << "SEARCHING FOR BORDER AROUND " << x << "," << y << "\n";
	while(y >= 0 && !is_pixel_border(s, x, y)) {
		--y;
	}

	while(x >= 0 && is_pixel_border(s, x, y)) {
		--x;
	}

	++x;

	std::cerr << "STEPPED TO " << x << "," << y << "\n";

	if(y >= 0 && is_pixel_border(s, x, y)) {
		std::cerr << "RETURNING " << get_border_rect(s, x, y) << "\n";
		return get_border_rect(s, x, y);
	} else {
		std::cerr << "TRYING HEURISTIC SEARCH AROUND " << ox << "," << oy << std::endl;
		return get_border_rect_heuristic_search(s, ox, oy, 10);
	}
}

bool find_full_animation(const surface& s, const rect& r, int* pad, int* num_frames, int* frames_per_row)
{
	const int x = r.x() + r.w()/2;
	const int y = r.y() + r.h()/2;

	int next_x = x + r.w();
	if(next_x >= s->w) {
		std::cerr << "FAIL FIND " << next_x << " >= " << s->w << "\n";
		return false;
	}

	rect next_rect = get_border_rect_around_loc(s, next_x, y);
	std::cerr << "NEXT RECT: " << next_rect << " VS " << r << "\n";
	if(next_rect.w() != r.w() || next_rect.h() != r.h()) {
		return false;
	}

	*pad = next_rect.x() - r.x2();
	*num_frames = 2;

	std::vector<rect> rect_row;
	rect_row.push_back(r);
	rect_row.push_back(next_rect);

	std::cerr << "SETTING... " << get_border_rect_around_loc(s, next_x + r.w() + *pad, y) << " VS " << rect(next_rect.x() + next_rect.w() + *pad, y, r.w(), r.h()) << "\n";

	while(next_x + r.w() + *pad < s->w && get_border_rect_around_loc(s, next_x + r.w() + *pad, y) == rect(next_rect.x() + next_rect.w() + *pad, r.y(), r.w(), r.h())) {
			std::cerr << "ITER\n";
		*num_frames += 1;
		next_x += r.w() + *pad;
		next_rect = rect(next_rect.x() + next_rect.w() + *pad, r.y(), r.w(), r.h());
		rect_row.push_back(next_rect);
	}

	*frames_per_row = *num_frames;

	bool row_valid = true;
	while(row_valid) {
		int index = 0;
		foreach(rect& r, rect_row) {
			rect next_rect(r.x(), r.y() + r.h() + *pad, r.w(), r.h());
			std::cerr << "MATCHING: " << get_border_rect_around_loc(s, next_rect.x() + next_rect.w()/2, next_rect.y() + next_rect.h()/2) << " VS " << next_rect << "\n";
			if(next_rect.y2() >= s->h || get_border_rect_around_loc(s, next_rect.x() + next_rect.w()/2, next_rect.y() + next_rect.h()/2) != next_rect) {
				std::cerr << "MISMATCH: " << index << "/" << rect_row.size() << " -- " << get_border_rect_around_loc(s, next_rect.x() + next_rect.w()/2, next_rect.y() + next_rect.h()/2) << " VS " << next_rect << "\n";
				row_valid = false;
				break;
			}

			r = next_rect;
			++index;
		}

		if(row_valid) {
			*num_frames += *frames_per_row;
		}
	}

	return true;
}

}

namespace gui {

bool animation_preview_widget::is_animation(variant obj)
{
	return !obj.is_null() && obj["image"].is_string() && !obj["image"].as_string().empty();
}

animation_preview_widget::animation_preview_widget(const variant& v, game_logic::formula_callable* e) 
	: widget(v,e), cycle_(0), zoom_label_(NULL), pos_label_(NULL), scale_(0), 
	anchor_x_(-1), anchor_y_(-1), anchor_pad_(-1), has_motion_(false), dragging_sides_bitmap_(0), 
	moving_solid_rect_(false), anchor_solid_x_(-1), anchor_solid_y_(-1)
{
	ASSERT_LOG(get_environment() != 0, "You must specify a callable environment");

	if(v.has_key("on_rect_change")) {
		rect_handler_ = boost::bind(&animation_preview_widget::rect_handler_delegate, this, _1);
		ffl_rect_handler_ = get_environment()->create_formula(v["on_rect_change"]);
	}
	if(v.has_key("on_pad_change")) {
		pad_handler_ = boost::bind(&animation_preview_widget::pad_handler_delegate, this, _1);
		ffl_pad_handler_ = get_environment()->create_formula(v["on_pad_change"]);
	}
	if(v.has_key("on_frames_change")) {
		num_frames_handler_ = boost::bind(&animation_preview_widget::num_frames_handler_delegate, this, _1);
		ffl_num_frames_handler_ = get_environment()->create_formula(v["on_frames_change"]);
	}
	if(v.has_key("on_frames_per_row_change")) {
		frames_per_row_handler_ = boost::bind(&animation_preview_widget::frames_per_row_handler_delegate, this, _1);
		ffl_frames_per_row_handler_ = get_environment()->create_formula(v["on_frames_per_row_change"]);
	}
	if(v.has_key("on_solid_change")) {
		solid_handler_ = boost::bind(&animation_preview_widget::solid_handler_delegate, this, _1, _2);
		ffl_solid_handler_ = get_environment()->create_formula(v["on_solid_change"]);
	}
	
	try {
		set_object(v);
	} catch(type_error&) {
	} catch(frame::error&) {
	} catch(validation_failure_exception&) {
	} catch(graphics::load_image_error&) {
	}
}

animation_preview_widget::animation_preview_widget(variant obj) : cycle_(0), zoom_label_(NULL), pos_label_(NULL), scale_(0), anchor_x_(-1), anchor_y_(-1), anchor_pad_(-1), has_motion_(false), dragging_sides_bitmap_(0), moving_solid_rect_(false), anchor_solid_x_(-1), anchor_solid_y_(-1)
{
	set_environment();
	set_object(obj);
}

void animation_preview_widget::init()
{
	widgets_.clear();

	button* b = new button("+", boost::bind(&animation_preview_widget::zoom_in, this));
	b->set_loc(x() + 10, y() + height() - b->height() - 5);
	widgets_.push_back(widget_ptr(b));
	b = new button("-", boost::bind(&animation_preview_widget::zoom_out, this));
	b->set_loc(x() + 40, y() + height() - b->height() - 5);
	widgets_.push_back(widget_ptr(b));

	zoom_label_ = new label("Zoom: 100%");
	zoom_label_->set_loc(b->x() + b->width() + 10, b->y());
	widgets_.push_back(widget_ptr(zoom_label_));

	pos_label_ = new label("");
	pos_label_->set_loc(zoom_label_->x() + zoom_label_->width() + 8, zoom_label_->y());
	widgets_.push_back(widget_ptr(pos_label_));

	b = new button("Reset", boost::bind(&animation_preview_widget::reset_rect, this));
	b->set_loc(pos_label_->x() + pos_label_->width() + 58, y() + height() - b->height() - 5);
	widgets_.push_back(widget_ptr(b));
}

void animation_preview_widget::set_object(variant obj)
{
	if(obj == obj_) {
		return;
	}

	frame* f = new frame(obj);

	obj_ = obj;
	frame_.reset(f);
	cycle_ = 0;
}

void animation_preview_widget::process()
{
}

void animation_preview_widget::handle_draw() const
{
	int mousex, mousey;
	int mouse_buttons = SDL_GetMouseState(&mousex, &mousey);
	graphics::draw_rect(rect(x(),y(),width(),height()), graphics::color(0,0,0,196));
	rect image_area(x(), y(), (width()*3)/4, height() - 30);
	const graphics::texture image_texture(graphics::texture::get(obj_["image"].as_string(), graphics::texture::NO_STRIP_SPRITESHEET_ANNOTATIONS));
	if(image_texture.valid()) {
#ifndef SDL_VIDEO_OPENGL_ES
		const graphics::clip_scope clipping_scope(image_area.sdl_rect());
#endif // SDL_VIDEO_OPENGL_ES

		const bool view_locked = mouse_buttons && locked_focus_.w()*locked_focus_.h();

		rect focus_area;
		if(frame_->num_frames_per_row() == 0) {
			focus_area = rect();
		} else {
			focus_area = rect(frame_->area().x(), frame_->area().y(),
				  (frame_->area().w() + frame_->pad())*frame_->num_frames_per_row(),
				  (frame_->area().h() + frame_->pad())*(frame_->num_frames()/frame_->num_frames_per_row() + (frame_->num_frames()%frame_->num_frames_per_row() ? 1 : 0)));
		}

		if(view_locked) {
			focus_area = locked_focus_;
		} else {
			locked_focus_ = focus_area;
		}

		GLfloat scale = 2.0;
		for(int n = 0; n != abs(scale_); ++n) {
			scale *= (scale_ < 0 ? 0.5f : 2.0f);
		}

		if(!view_locked) {
			while(image_area.w()*scale*2.0f < image_area.w() &&
			      image_area.h()*scale*2.0f < image_area.h()) {
				scale *= 2.0f;
				scale_++;
				update_zoom_label();
			}
		
			while(focus_area.w()*scale > image_area.w() ||
			      focus_area.h()*scale > image_area.h()) {
				scale *= 0.5f;
				scale_--;
				update_zoom_label();
			}
		}

		const int show_width = image_area.w()/scale;
		const int show_height = image_area.h()/scale;

		int x1 = focus_area.x() + (focus_area.w() - show_width)/2;
		int y1 = focus_area.y() + (focus_area.h() - show_height)/2;
		if(x1 < 0) {
			x1 = 0;
		}

		if(y1 < 0) {
			y1 = 0;
		}

		int x2 = x1 + show_width;
		int y2 = y1 + show_height;
		if(x2 > image_texture.width()) {
			x1 -= (x2 - image_texture.width());
			x2 = image_texture.width();
			if(x1 < 0) {
				x1 = 0;
			}
		}

		if(y2 > image_texture.height()) {
			y1 -= (y2 - image_texture.height());
			y2 = image_texture.height();
			if(y1 < 0) {
				y1 = 0;
			}
		}

		int xpos = image_area.x();
		int ypos = image_area.y();

		src_rect_ = rect(x1, y1, x2 - x1, y2 - y1);
		dst_rect_ = rect(xpos, ypos, (x2-x1)*scale, (y2-y1)*scale);

		graphics::blit_texture(image_texture, xpos, ypos,
		                       (x2-x1)*scale, (y2-y1)*scale, 0.0,
							   GLfloat(x1)/image_texture.width(),
							   GLfloat(y1)/image_texture.height(),
							   GLfloat(x2)/image_texture.width(),
							   GLfloat(y2)/image_texture.height());

		if(!mouse_buttons) {
			dragging_sides_bitmap_ = 0;
		}

		for(int n = 0; n != frame_->num_frames(); ++n) {
			const int row = n/frame_->num_frames_per_row();
			const int col = n%frame_->num_frames_per_row();
			const int x = xpos - x1*scale + (frame_->area().x() + col*(frame_->area().w()+frame_->pad()))*scale;
			const int y = ypos - y1*scale + (frame_->area().y() + row*(frame_->area().h()+frame_->pad()))*scale;
			const rect box(x, y, frame_->area().w()*scale, frame_->area().h()*scale);
			graphics::draw_hollow_rect(box.sdl_rect(), n == 0 ? graphics::color_yellow() : graphics::color_white(), frame_->frame_number(cycle_) == n ? 0xFF : 0x88);

			if(n == 0 && !mouse_buttons) {
				bool rect_chosen = false;
				if(box.w() > 10 && box.h() > 10) {
					rect_chosen = point_in_rect(point(mousex, mousey), rect(box.x()+5, box.y()+5, box.w()-10, box.h()-10));
				}

				if(rect_chosen || point_in_rect(point(mousex, mousey), rect(box.x(), box.y()-4, box.w(), 9))) {
					dragging_sides_bitmap_ |= TOP_SIDE;
					graphics::draw_rect(rect(box.x(), box.y()-1, box.w(), 2).sdl_rect(), graphics::color_red());
				}
				
				if(rect_chosen || !(dragging_sides_bitmap_&TOP_SIDE) && point_in_rect(point(mousex, mousey), rect(box.x(), box.y2()-4, box.w(), 9))) {
					dragging_sides_bitmap_ |= BOTTOM_SIDE;
					graphics::draw_rect(rect(box.x(), box.y2()-1, box.w(), 2).sdl_rect(), graphics::color_red());
				}

				if(rect_chosen || point_in_rect(point(mousex, mousey), rect(box.x()-4, box.y(), 9, box.h()))) {
					dragging_sides_bitmap_ |= LEFT_SIDE;
					graphics::draw_rect(rect(box.x()-1, box.y(), 2, box.h()).sdl_rect(), graphics::color_red());
				}
				
				if(rect_chosen || (!dragging_sides_bitmap_&LEFT_SIDE) && point_in_rect(point(mousex, mousey), rect(box.x2()-4, box.y(), 9, box.h()))) {
					dragging_sides_bitmap_ |= RIGHT_SIDE;
					graphics::draw_rect(rect(box.x2()-1, box.y(), 2, box.h()).sdl_rect(), graphics::color_red());
				}
			} else if(n != 0 && !mouse_buttons) {
				if(point_in_rect(point(mousex, mousey), box)) {
					dragging_sides_bitmap_ = PADDING;
					graphics::draw_rect(box.sdl_rect(), graphics::color_yellow(), 128);
				}
			}
		}

		if(anchor_x_ != -1 && !dragging_sides_bitmap_ &&
		   SDL_GetMouseState(&mousex, &mousey) &&
		   point_in_rect(point(mousex, mousey), dst_rect_)) {
			const point p1 = mouse_point_to_image_loc(point(mousex, mousey));
			const point p2 = mouse_point_to_image_loc(point(anchor_x_, anchor_y_));

			int xpos1 = xpos - x1*scale + p1.x*scale;
			int xpos2 = xpos - x1*scale + p2.x*scale;
			int ypos1 = ypos - y1*scale + p1.y*scale;
			int ypos2 = ypos - y1*scale + p2.y*scale;
			
			if(xpos2 < xpos1) {
				std::swap(xpos1, xpos2);
			}

			if(ypos2 < ypos1) {
				std::swap(ypos1, ypos2);
			}
			
			rect area(xpos1, ypos1, xpos2 - xpos1, ypos2 - ypos1);
			graphics::draw_hollow_rect(area.sdl_rect(), graphics::color_white());
		}
	}

	rect preview_area(x() + (width()*3)/4, y(), width()/4, height());
	GLfloat scale = 1.0;
	while(false && (frame_->width()*scale > preview_area.w() || frame_->height()*scale > preview_area.h())) {
		scale *= 0.5;
	}

	const int framex = preview_area.x() + (preview_area.w() - frame_->width()*scale)/2;
	const int framey = preview_area.y() + (preview_area.h() - frame_->height()*scale)/2;
	frame_->draw(framex, framey, true, false, cycle_, 0, scale);
	if(++cycle_ >= frame_->duration()) {
		cycle_ = 0;
	}

	solid_rect_ = rect();

	const_solid_info_ptr solid = frame_->solid();
	if(solid && solid->area().w()*solid->area().h()) {
		const rect area = solid->area();
		solid_rect_ = rect(framex + area.x(), framey + area.y(), area.w(), area.h());
		graphics::draw_rect(solid_rect_, graphics::color(255, 255, 255, 64));
	}

	foreach(const_widget_ptr w, widgets_) {
		w->draw();
	}
}

point animation_preview_widget::mouse_point_to_image_loc(const point& p) const
{
	const double xpos = double(p.x - dst_rect_.x())/double(dst_rect_.w());
	const double ypos = double(p.y - dst_rect_.y())/double(dst_rect_.h());

	const int x = src_rect_.x() + (double(src_rect_.w()) + 1.0)*xpos;
	const int y = src_rect_.y() + (double(src_rect_.h()) + 1.0)*ypos;
	
	return point(x, y);
}

bool animation_preview_widget::handle_event(const SDL_Event& event, bool claimed)
{
	foreach(widget_ptr w, widgets_) {
		claimed = w->process_event(event, claimed) || claimed;
	}

	if(event.type == SDL_MOUSEBUTTONUP) {
		moving_solid_rect_ = false;
	}

	if(event.type == SDL_MOUSEMOTION) {
		has_motion_ = true;
		const SDL_MouseMotionEvent& e = event.motion;
		if(moving_solid_rect_) {
			if(solid_handler_) {
				const int x = e.x/2;
				const int y = e.y/2;

				solid_handler_(x - anchor_solid_x_, y - anchor_solid_y_);

				anchor_solid_x_ = x;
				anchor_solid_y_ = y;
			}

			return claimed;
		}

		point p(e.x, e.y);
		if(point_in_rect(p, dst_rect_)) {
			p = mouse_point_to_image_loc(p);
			pos_label_->set_text(formatter() << p.x << "," << p.y);
		}

		if(e.state && dragging_sides_bitmap_) {
			int delta_x = e.x - anchor_x_;
			int delta_y = e.y - anchor_y_;

			if(scale_ >= 0) {
				for(int n = 0; n < scale_+1; ++n) {
					delta_x >>= 1;
					delta_y >>= 1;
				}
			}

			int x1 = anchor_area_.x();
			int x2 = anchor_area_.x2();
			int y1 = anchor_area_.y();
			int y2 = anchor_area_.y2();

			if(dragging_sides_bitmap_&LEFT_SIDE) {
				x1 += delta_x;
				if(x1 > x2 - 1) {
					x1 = x2 - 1;
				}
			}

			if(dragging_sides_bitmap_&RIGHT_SIDE) {
				x2 += delta_x;
				if(x2 < x1 + 1) {
					x2 = x1 + 1;
				}
			}

			if(dragging_sides_bitmap_&TOP_SIDE) {
				y1 += delta_y;
				if(y1 > y2 - 1) {
					y1 = y2 - 1;
				}
			}

			if(dragging_sides_bitmap_&BOTTOM_SIDE) {
				y2 += delta_y;
				if(y2 < y1 + 1) {
					y2 = y1 + 1;
				}
			}

			const int width = x2 - x1;
			const int height = y2 - y1;

			rect area(x1, y1, width, height);
			if(area != frame_->area() && rect_handler_) {
				rect_handler_(area);
			}

			if(dragging_sides_bitmap_&PADDING && pad_handler_) {
				const int new_pad = anchor_pad_ + delta_x;
				if(new_pad != frame_->pad()) {
					pad_handler_(new_pad);
				}
			}
		}
	} else if(event.type == SDL_MOUSEBUTTONDOWN) {
		moving_solid_rect_ = false;

		const SDL_MouseButtonEvent& e = event.button;
		const point p(e.x, e.y);
		anchor_area_ = frame_->area();
		anchor_pad_ = frame_->pad();
		has_motion_ = false;
		if(point_in_rect(p, dst_rect_)) {
			claimed = true;
			anchor_x_ = e.x;
			anchor_y_ = e.y;
		} else {
			anchor_x_ = anchor_y_ = -1;

			if(point_in_rect(p, solid_rect_)) {
				moving_solid_rect_ = true;
				anchor_solid_x_ = e.x/2;
				anchor_solid_y_ = e.y/2;
			}
		}
	} else if(event.type == SDL_MOUSEBUTTONUP && anchor_x_ != -1) {

		const SDL_MouseButtonEvent& e = event.button;
		point anchor(anchor_x_, anchor_y_);
		point p(e.x, e.y);
		if(anchor == p && !has_motion_) {
			claimed = true;
			p = mouse_point_to_image_loc(p);
			graphics::surface surf = graphics::surface_cache::get(obj_["image"].as_string());
			std::vector<graphics::surface> surf_key;
			surf_key.push_back(surf);
			if(surf) {
				surf = graphics::texture::build_surface_from_key(surf_key, surf->w, surf->h);
			}

			if(surf) {
				rect area = get_border_rect_around_loc(surf, p.x, p.y);
				if(area.w() > 0) {
					if(rect_handler_) {
						rect_handler_(area);
					}

					int pad = frame_->pad();
					int num_frames = 1;
					int frames_per_row = 1;
					if(find_full_animation(surf, area, &pad, &num_frames, &frames_per_row)) {
						if(pad_handler_) {
							pad_handler_(pad);
						}

						if(num_frames_handler_) {
							std::cerr << "SETTING NUM FRAMES TO " << num_frames << "\n";
							num_frames_handler_(num_frames);
						}

						if(frames_per_row_handler_) {
							frames_per_row_handler_(frames_per_row);
						}
					}
				}
			}
		} else if(!dragging_sides_bitmap_ && point_in_rect(anchor, dst_rect_) && point_in_rect(p, dst_rect_)) {
			claimed = true;

			anchor = mouse_point_to_image_loc(anchor);
			p = mouse_point_to_image_loc(p);

			int x1 = anchor.x;
			int y1 = anchor.y;
			int x2 = p.x;
			int y2 = p.y;

			if(x2 < x1) {
				std::swap(x1, x2);
			}

			if(y2 < y1) {
				std::swap(y1, y2);
			}

			rect area(x1, y1, x2 - x1, y2 - y1);

			if(rect_handler_) {
				rect_handler_(area);
			}
		}

		anchor_x_ = anchor_y_ = -1;
	}

	return claimed;
}

void animation_preview_widget::zoom_in()
{
	++scale_;
	update_zoom_label();
}

void animation_preview_widget::zoom_out()
{
	--scale_;
	update_zoom_label();
}

void animation_preview_widget::reset_rect()
{
	if(rect_handler_) {
		rect_handler_(rect(0,0,0,0));
	}
}

void animation_preview_widget::update_zoom_label() const
{
	if(zoom_label_) {
		int percent = 100;
		for(int n = 0; n != abs(scale_); ++n) {
			if(scale_ > 0) {
				percent *= 2;
			} else {
				percent /= 2;
			}
		}

		zoom_label_->set_text(formatter() << "Zoom: " << percent << "%");
	}
}

void animation_preview_widget::set_rect_handler(boost::function<void(rect)> handler)
{
	rect_handler_ = handler;
}

void animation_preview_widget::set_pad_handler(boost::function<void(int)> handler)
{
	pad_handler_ = handler;
}

void animation_preview_widget::set_num_frames_handler(boost::function<void(int)> handler)
{
	num_frames_handler_ = handler;
}

void animation_preview_widget::set_frames_per_row_handler(boost::function<void(int)> handler)
{
	frames_per_row_handler_ = handler;
}

void animation_preview_widget::set_solid_handler(boost::function<void(int,int)> handler)
{
	solid_handler_ = handler;
}

void animation_preview_widget::rect_handler_delegate(rect r)
{
	using namespace game_logic;
	if(get_environment()) {
		map_formula_callable_ptr callable = map_formula_callable_ptr(new map_formula_callable(get_environment()));
		callable->add("new_rect", r.write());
		variant value = ffl_rect_handler_->execute(*callable);
		get_environment()->execute_command(value);
	} else {
		std::cerr << "animation_preview_widget::rect_handler_delegate() called without environment!" << std::endl;
	}
}

void animation_preview_widget::pad_handler_delegate(int pad)
{
	using namespace game_logic;
	if(get_environment()) {
		map_formula_callable_ptr callable = map_formula_callable_ptr(new map_formula_callable(get_environment()));
		callable->add("new_pad", variant(pad));
		variant value = ffl_pad_handler_->execute(*callable);
		get_environment()->execute_command(value);
	} else {
		std::cerr << "animation_preview_widget::pad_handler_delegate() called without environment!" << std::endl;
	}
}

void animation_preview_widget::num_frames_handler_delegate(int frames)
{
	using namespace game_logic;
	if(get_environment()) {
		map_formula_callable_ptr callable = map_formula_callable_ptr(new map_formula_callable(get_environment()));
		callable->add("new_frames", variant(frames));
		variant value = ffl_num_frames_handler_->execute(*callable);
		get_environment()->execute_command(value);
	} else {
		std::cerr << "animation_preview_widget::num_frames_handler_delegate() called without environment!" << std::endl;
	}
}

void animation_preview_widget::frames_per_row_handler_delegate(int frames_per_row)
{
	using namespace game_logic;
	if(get_environment()) {
		map_formula_callable_ptr callable = map_formula_callable_ptr(new map_formula_callable(get_environment()));
		callable->add("new_frames_per_row", variant(frames_per_row));
		variant value = ffl_frames_per_row_handler_->execute(*callable);
		get_environment()->execute_command(value);
	} else {
		std::cerr << "animation_preview_widget::frames_per_row_handler_delegate() called without environment!" << std::endl;
	}
}

void animation_preview_widget::solid_handler_delegate(int x, int y)
{
	using namespace game_logic;
	if(get_environment()) {
		map_formula_callable_ptr callable = map_formula_callable_ptr(new map_formula_callable(get_environment()));
		callable->add("new_solidx", variant(x));
		callable->add("new_solidy", variant(y));
		variant value = ffl_solid_handler_->execute(*callable);
		get_environment()->execute_command(value);
	} else {
		std::cerr << "animation_preview_widget::solid_handler_delegate() called without environment!" << std::endl;
	}
}

void animation_preview_widget::set_value(const std::string& key, const variant& v)
{
	if(key == "object") {
		try {
			set_object(v);
		} catch(type_error&) {
		} catch(frame::error&) {
		} catch(validation_failure_exception&) {
		} catch(graphics::load_image_error&) {
		}
	}
	widget::set_value(key, v);
}

variant animation_preview_widget::get_value(const std::string& key) const
{
	if(key == "object") {
		return obj_;
	}
	return widget::get_value(key);
}

}

#endif // !NO_EDITOR

