class Reel {

    constructor (config)
    {
        this.scene = config.scene;
        this.manager = config.manager;
        
        this.reel = config.reel;//array, [0..n] integers
        this.current_reel = [...this.reel];
        this.next_spin_reel = [...this.reel];

        this.reel_original_length = this.reel.length;
        this.reel_id = config.id;
        this.lines = config.lines;
        this.target_spin_position = config.target_spin_position;//position at which reel must stop [0..n-1]
        this.target_spin_position_next_spin = 0;
        this.spin_symbols_position = 0;//random position to insert received spin symbols
        this.symbols = config.symbols;//
        this.vDelta = config.vDelta;
        this.rt = config.rt;//render texture
        this.rt_draw_x = config.rt_draw_x;
        this.nm_lines = config.nm_lines;//number of lines, 3,4 or 5
        this.rect_width = REEL_RECT_WIDTH;//width of reel rect in pixels
        this.rect_height = REEL_RECT_HEIGHT;//height of reel rect in pixels

        this.rt_ex = this.scene.add.renderTexture(0, 0 + this.vDelta, 358, 342);//for darkening non - winning symbols when total win scene is active
        this.rt_ex.setVisible(false);
        this.rt_ex.alpha = 0.5;
        this.rt_ex.setOrigin(0);

        this.length_in_pixels = this.rect_height * this.reel.length;//total length of the reel in pixels
        this.current_spin_position_in_pixels = this.rect_height * config.target_spin_position;
        this.path_to_target_position_in_pixels = 0;

        this.reel_speed = config.reel_speed;//pixels / ms
        this.time_to_spin = config.time_to_spin;//ms

        this.current_reel_speed = this.reel_speed;

        this.path_to_spin_in_pixels = this.current_reel_speed * this.time_to_spin;//
        
        this.state = REEL_STATE_SHOW;

        this.reel_back_speed = config.reel_back_speed;
        this.time_to_spin_back = config.time_to_spin_back;

        this.reel_forth_speed = config.reel_forth_speed;

        this.reel_stop_speed = config.reel_stop_speed;
        this.time_to_spin_stop = config.time_to_spin_stop;

        this.stop_snd_pos_to_play = 250;//in pixels remaining until reel stops
        this.stop_snd_played = false;

        this.stop_button_notify_to_hide = false;

        this.remainder = 0;
        this.vofs = 22;
        this.ypos_on_show = this.vofs;//initial y show symbols
        this.spin_symbols = [];

        this.line_info = [];

        this.tmp_reel_indexes = [];

        this.tmp_path_to_target_position_in_pixels = -1;

        this.current_win_line = 0;

        this.sprites = [];
        
        for (let i = 0; i < this.nm_lines; ++i)
        {
            this.sprites[i] = new SpriteEx(this.scene, this, 0, 0, "cherry", 0);
            this.sprites[i].setVisible(false);
        }

        this.force_stop = false;

        this.line_win = this.scene.add.bitmapText(740, 253 + this.vDelta, "totalLineWinFont", "0.25");
        this.line_win.setScale(0.8);
        this.line_win.setVisible(false);

        this.line_win_tween = null;
        this.line_win_target_x = 0;

        this.scatter_key = "";

        this.shake_x = 0;
        this.shake_y = 0;

        this.shakeTime = 50;//ms
        this.shakeTimer = -1;
        this.break_position = -1;//BREAK symbol position on reel
    }
    
    update(time, delta) 
    {
        if(this.state != REEL_STATE_STOPPED && 
           this.state != REEL_STATE_PLAY_WIN_LINES &&
           this.state != REEL_STATE_SHOW &&
           this.state != REEL_STATE_SCATTER_WIN &&
           this.state != REEL_STATE_SHAKE)
        {
            this.current_spin_position_in_pixels += this.current_reel_speed * delta;

            if(this.current_reel_speed > 0)
            {
                this.current_spin_position_in_pixels = this.normGE(this.current_spin_position_in_pixels, this.length_in_pixels, this.length_in_pixels);
            }
            else
            {
                this.current_spin_position_in_pixels = this.normLE(this.current_spin_position_in_pixels, -this.length_in_pixels, this.length_in_pixels);
            }

            this.path_to_target_position_in_pixels -= this.current_reel_speed * delta;

            if(this.state == REEL_STATE_SPIN)
            {
                if(this.current_reel_speed > 0)
                {
                    if(this.path_to_target_position_in_pixels >= 0 && this.path_to_target_position_in_pixels <= 50)//slow down
                    {
                        let v = parseFloat(this.path_to_target_position_in_pixels/100).toFixed(2);

                        this.current_reel_speed *= v;

                        if(this.current_reel_speed < 0.5)
                        {
                            this.current_reel_speed = 0.5;
                        }
                    }
                }
                else
                {
                    if(this.path_to_target_position_in_pixels >= -50 && this.path_to_target_position_in_pixels <= 0)//slow down
                    {
                        let v = Math.abs(parseFloat(this.path_to_target_position_in_pixels/100).toFixed(2));

                        this.current_reel_speed *= v;

                        if(this.current_reel_speed > -0.5)
                        {
                            this.current_reel_speed = -0.5;
                        }
                    }
                }                    
            }
        }                            
        else if (this.state == REEL_STATE_SHOW)
        {
            this.ypos_on_show += 30;

            if(this.ypos_on_show >= (this.rect_height * 2))
            {
                this.state = REEL_STATE_STOPPED;
                
                this.rt_ex.setAlpha(1.0);
                if(this.reel_speed < 0 )
                {
                    this.current_spin_position_in_pixels = this.target_spin_position * this.rect_height - this.length_in_pixels;
                }                    
                this.manager.onShowReelsCompleted(this.reel_id);
            }
        }
        else if (this.state == REEL_STATE_SHAKE)
        {
            if(this.shakeTimer >= 0)
            {
                this.shakeTimer -= delta;

                if(this.shakeTimer < 0)
                {
                    this.shakeTimer = this.shakeTime;
                    this.shake_x = -this.shake_x;
                    this.shake_y = -this.shake_y;
                }
            }
        }

        switch(this.state)
        {
            case REEL_STATE_BACKSPIN:{this.update_on_back_spin();};break;
            case REEL_STATE_FORTHSPIN:{this.update_on_forth_spin();};break;
            case REEL_STATE_SPIN:{this.update_on_main_spin(delta);};break;            
            case REEL_STATE_AFTERSPIN:{this.update_on_after_spin();};break;
            case REEL_STATE_STOPPED:{this.update_on_stopped();};break;
            case REEL_STATE_PLAY_WIN_LINES:{this.update_on_lines_win();};break;
            case REEL_STATE_SCATTER_WIN:{this.update_on_scatter_win_symbols();this.update_on_scatter_win();};break;
            case REEL_STATE_SHOW:{this.update_on_show_symbols();};break;
            case REEL_STATE_SHAKE:{this.update_on_shake_symbols();};break;
                        
            default:return;
        }            
    }

    update_symbols()
    {
        switch(this.state)
        {
            case REEL_STATE_TOTAL_WIN:{this.update_on_total_win_symbols();};break;
            case REEL_STATE_SCATTER_WIN:{this.update_on_scatter_win_symbols();};break;
                        
            default:return;
        }            
    }

    update_active_symbols()
    {
        switch(this.state)
        {
            case REEL_STATE_TOTAL_WIN:{this.update_on_total_win_current_line();};break;
            case REEL_STATE_SCATTER_WIN:{this.update_on_scatter_win();};break;
                        
            default:return;
        }
    }

    set_spin_direction(direction)
    {
        if(direction == "UP" && this.reel_speed < 0)
        {
            return;
        }
        else if(direction == "DOWN" && this.reel_speed > 0)
        {
            return;
        }

        this.reel_speed = -this.reel_speed;
        this.current_reel_speed = this.reel_speed;
        this.path_to_spin_in_pixels = this.current_reel_speed * this.time_to_spin;
    }

    render_texture(key, x, y)
    {
        let dx = (this.rect_width - this.scene.images[key].width) / 2;
        let dy = (this.rect_height - this.scene.images[key].height) / 2;
        this.rt.draw(this.scene.images[key].frame, x + dx, y + dy);
    }

    render_texture_ex(key, x, y)
    {
        this.rt_ex.clear();

        let dx = (this.rt_ex.displayWidth - this.scene.images[key].displayWidth) / 2;
        let dy = (this.rt_ex.displayHeight - this.scene.images[key].displayHeight) / 2;

        this.rt_ex.draw(this.scene.images[key].frame, dx, dy);

        dx = (this.rect_width - this.rt_ex.displayWidth) / 2;
        dy = (this.rect_height - this.rt_ex.displayHeight) / 2;

        this.rt.draw(this.rt_ex, x + dx, y + dy);
    }
    
    render_sprite(sprite, x, y)
    {
        let frame = sprite.getFrame();

        let dx = (this.rect_width - frame.displayWidth) / 2;
        let dy = (this.rect_height - frame.displayHeight) / 2;
    
        this.rt.draw(frame, x + dx, y + dy); 
    }

    render_symbols(reel_positions_to_render)
    {
        let xpos = this.rt_draw_x;
        let ypos = this.vofs;

        if(this.remainder != 0)
        { 
            ypos = this.remainder - this.rect_height;
        }

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];

            this.render_texture(key, xpos, ypos);

            ypos += this.rect_height;
        };
    }

    render_symbols_on_shake(reel_positions_to_render)
    {
        let xpos = this.rt_draw_x + this.shake_x;
        let ypos = this.vofs + this.shake_y;

        if(this.remainder != 0)
        { 
            ypos = this.remainder - this.rect_height;
        }

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];

            if(key != "break")
            {
                this.render_texture(key, xpos, ypos);
            }
            else
            {
                if(this.sprites[this.break_position].isPlaying)
                {
                    this.render_sprite(this.sprites[this.break_position], xpos - this.shake_x, ypos - this.shake_y);
                }
                else
                {
                    this.render_texture(key, xpos - this.shake_x, ypos - this.shake_y);
                }                    
            }
            
            ypos += this.rect_height;
        };
    }

    render_symbols_spin(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.vofs;

        let alpha = 1.0;

        if(this.remainder != 0)
        { 
            alpha = this.remainder / this.rect_height;
            
            ypos = this.remainder - this.rect_height;
        }

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];
        
            if(reel_positions_to_render.length > 3)
            {
                switch(i)
                {
                    case 0:{this.rt_ex.setAlpha(alpha);};break;
                    case (reel_positions_to_render.length-1):{this.rt_ex.setAlpha(1.0 - alpha);};break;
                    default:this.rt_ex.setAlpha(1.0);break;
                }
            }
            else
            {
                this.rt_ex.setAlpha(1.0);
            }
            
            this.render_texture_ex(key, xpos, ypos);
            
            ypos += this.rect_height;
        };
    }
    
    render_symbols_lines_win(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.vofs;
        
        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];
        
            if(this.reel_id < this.line_info[this.current_win_line].len && this.sprites[i].isPlaying)
            {        
            }
            else
            {    
                this.render_texture_ex(key, xpos, ypos);
            }
    
            ypos += this.rect_height;
        };
        
        ypos = this.vofs;
        
        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            if(this.reel_id < this.line_info[this.current_win_line].len && this.sprites[i].isPlaying)
            {        
                this.render_sprite(this.sprites[i], xpos, ypos);
            }
        
            ypos += this.rect_height;
        };
    }

    render_symbols_total_win_symbols(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.vofs;

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];
            let v = this.lines[this.line_info[this.current_win_line].id][this.reel_id] + 1;

            if(this.reel_id < this.line_info[this.current_win_line].len)
            {
                if(!this.sprites[i].isPlaying)
                {
                    if(i != v)
                    {
                        this.render_texture_ex(key, xpos, ypos);
                    }
                }                    
            }                
            else
            {
                if(i != v)
                {
                    this.render_texture_ex(key, xpos, ypos);
                }
            }

            ypos += this.rect_height;
        };
    }

    render_symbols_total_win_current_line(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.vofs;

        let v = this.lines[this.line_info[this.current_win_line].id][this.reel_id] + 1;

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];

            if(this.reel_id < this.line_info[this.current_win_line].len)
            {
                if(!this.sprites[i].isPlaying)
                {
                    if(i == v)
                    {
                        this.render_texture_ex(key, xpos, ypos);
                    }
                }                    
            }                
            else
            {
                if(i == v)
                {
                    this.render_texture_ex(key, xpos, ypos);                   
                }
            }

            ypos += this.rect_height;
        };
        
        ypos = this.vofs;
        
        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            if(this.reel_id < this.line_info[this.current_win_line].len && i == v)
            {
                this.render_sprite(this.sprites[i], xpos, ypos);
            }                

            ypos += this.rect_height;
        };
    }

    render_symbols_scatter_win_symbols(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.vofs;
        
        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];
        
            if(key != this.scatter_key)
            {
                this.render_texture_ex(key, xpos, ypos);
            }

            ypos += this.rect_height;
        };
    }

    render_symbols_scatter_win(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.vofs;

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];
        
            if(key == this.scatter_key)
            {
                this.render_sprite(this.sprites[i], xpos, ypos);
            }                

            ypos += this.rect_height;
        };
    }

    render_symbols_on_show(reel_positions_to_render)
    {
        let xpos = 0 + this.rt_draw_x;
        let ypos = this.ypos_on_show;
        
        reel_positions_to_render = reel_positions_to_render.reverse();

        for(let i = 0; i < reel_positions_to_render.length; ++i)
        {
            let spin_position = reel_positions_to_render[i];
            let key = this.symbols[this.current_reel[spin_position]];

            this.render_texture(key, xpos, ypos);

            ypos -= this.rect_height;

            if(ypos < -this.rect_height)
            {
                return;
            }
        };
    }

    get_positions_to_render()
    {
        let reel_positions_to_render = [];
        let reel_position_start_in_pixels = this.current_spin_position_in_pixels;
        let reel_position_end_in_pixels = 0;

        let idx = 0;
        let pos = reel_position_start_in_pixels;
        
        this.remainder = 0;
        
        if(this.current_reel_speed > 0)
        {
            reel_position_end_in_pixels = this.current_spin_position_in_pixels + this.rect_height * this.nm_lines;
            while(pos < reel_position_end_in_pixels)
            {
                pos = this.normGE(pos, this.length_in_pixels, this.length_in_pixels);

                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
                pos = reel_position_start_in_pixels + idx * this.rect_height;
            }

            pos = this.normGE(pos, this.length_in_pixels, this.length_in_pixels);

            this.remainder = pos % this.rect_height;
            if(this.remainder != 0)
            {
                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
            }
            
            reel_positions_to_render = reel_positions_to_render.reverse();

            this.current_spin_position_in_pixels = this.normGE(this.current_spin_position_in_pixels, this.length_in_pixels, this.length_in_pixels);
        }
        else
        {
            reel_position_end_in_pixels = this.current_spin_position_in_pixels + this.rect_height * this.nm_lines;

            while(pos < reel_position_end_in_pixels)
            {
                pos = this.normL(pos, 0, this.length_in_pixels);
           
                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
                pos = reel_position_start_in_pixels + idx * this.rect_height;
            }

            pos = this.normL(pos, 0, this.length_in_pixels);

            this.remainder = pos % this.rect_height;
            if(this.remainder != 0)
            {
                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
            }
            
            reel_positions_to_render =  reel_positions_to_render.reverse();

            if(this.current_spin_position_in_pixels <= -this.length_in_pixels)
            {
                this.current_spin_position_in_pixels = 0;
            }
        }
        
        return reel_positions_to_render;
    }

    get_positions_to_render_on_show()
    {
        let reel_positions_to_render = [];
        let reel_position_start_in_pixels = this.current_spin_position_in_pixels;
        let reel_position_end_in_pixels = 0;

        let idx = 0;
        let pos = reel_position_start_in_pixels;
        
        this.remainder = 0;
        
        reel_position_end_in_pixels = this.current_spin_position_in_pixels + this.rect_height * this.nm_lines;
        while(pos < reel_position_end_in_pixels)
        {
            pos = this.normGE(pos, this.length_in_pixels, this.length_in_pixels);

            reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
            pos = reel_position_start_in_pixels + idx * this.rect_height;
        }

        pos = this.normGE(pos, this.length_in_pixels, this.length_in_pixels);

        this.remainder = pos % this.rect_height;
        if(this.remainder != 0)
        {
            reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
        }
        
        reel_positions_to_render = reel_positions_to_render.reverse();

        if(this.current_spin_position_in_pixels >= this.length_in_pixels)
        {
            this.current_spin_position_in_pixels -= this.length_in_pixels;
        }

        return reel_positions_to_render;
    }
    
    get_positions_to_render_nm(nm_positions)
    {
        let reel_positions_to_render = [];
        let reel_position_start_in_pixels = this.current_spin_position_in_pixels;
        let reel_position_end_in_pixels = 0;

        let idx = 0;
        let pos = reel_position_start_in_pixels;
        
        this.remainder = 0;
        
        if(this.current_reel_speed > 0)
        {
            reel_position_end_in_pixels = this.current_spin_position_in_pixels + this.rect_height * this.nm_lines;
            while(pos < reel_position_end_in_pixels)
            {
                pos = this.normGE(pos, this.length_in_pixels, this.length_in_pixels);
                
                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
                pos = reel_position_start_in_pixels + idx * this.rect_height;
            }

            pos = this.normGE(pos, this.length_in_pixels, this.length_in_pixels);

            if(nm_positions == 4)
            {
                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
            }
            
            reel_positions_to_render = reel_positions_to_render.reverse();

            if(this.current_spin_position_in_pixels >= this.length_in_pixels)
            {
                this.current_spin_position_in_pixels -= this.length_in_pixels;
            }
        }
        else
        {
            reel_position_end_in_pixels = this.current_spin_position_in_pixels + this.rect_height * this.nm_lines;

            while(pos < reel_position_end_in_pixels)
            {
                pos = this.normL(pos, 0, this.length_in_pixels);

                reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
                pos = reel_position_start_in_pixels + idx * this.rect_height;
            }

            if(nm_positions == 4)
            {
                this.remainder = pos % this.rect_height;
                if(this.remainder != 0)
                {
                    reel_positions_to_render[idx++] = parseInt(pos / this.rect_height);
                }
            }

            reel_positions_to_render =  reel_positions_to_render.reverse();

            if(this.current_spin_position_in_pixels <= -this.length_in_pixels)
            {
                this.current_spin_position_in_pixels = 0;
            }
        }
        
        return reel_positions_to_render;
    }

    init_back_spin()
    {
        this.stop_snd_played = false;
        this.stop_button_notify_to_hide = false;

        let path =  this.reel_back_speed * this.time_to_spin_back;

        if(this.reel_speed > 0)
        {
            this.current_reel_speed = -this.reel_back_speed;
            this.path_to_target_position_in_pixels = -path;
            this.current_spin_position_in_pixels -= this.length_in_pixels;
        }
        else
        {
            this.current_reel_speed = this.reel_back_speed;
            this.path_to_target_position_in_pixels = path;

            this.current_spin_position_in_pixels = this.normL(this.current_spin_position_in_pixels, 0, this.length_in_pixels);
        }
        
        this.state = REEL_STATE_BACKSPIN;
    }

    update_on_back_spin()
    {
        this.render_symbols_spin(this.get_positions_to_render());

        if( (this.current_reel_speed > 0 && this.path_to_target_position_in_pixels <= 0) || (this.current_reel_speed < 0 && this.path_to_target_position_in_pixels >= 0) )
        {
            this.init_spin_forth();
        }    
    };

    init_spin_forth()
    {
        let path =  this.reel_back_speed * this.time_to_spin_back + this.rect_height * this.nm_lines;

        if(this.reel_speed > 0)
        {
            this.current_reel_speed = this.reel_forth_speed;
            this.path_to_target_position_in_pixels = path;

            this.current_spin_position_in_pixels = this.normL(this.current_spin_position_in_pixels, 0, this.length_in_pixels);
        }
        else
        {
            this.current_reel_speed = -this.reel_forth_speed;
            this.path_to_target_position_in_pixels = -path;

            this.current_spin_position_in_pixels = this.normG(this.current_spin_position_in_pixels, 0, this.length_in_pixels);
        }

        this.state = REEL_STATE_FORTHSPIN;
    }

    update_on_forth_spin()
    {
        this.render_symbols_spin(this.get_positions_to_render());
        
        if( (this.current_reel_speed > 0 && this.path_to_target_position_in_pixels <= 0) || (this.current_reel_speed < 0 && this.path_to_target_position_in_pixels >= 0) )
        {
            this.init_main_spin();
        }
     };

    init_main_spin()
    {
        let rs = this.get_positions_to_render().reverse();
        
        if(this.current_reel_speed > 0)
        {
            if((this.current_spin_position_in_pixels % this.rect_height) > this.rect_height / 2)
            {
                if(rs.length > 3)
                {
                    rs.splice(0, 1);
                }                
            }
        }
        else
        {
            if((this.current_spin_position_in_pixels % this.rect_height) < -this.rect_height / 2)
            {
                if(rs.length > 3)
                {
                    rs.pop();
                }                    
            }
            else
            {
                if(rs.length > 3)
                {
                    rs.splice(0, 1);
                }                    
            }
        }
        
        let rs_values = [];

        for(let i = 0; i < rs.length; ++i )
        {
            rs_values.push(this.current_reel[rs[i]]);
        }

        this.tmp_path_to_target_position_in_pixels = rs.length * this.rect_height;

        this.target_spin_position = this.target_spin_position_next_spin;

        this.current_reel = [...this.next_spin_reel];

        this.length_in_pixels = this.rect_height * this.current_reel.length;

        this.current_reel_speed = this.reel_speed;

        let correction = 0;

        if(this.reel_speed > 0)
        {
            let path_to_spin_in_pixels = this.path_to_spin_in_pixels - this.reel_stop_speed * this.time_to_spin_stop;
            
            path_to_spin_in_pixels = this.normGE(path_to_spin_in_pixels, this.length_in_pixels, this.length_in_pixels);

            this.current_spin_position_in_pixels = this.length_in_pixels + this.target_spin_position * this.rect_height - path_to_spin_in_pixels;

            correction = this.rect_height - (this.current_spin_position_in_pixels % this.rect_height);

            this.current_spin_position_in_pixels = this.normGE(this.current_spin_position_in_pixels + correction, this.length_in_pixels, this.length_in_pixels);

            this.tmp_reel_indexes = this.get_positions_to_render_nm(rs.length).reverse();

            for(let i = 0; i < this.tmp_reel_indexes.length; ++i )
            {
                this.current_reel[this.tmp_reel_indexes[i]] = rs_values[i];
            }
        }
        else if(this.reel_speed < 0)
        {
            let path_to_spin_in_pixels = this.path_to_spin_in_pixels + this.reel_stop_speed * this.time_to_spin_stop;
            
            path_to_spin_in_pixels = this.normGE(path_to_spin_in_pixels, this.length_in_pixels, this.length_in_pixels);

            this.current_spin_position_in_pixels = -this.length_in_pixels + this.target_spin_position * this.rect_height - path_to_spin_in_pixels;

            correction = this.rect_height - (this.current_spin_position_in_pixels % this.rect_height);

            this.current_spin_position_in_pixels = this.normGE(this.current_spin_position_in_pixels + correction, 0, this.length_in_pixels);

            this.tmp_reel_indexes = this.get_positions_to_render_nm(rs.length).reverse();

            for(let i = 0; i < this.tmp_reel_indexes.length; ++i )
            {
                this.current_reel[this.tmp_reel_indexes[i]] = rs_values[i];
            }
        }            

        this.path_to_target_position_in_pixels = this.path_to_spin_in_pixels - correction;
        this.state = REEL_STATE_SPIN;
    }

    update_on_main_spin(delta)
    {
        if(this.current_reel_speed > 0)
        {
            this.tmp_path_to_target_position_in_pixels -= this.current_reel_speed * delta;
        }
        else
        {
            this.tmp_path_to_target_position_in_pixels += this.current_reel_speed * delta;
        }            

        if(this.tmp_path_to_target_position_in_pixels < 0 && this.tmp_reel_indexes.length > 0)
        {
            this.current_reel = [...this.next_spin_reel];
            
            this.tmp_reel_indexes.length = 0;
        }

        let v = Math.abs(this.path_to_target_position_in_pixels);

        if(!this.stop_button_notify_to_hide && this.reel_id == 0)
        {
            if(v < this.stop_snd_pos_to_play * 2)//reels are about to begin to stop, hide stop button
            {
                this.stop_button_notify_to_hide = true;
                this.manager.hideStopBtn();
            }
        }

        if(!this.stop_snd_played)
        {
            if((this.current_reel_speed > 0 && v < this.stop_snd_pos_to_play) || (this.current_reel_speed < 0 && v < this.stop_snd_pos_to_play + 2 * REEL_RECT_HEIGHT))
            {
                if(!this.force_stop)
                {
                    this.scene.sound.play("reelstop");
                }
                this.stop_snd_played = true;
            }
        }

        this.render_symbols_spin(this.get_positions_to_render());
        
        if( (this.reel_speed > 0 && this.path_to_target_position_in_pixels <= 0) || (this.reel_speed < 0 && this.path_to_target_position_in_pixels >= 0) )
        {
            if(!this.force_stop)
            {
                if(this.manager.queryPrevReelStopped(this.reel_id))
                {
                    this.init_after_spin();
                }
                else//wait another spin till prev reel stop
                {
                    this.current_spin_position_in_pixels -= this.current_reel_speed * delta;
                    this.path_to_target_position_in_pixels += this.current_reel_speed * delta;
                }
            }
            else
            {
                this.update_on_stop();
            }                
        }    
    }
    
    init_after_spin()
    {
        let path =  this.reel_stop_speed * this.time_to_spin_stop;

        if(this.reel_speed > 0)
        {
            this.current_reel_speed = -this.reel_stop_speed;
            this.path_to_target_position_in_pixels = -path;
            this.current_spin_position_in_pixels -= this.length_in_pixels;
        }
        else
        {
            this.current_reel_speed = this.reel_stop_speed;
            this.path_to_target_position_in_pixels = path;
            
            this.current_spin_position_in_pixels = this.normL(this.current_spin_position_in_pixels, 0, this.length_in_pixels);
        }

        this.state = REEL_STATE_AFTERSPIN;
    }

    update_on_after_spin()
    {
        this.render_symbols_spin(this.get_positions_to_render());

         if( ((this.current_reel_speed > 0) && (this.path_to_target_position_in_pixels <= 0)) || ((this.current_reel_speed < 0) && (this.path_to_target_position_in_pixels >= 0)) )//negative, "UP"(first), //positive, "DOWN" second 
         {
             this.update_on_stop();
         }
    };

    update_on_stop()
    {
        this.current_spin_position_in_pixels = (this.reel_speed > 0)?this.target_spin_position * this.rect_height:this.target_spin_position * this.rect_height - this.length_in_pixels;
        this.current_reel_speed = this.reel_speed;
        this.remainder = 0;
        this.rt_ex.setAlpha(1.0);
        
        this.state = REEL_STATE_STOPPED;
        this.manager.onReelStopped(this.reel_id);
    }

    update_on_stopped()
    {
        this.render_symbols(this.get_positions_to_render());
    }

    update_on_lines_win()
    {
        this.render_symbols_lines_win(this.get_positions_to_render());
    }

    update_on_total_win_symbols()
    {
        this.render_symbols_total_win_symbols(this.get_positions_to_render());
    }

    update_on_total_win_current_line()
    {
        this.render_symbols_total_win_current_line(this.get_positions_to_render());
    }

    update_on_scatter_win_symbols()
    {
        this.render_symbols_scatter_win_symbols(this.get_positions_to_render());
    }

    update_on_scatter_win()
    {
        this.render_symbols_scatter_win(this.get_positions_to_render());
    }
    
    update_on_show_symbols()
    {
        this.render_symbols_on_show(this.get_positions_to_render_on_show());
    }

    update_on_shake_symbols()
    {
        this.render_symbols_on_shake(this.get_positions_to_render());
    }

    create_animation(v, key, isscatter)
    {
        switch(key)
        {
            case "cherry":
            case "lemon":
            case "orange":                
            case "plum":
            case "bell":
            case "grape":
            case "melon":
            case "star":
            case "seven":
                {
                    let animtype = (isscatter) ?2:1;
                    this.sprites[v].setTextureW(key, animtype);
                    this.sprites[v].setDefaultScale_();
                };break;

            case "scatter":
            case "mystery":                
                {
                    this.sprites[v].setTextureW(key, 2);
                    this.sprites[v].setDefaultScale_();
                };break;
        }
    }

    create_line_animations()
    {
        let spin_symbols = [...this.spin_symbols];
        spin_symbols = spin_symbols.reverse();

        for(let i = 0; i < this.line_info.length; ++i)
        {
            if(this.reel_id < this.line_info[i].len)
            {
                let v = this.lines[this.line_info[i].id][this.reel_id] + 1;
                let key = this.symbols[spin_symbols[v]];

                this.create_animation(v, key, false);
            }
        }
    }

    create_scatter_animation(symbol, scatter_info)
    {
        this.scatter_key = this.symbols[symbol];

        for(let i = 0; i < scatter_info.length; ++i)
        {
            if(scatter_info[i] == 1)
            {
                this.create_animation(i, this.symbols[symbol], true);
                return;
            }
        }
    }

    create_break_animation()
    {
        this.sprites[this.break_position].setTextureW("break_anim", 0);
        let animation = this.scene.anims.create({
            key: 'break_animation',
            frames: this.scene.anims.generateFrameNumbers("break_anim"),
            frameRate: 10,
            repeat: 1,
            yoyo: true
        });
        
        this.sprites[this.break_position].play(animation);
        this.sprites[this.break_position].anims.stop();
    }

    play_animations()
    {
        this.sprites.forEach(sprite => {sprite.play_();});
    }

    stop_animations()
    {
        this.sprites.forEach(sprite => {sprite.stop_();});
    }

    destroy_animations()
    {
        this.sprites.forEach(sprite => {sprite.destroy_();});
    }

    play_break_animation()
    {
        this.sprites[this.break_position].play_();
    }

    play_line_animation()
    {
        let v = this.lines[this.line_info[this.current_win_line].id][this.reel_id] + 1;

        this.sprites[v].play_();
    }

    play_scatter_animations(symbol)
    {
        for (let i = 0; i < this.sprites.length; ++i)
        {
            if(this.sprites[i].getTextureKey() == this.symbols[symbol])
            {
                this.sprites[i].play_();    
            }
        }
    }

    on_animation_completed()
    {
        //
    }

    set_winning_line(winningLine)
    {
        this.current_win_line = winningLine;
    }

    start_animation_scalling()
    {
        let len = this.line_info[this.current_win_line].len;

        if(this.reel_id < len)
        {
            let v = this.lines[this.line_info[this.current_win_line].id][this.reel_id] + 1;
          
            this.sprites[v].startSpriteScale();//very short scale up animation

            if(((this.reel_id + 1) == len) && this.state == REEL_STATE_TOTAL_WIN)//show line win
            {
                let x = this.rt_draw_x + 90;
                let y = this.vofs + (v - 1) * this.rect_height - 50 + this.vDelta;

                this.line_win.setText(this.line_info[this.current_win_line].win * this.scene.gameSocket.getBetPerLine());
                this.line_win.setPosition(x, y+140);
                this.line_win.setVisible(true);

                let d = 230;

                if(this.reel_id == 4)//last symbol, change move direction so it can be visible
                {
                    d = -d;
                    this.line_win.setPosition(x + 50, y+140);
                    this.line_win.setOrigin(1, 0);
                }

                this.line_win_target_x = this.line_win.x + d;

                this.line_win_tween = this.scene.tweens.add({

                    targets: this.line_win,
                    ease: 'Quad',
                    duration: 1000,
                    x: this.line_win_target_x,
        
                    onCompleteScope: this,
                    onComplete: function() 
                    {
                        this.scene.tweens.killTweensOf(this.line_win);
                        this.line_win.setVisible(false);
                    },
                });
            }
        }            
    }

    update_line_win_tween(tween, target, targetx)
    {
        if(tween != null)
        {
            tween.stop();
        }

        let duration = tween.totalDuration - tween.totalElapsed;

        tween = this.scene.tweens.add({

            targets: target,
            ease: 'Quad',
            duration: duration,
            x: targetx,

            onCompleteScope: this,
            onCompleteParams:[target],
            onComplete: function() 
            {
                this.scene.tweens.killTweensOf(target);
                target.setVisible(false);
            },
        });

        return tween;
    }

    set_spin_symbols(spin_symbols)
    {
        this.spin_symbols = [...spin_symbols];
        this.spin_symbols = this.spin_symbols.reverse();

        this.next_spin_reel = [...this.reel];

        this.target_spin_position_next_spin = Math.round(Math.random() * (this.next_spin_reel.length - 4) + 1);
        this.next_spin_reel.splice(this.target_spin_position_next_spin, 0, ...this.spin_symbols);

        this.break_position = -1;

        for(let i = 0; i < this.spin_symbols.length; ++i)
        {
            if(this.spin_symbols[i] == BREAK)
            {
                this.break_position = i;
                break;
            }
        }
    }

    set_state(state)
    {
        this.state = state;
        
        if(this.state == REEL_STATE_STOPPED)
        {
            this.rt_ex.setAlpha(1.0);
        }
        else if (this.state == REEL_STATE_SHAKE)
        {
            this.shakeTimer = this.shakeTime;
            
            this.shake_x = Math.floor(Math.random() * (4 - 2 + 1) + 2);
            this.shake_y = Math.floor(Math.random() * (4 - 2 + 1) + 2);
        }
    }

    set_line_info(line_info)
    {
        this.line_info = line_info;

        this.current_win_line = 0;
    }

    get_state()
    {
        return this.state;
    }

    set_reel_speed(v, time_to_spin)
    {
        this.reel_speed = v;
        this.current_reel_speed = v;
        this.reel_forth_speed = Math.abs(v);
        this.time_to_spin = time_to_spin;
        this.path_to_spin_in_pixels = this.current_reel_speed * this.time_to_spin;
    }

    set_vertical_offset(v)
    {
        this.vDelta = v;

        let v1 = (v == 0)?-800:v;

        this.rt_ex.setPosition(0, this.vDelta);
        
        this.line_win.setPosition(this.line_win.x, this.line_win.y + v1);

        if(this.line_win_tween != null && this.line_win_tween.isPlaying())
        {
            this.line_win_tween = this.update_line_win_tween(this.line_win_tween, this.line_win, this.line_win_target_x);
        }            
    }

    stop()
    {
        this.current_reel = [...this.next_spin_reel];

        this.tmp_reel_indexes.length = 0;

        this.target_spin_position = this.target_spin_position_next_spin;
        this.current_reel_speed = this.reel_speed;

        if(this.current_reel_speed > 0)
        {
            this.current_spin_position_in_pixels = this.target_spin_position * this.rect_height - 2 * this.rect_height - this.reel_stop_speed * this.time_to_spin_stop;
            this.path_to_target_position_in_pixels = 2 * this.rect_height + this.reel_stop_speed * this.time_to_spin_stop;
            this.current_spin_position_in_pixels = this.normL(this.current_spin_position_in_pixels, 0, this.length_in_pixels);
        }
        else if(this.current_reel_speed < 0)
        {
            this.current_spin_position_in_pixels = this.target_spin_position * this.rect_height - this.length_in_pixels + 2 * this.rect_height + this.reel_stop_speed * this.time_to_spin_stop;
           this.path_to_target_position_in_pixels = - (2 * this.rect_height + this.reel_stop_speed * this.time_to_spin_stop);
            this.current_spin_position_in_pixels = this.normG(this.current_spin_position_in_pixels, 0, this.length_in_pixels);
        }            
        
        this.force_stop = true;
        this.state = REEL_STATE_SPIN;
        this.stop_button_notify_to_hide = true;
        this.manager.hideStopBtn();
    }

    is_force_stopped()
    {
        return this.force_stop;
    }

    on_start()
    {
        this.scene.tweens.killTweensOf(this.line_win);
        this.line_win.setVisible(false);
        this.line_win_tween = null;
        this.force_stop = false;
        this.stop_animations();

        this.state = REEL_STATE_STOPPED;
    }

    normG(u, v, w)
    {
        while(u > v)
        {
            u -= w;
        }

        return u;
    }

    normGE(u, v, w)
    {
        while(u >= v)
        {
            u -= w;
        }

        return u;
    }

    normL(u, v, w)
    {
        while(u < v)
        {
            u += w;
        }

        return u;
    }

    normLE(u, v, w)
    {
        while(u <= v)
        {
            u += w;
        }

        return u;
    }
}
