<?php

abstract class GCompList
{
    protected $id;

    protected $num_items;
    protected $page_size;
    protected $preload_size;

    protected $width;
    protected $height;
    protected $h_padding;

    protected $sel_name;
    protected $top_name;

    protected $num_labels;
    protected $num_images;

    protected $sel_dw;
    protected $sel_dh;
    protected $sel_dx;
    protected $sel_dy;

    protected $sep_ndx;
    protected $sep_caption;

    public function __construct($id,
        $num_items, $page_size, $preload_size,
        $width, $height, $h_padding=0)
    {
        $this->id = $id;

        $this->num_items = $num_items;
        $this->page_size = $page_size;
        $this->preload_size = $preload_size;

        $this->width = $width;
        $this->height = $height;
        $this->h_padding = $h_padding;

        $this->sel_name = "$id.sel";
        $this->top_name = "$id.top";

        $this->num_labels = 0;
        $this->num_images = 0;

        $this->sel_dw = 0;
        $this->sel_dh = 0;
        $this->sel_dx = 0;
        $this->sel_dy = 0;

        $this->sep_ndx = -1;
        $this->sep_caption = null;
    }

    public function set_sel_geom($dw, $dh, $dx, $dy)
    {
        $this->sel_dw = $dw;
        $this->sel_dh = $dh;
        $this->sel_dx = $dx;
        $this->sel_dy = $dy;
    }

    public function get_sel_state($base_sel_state, $sel, $top)
    {
        $new_sel_state = clone $base_sel_state;
        $new_sel_state->{$this->sel_name} = $sel;
        $new_sel_state->{$this->top_name} = $top;
        return $new_sel_state;
    }

    public function get_item_sel($sel_state)
    {
        return $sel_state->{$this->sel_name};
    }

    public function get_item_top($sel_state)
    {
        return $sel_state->{$this->top_name};
    }

    ///////////////////////////////////////////////////////////////////////

    protected function get_num_seps_in_range($from, $to)
    {
        if ($this->sep_ndx >= $from && $this->sep_ndx < $to)
            return 1;
        return 0;
    }

    protected function get_align_bottom_num_seps($sel_state)
    {
        $top = $sel_state->{$this->top_name};
        if ($top + $this->page_size != $this->num_items)
            return 0;

        $n = $this->get_num_seps_in_range($top, $this->num_items);
        $sel = $sel_state->{$this->sel_name};
        $bwd_pos = $this->num_items - 1 - $sel;
        return max(0, $n - max(0, $bwd_pos - 1));
    }

    ///////////////////////////////////////////////////////////////////////

    public function get_sel_geom($sel_state)
    {
        return GCompGeom::place_same_size(
            $this->sel_dw, $this->sel_dh, $this->sel_dx, $this->sel_dy,
            $this->get_item_id($sel_state->{$this->sel_name}));
    }

    public function get_label_def($ndx, $label_ndx,
        $geom, $selected, $text, $max_num_lines)
    {
        $def = GCompsFactory::label($geom,
            null, $text, $max_num_lines,
            $this->get_item_text_color($selected),
            $this->get_item_text_size($selected),
            null,
            array('base_font_size' => $this->get_item_text_size(true)));
        $def[GComponentDef::id] = $this->get_label_id($ndx, $label_ndx);
        return $def;
    }

    public function get_image_def($ndx, $image_ndx,
        $selected, $url,
        $keep_aspect_ratio=false, $upscale_enabled=true,
        $not_loaded_url=null, $load_failed_url=null)
    {
        $geom = $this->get_item_img_geom($image_ndx, $selected);
        $def = GCompsFactory::get_image_def($geom,
            null, $url, $keep_aspect_ratio, $upscale_enabled,
            $not_loaded_url, $load_failed_url);
        $def[GComponentDef::id] = $this->get_image_id($ndx, $image_ndx);
        return $def;
    }

    private function get_label_id($ndx, $label_ndx)
    {
        return $this->id . "_item${ndx}_label$label_ndx";
    }

    private function get_image_id($ndx, $image_ndx)
    {
        return $this->id . "_item${ndx}_image$image_ndx";
    }

    private function get_item_id($ndx)
    {
        return $this->id . "_item$ndx";
    }

    private function get_item_geom($sel_state, $ndx)
    {
        $row_h = $this->height / $this->page_size;
        $top = $sel_state->{$this->top_name};
        $y = ($ndx - $top) * $row_h;

        $n = $this->get_num_seps_in_range($top, $ndx + 1);
        $n -= $this->get_align_bottom_num_seps($sel_state);
        $y += $n * (L::$VOD_LIST_SEP_TOP + L::$VOD_LIST_SEP_BOTTOM);

        return GCompGeom::place_top_left(
            $this->width - $this->h_padding * 2, $row_h,
            $this->h_padding, $y);
    }

    private function get_item_sep_geom($sel_state)
    {
        $row_h = $this->height / $this->page_size;
        $top = $sel_state->{$this->top_name};
        $y = L::$VOD_LIST_ICON_V_PADDING +
            $row_h * ($this->sep_ndx - $top) + L::$VOD_LIST_SEP_TOP - 10;

        $n = $this->get_num_seps_in_range($top, $this->sep_ndx);
        $n -= $this->get_align_bottom_num_seps($sel_state);
        $y += $n * (L::$VOD_LIST_SEP_TOP + L::$VOD_LIST_SEP_BOTTOM);

        return GCompGeom::place_top_left_by_center(
            $this->width,
            L::$VOD_LIST_SEP_TOP + L::$VOD_LIST_SEP_BOTTOM,
            0, $y);
    }

    private function get_item_sep_def($id, $geom)
    {
        $h = L::$VOD_LIST_SEP_TOP + L::$VOD_LIST_SEP_BOTTOM;

        $defs1[] = GCompsFactory::get_panel_def(null,
            GCompGeom::center(L::$VOD_LIST_SEP_TITLE_LEFT, $h),
            null,
            array(
                GCompsFactory::get_rect_def(
                    GCompGeom::place_left_center(
                        L::$VOD_LIST_SEP_TITLE_LEFT - L::$TEXT_HGAP, 3,
                        0, -L::$SMALL_TEXT_ALIGN),
                    null, "#FF404040")
            ));

        $defs1[] = GCompsFactory::label(
            GCompGeom::right(-1, -1, 0),
            null, $this->sep_caption, 1, '#FF808080', L::$SMALL_TEXT_SIZE);

        $defs2[] = GCompsFactory::get_panel_def(null,
            GCompGeom::left(-1, $h), null, $defs1);

        $defs2[] = GCompsFactory::get_panel_def(null,
            GCompGeom::center(-1, $h), null,
            array(
                GCompsFactory::get_rect_def(
                    GCompGeom::place_left_center(
                        L::$WIDTH, 3,
                        L::$TEXT_HGAP, -L::$SMALL_TEXT_ALIGN),
                    null, "#FF404040")
            ));

        return GCompsFactory::get_panel_def($id, $geom, null, $defs2);
    }

    private function get_text_props($selected)
    {
        return array(
            'text_size' => $this->get_item_text_size($selected),
            'color' => $this->get_item_text_color($selected)
        );
    }

    private function get_item_def($ndx, $sel_state)
    {
        $selected = $ndx == $sel_state->{$this->sel_name};
        $geom = $this->get_item_geom($sel_state, $ndx);

        $def = $this->get_item_def_internal(
            $ndx, $sel_state, $geom, $selected);
        $def[GComponentDef::id] = $this->get_item_id($ndx);
        return $def;
    }

    public function get_panel_def($sel_state, $x, $y)
    {
        $defs = array();
        $top = $sel_state->{$this->top_name};
        $to = min($this->num_items - $top, $this->preload_size);
        for ($i = 0; $i < $to; $i++)
        {
            $ndx = $i + $top;
            $defs[] = $this->get_item_def($ndx, $sel_state);
        }
        if ($this->sep_ndx >= 0)
        {
            $geom = $this->get_item_sep_geom($sel_state);
            $defs[] = $this->get_item_sep_def($this->id . "_sep", $geom);
        }
        return GCompsFactory::get_panel_def($this->id,
            GCompGeom::place_top_left($this->width, $this->height, $x, $y),
            null, $defs);
    }

    private function add_label_change_defs(&$change_defs,
        $ndx, $props, $transition)
    {
        for ($label_ndx = 0; $label_ndx < $this->num_labels; $label_ndx++)
        {
            $change_defs[] = GCompsFactory::get_change_def(
                $this->get_label_id($ndx, $label_ndx),
                null, $props,
                null, null, null, $transition);
        }
    }

    private function add_image_change_defs(&$change_defs,
        $ndx, $selected, $transition)
    {
        if (!isset($selected))
            return;
        for ($image_ndx = 0; $image_ndx < $this->num_images; $image_ndx++)
        {
            $geom = $this->get_item_img_geom($image_ndx, $selected);
            if (!isset($geom))
                continue;

            $change_defs[] = GCompsFactory::get_change_def(
                $this->get_image_id($ndx, $image_ndx),
                $geom, null, null, null, null, $transition);
        }
    }

    private function add_insert_actions(&$change_defs,
        $sel_state, $new_sel_state, $no_transition)
    {
        $sel_name = $this->sel_name;
        $top_name = $this->top_name;

        if ($no_transition)
        {
            $from = $new_sel_state->$top_name;
            $to = min($this->num_items,
                $new_sel_state->$top_name + $this->preload_size);
        }
        else if ($new_sel_state->$top_name < $sel_state->$top_name)
        {
            $from = $new_sel_state->$top_name;
            $to = $sel_state->$top_name;
        }
        else
        {
            $from = min($this->num_items,
                $sel_state->$top_name + $this->preload_size);
            $to = min($this->num_items,
                $new_sel_state->$top_name + $this->preload_size);
        }

        $transition = $no_transition ?
            GCOMP_TRANSITION_NONE : GCOMP_TRANSITION_DEFAULT;

        for ($ndx = $from; $ndx < $to; $ndx++)
        {
            hd_print("DEBUG adding item #$ndx");
            $def = $this->get_item_def($ndx, $sel_state);
            $change_def = GCompsFactory::get_change_def($this->id,
                null, null, null, null,
                array($def), GCOMP_TRANSITION_DEFAULT);
            $change_def[ChangeGCompDef::children_merge_mode] = true;
            $change_def[ChangeGCompDef::children_insert_after_id] =
                $this->get_item_id($ndx - 1);
            $change_defs[] = $change_def;

            $sel_type = null;
            if ($ndx == $sel_state->$sel_name)
                $sel_type = false;
            else if ($ndx == $new_sel_state->$sel_name)
                $sel_type = true;
            $props = isset($sel_type) ?
                $this->get_text_props($sel_type) : null;

            if ($new_sel_state->$top_name != $sel_state->$top_name)
            {
                $change_defs[] = GCompsFactory::get_change_def(
                    $this->get_item_id($ndx),
                    $this->get_item_geom($new_sel_state, $ndx), null,
                    null, null, null, $transition);
            }

            if (isset($props))
            {
                $this->add_label_change_defs($change_defs,
                    $ndx, $props, $transition);
            }
            $this->add_image_change_defs($change_defs,
                $ndx, $sel_type, $transition);
        }
    }

    public function handle_op(&$change_defs, $op, $sel_state, $no_animation=false)
    {
        $sel = $sel_state->{$this->sel_name};
        $top = $sel_state->{$this->top_name};

        $num_items = $this->num_items;
        $page_size = $this->page_size;

        $new_sel = $sel;
        $new_top = $top;

        $scroll_page_size = floor(($page_size + 1) / 2);

        if ($op == 'page_up')
            $new_sel = max(0, $sel - $scroll_page_size);
        else if ($op == 'page_down')
            $new_sel = min($num_items - 1, $sel + $scroll_page_size);
        else if ($op == 'up')
            $new_sel = max(0, $sel - 1);
        else if ($op == 'down')
            $new_sel = min($num_items - 1, $sel + 1);
        else if ($op == 'left')
            $new_sel = 0;
        else if ($op == 'right')
            $new_sel = $num_items - 1;
        else if (0 === strpos($op, "sel:"))
        {
            $new_sel = intval(substr($op, 4));
            $new_sel = min($num_items - 1, $new_sel);
            $new_sel = max(0, $new_sel);
        }

        if ($new_sel == $sel)
            return null;

        $new_top = $new_sel - intval($page_size / 2);
        if ($new_top > $num_items - $page_size)
            $new_top = $num_items - $page_size;
        $new_top = max(0, $new_top);

        $new_sel_state = $this->get_sel_state(
            $sel_state, $new_sel, $new_top);

        $align_bottom_num_seps =
            $this->get_align_bottom_num_seps($sel_state);
        $new_align_bottom_num_seps =
            $this->get_align_bottom_num_seps($new_sel_state);

        hd_print("new_sel=$new_sel, new_top=$new_top");

        $no_transition = abs($new_top - $top) > $page_size;
        $transition = $no_transition || $no_animation ?
            GCOMP_TRANSITION_NONE : GCOMP_TRANSITION_DEFAULT;

        $change_defs[] = GCompsFactory::get_change_def('sel',
            self::get_sel_geom($new_sel_state),
            null, null, null, null, $transition);

        $sel_props = $this->get_text_props(true);
        $desel_props = $this->get_text_props(false);

        if ($new_top != $top ||
            $new_align_bottom_num_seps != $align_bottom_num_seps)
        {
            $to = min($this->num_items, $top + $this->preload_size);
            for ($i = $top; $i < $to; $i++)
            {
                $removed = $i < $new_top ||
                    $i >= $new_top + $this->preload_size;
                $fast_removed = $no_transition && $removed;

                if ($removed)
                    hd_print("DEBUG removing item $i");

                $geom = null;
                $props = null;
                $sel_type = null;
                if (!$fast_removed)
                {
                    $geom = self::get_item_geom($new_sel_state, $i);
                    $props = $i == $sel ? $desel_props :
                        ($i == $new_sel ? $sel_props : null);
                    $sel_type = $i == $sel ? false :
                        ($i == $new_sel ? true : null);
                }

                $change_def = GCompsFactory::get_change_def(
                    $this->get_item_id($i),
                    $geom, null, null, null, null, $transition);
                $change_def[ChangeGCompDef::remove] = $removed;
                $change_defs[] = $change_def;

                if (isset($props))
                {
                    $this->add_label_change_defs($change_defs,
                        $i, $props, $transition);
                }
                $this->add_image_change_defs($change_defs,
                    $i, $sel_type, $transition);
            }

            $this->add_insert_actions($change_defs,
                $sel_state, $new_sel_state, $no_transition);

            if ($this->sep_ndx != -1)
            {
                $change_defs[] = GCompsFactory::get_change_def(
                    $this->id . "_sep",
                    $this->get_item_sep_geom($new_sel_state),
                    null, null, null, null, $transition);
            }
        }
        else
        {
            $this->add_label_change_defs($change_defs,
                $sel, $desel_props, $transition);
            $this->add_label_change_defs($change_defs,
                $new_sel, $sel_props, $transition);
            $this->add_image_change_defs($change_defs,
                $sel, false, $transition);
            $this->add_image_change_defs($change_defs,
                $new_sel, true, $transition);
        }

        return $new_sel_state;
    }

    ///////////////////////////////////////////////////////////////////////

    protected function get_item_text_size($selected)
    { return null; }

    protected function get_item_text_color($selected)
    { return null; }

    protected function get_item_img_geom($image_ndx, $selected)
    { return null; }

    protected abstract function get_item_def_internal(
        $ndx, $sel_state, $geom, $selected);
}

?>
