/*****************************************************************************
 * vout_beos.cpp: beos video output display method
 *****************************************************************************
 * Copyright (C) 2000, 2001 VideoLAN
 * $Id: vout_beos.cpp,v 1.25 2001/04/29 17:03:20 sam Exp $
 *
 * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
 *          Samuel Hocevar <sam@zoy.org>
 *          Tony Castley <tcastley@mail.powerup.com.au>
 *          Richard Shepherd <richard@rshepherd.demon.co.uk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

#define MODULE_NAME beos
#include "modules_inner.h"

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include "defs.h"

#include <errno.h>                                                 /* ENOMEM */
#include <stdlib.h>                                                /* free() */
#include <stdio.h>
#include <string.h>                                            /* strerror() */
#include <kernel/OS.h>
#include <View.h>
#include <Application.h>
#include <Window.h>
#include <Locker.h>
#include <Screen.h>
#include <malloc.h>
#include <string.h>

extern "C"
{
#include "config.h"
#include "common.h"
#include "threads.h"
#include "mtime.h"
#include "tests.h"
#include "modules.h"

#include "video.h"
#include "video_output.h"

#include "interface.h"
#include "intf_msg.h"

#include "main.h"
}

#include "VideoWindow.h"
#include <Screen.h>

#define WIDTH 128
#define HEIGHT 64
#define BITS_PER_PLANE 16
#define BYTES_PER_PIXEL 2

/*****************************************************************************
 * vout_sys_t: BeOS video output method descriptor
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
 * It describes the BeOS specific properties of an output thread.
 *****************************************************************************/
 
typedef struct vout_sys_s
{
    VideoWindow *         p_window;

    byte_t *              pp_buffer[2];
    s32                   i_width;
    s32                   i_height;
} vout_sys_t;


/*****************************************************************************
 * beos_GetAppWindow : retrieve a BWindow pointer from the window name
 *****************************************************************************/

BWindow *beos_GetAppWindow(char *name)
{
    int32       index;
    BWindow     *window;
    
    for (index = 0 ; ; index++)
    {
        window = be_app->WindowAt(index);
        if (window == NULL)
            break;
        if (window->LockWithTimeout(200000) == B_OK)
        {
            if (strcmp(window->Name(), name) == 0)
            {
                window->Unlock();
                break;
            }
            window->Unlock();
        }
    }
    return window; 
}

/*****************************************************************************
 * DrawingThread : thread that really does the drawing
 *****************************************************************************/

int32 DrawingThread(void *data)
{
  VideoWindow *w;
  w = (VideoWindow*) data;
    
  while(!w->teardownwindow)
  {
    if (w->Lock())
    {
      if( w->fDirty )
      {
        if(w->fUsingOverlay)
        {
          rgb_color key;
           w->view->SetViewOverlay( w->bitmap[w->i_buffer_index],
                                    w->bitmap[w->i_buffer_index]->Bounds(),
                                    w->Bounds(),
                                    &key,
                                    B_FOLLOW_ALL,
				                    B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL
				                    | B_OVERLAY_TRANSFER_CHANNEL );
           w->view->SetViewColor(key);
         }
         else
         {
           w->view->DrawBitmap( w->bitmap[w->i_buffer_index],
                                w->bitmap[w->i_buffer_index]->Bounds(),
                                w->Bounds());
         }
         
         w->fDirty = false;
       }
     w->Unlock();
   }
   else  // we couldn't lock the window, it probably closed.
     return B_ERROR;
     
   snooze (20000);
 } // while

  return B_OK;
}

/*****************************************************************************
 * VideoWindow constructor and destructor
 *****************************************************************************/

VideoWindow::VideoWindow(BRect frame, const char *name, vout_thread_t *p_video_output )
        : BWindow(frame, name, B_TITLED_WINDOW, NULL)
{
	float minWidth, minHeight, maxWidth, maxHeight; 

    teardownwindow = false;
    is_zoomed = false;
    p_vout = p_video_output;
	fDrawThreadID = NULL;
	bitmap[0] = NULL;
	bitmap[1] = NULL;
	
    rect = Frame();
    view = new VLCView(Bounds());
    AddChild(view);
	bitmap[0] = new BBitmap(Bounds(), B_BITMAP_WILL_OVERLAY|B_BITMAP_RESERVE_OVERLAY_CHANNEL, B_YCbCr422);
	bitmap[1] = new BBitmap(Bounds(), B_BITMAP_WILL_OVERLAY, B_YCbCr422);
	fUsingOverlay = true;
	i_screen_depth = 16;
	p_vout->b_YCbr = true;
	
	if ((bitmap[0]->InitCheck() != B_OK) || (bitmap[1]->InitCheck() != B_OK))
	{
		delete bitmap[0];
		delete bitmap[1];
		p_vout->b_YCbr = false;
		fUsingOverlay = false;
		BScreen *screen;
		screen = new BScreen();
		color_space space = screen->ColorSpace();
		delete screen;

		if(space == B_RGB15)
			{
			bitmap[0] = new BBitmap(Bounds(), B_RGB15);
			bitmap[1] = new BBitmap(Bounds(), B_RGB15);
	    	i_screen_depth = 15;
		    }
		    else if(space == B_RGB16)
		 	{
			bitmap[0] = new BBitmap(Bounds(), B_RGB16);
			bitmap[1] = new BBitmap(Bounds(), B_RGB16);
	   		i_screen_depth = 16;
			}
			else //default to 32bpp
			{
			bitmap[0] = new BBitmap(Bounds(), B_RGB32);
			bitmap[1] = new BBitmap(Bounds(), B_RGB32);
	   		i_screen_depth = 32;
			}
	 	SetTitle(VOUT_TITLE " (BBitmap output)");
	 }

	if(fUsingOverlay)
		{
		rgb_color key;
		view->SetViewOverlay(bitmap[0], bitmap[0]->Bounds(), Bounds(), &key, B_FOLLOW_ALL,
				B_OVERLAY_FILTER_HORIZONTAL|B_OVERLAY_FILTER_VERTICAL);
		view->SetViewColor(key);
	 	SetTitle(VOUT_TITLE " (Overlay output)");
		GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 
		SetSizeLimits((float) Bounds().IntegerWidth(), maxWidth, (float) Bounds().IntegerHeight(), maxHeight);
		}
//	else
		{
    	fDrawThreadID = spawn_thread(DrawingThread, "drawing_thread",
                    B_DISPLAY_PRIORITY, (void*) this);
    	resume_thread(fDrawThreadID);
    	}

	memset(bitmap[0]->Bits(), 0, bitmap[0]->BitsLength());
 	memset(bitmap[1]->Bits(), 0, bitmap[1]->BitsLength());
	i_bytes_per_pixel = bitmap[0]->BytesPerRow()/bitmap[0]->Bounds().IntegerWidth();
    fRowBytes = bitmap[0]->BytesPerRow();
    fDirty = false;
    Show();
}

VideoWindow::~VideoWindow()
{
    int32 result;

    Hide();
    Sync();
//    if(!fUsingOverlay)
//    	{
	    teardownwindow = true;
	    wait_for_thread(fDrawThreadID, &result);
    	delete bitmap[0];
    	delete bitmap[1];
//    	}
 }


/*****************************************************************************
 * VideoWindow::FrameResized
 *****************************************************************************/
void VideoWindow::FrameResized( float width, float height )
{
}

/*****************************************************************************
 * VideoWindow::Zoom
 *****************************************************************************/

void VideoWindow::Zoom(BPoint origin, float width, float height )
{
if(is_zoomed)
	{
	MoveTo(rect.left, rect.top);
	ResizeTo(rect.IntegerWidth(), rect.IntegerHeight());
	be_app->ShowCursor();
	}
else
	{
	rect = Frame();
	BScreen *screen;
	screen = new BScreen(this);
	BRect rect = screen->Frame();
	delete screen;
	MoveTo(0,0);
	ResizeTo(rect.IntegerWidth(), rect.IntegerHeight());
	be_app->HideCursor();
	}
is_zoomed = !is_zoomed;
}

/*****************************************************************************
 * VideoWindow::MessageReceived
 *****************************************************************************/

void VideoWindow::MessageReceived( BMessage * p_message )
{
    BWindow * p_win;
    
    switch( p_message->what )
    {
    case B_KEY_DOWN:
    case B_SIMPLE_DATA:
        // post the message to the interface window which will handle it
        p_win = beos_GetAppWindow( "interface" );
        if( p_win != NULL )
        {
            p_win->PostMessage( p_message );
        }
        break;
    
    default:
        BWindow::MessageReceived( p_message );
        break;
    }
}

/*****************************************************************************
 * VideoWindow::QuitRequested
 *****************************************************************************/

bool VideoWindow::QuitRequested()
{
    /* FIXME: send a message ! */
    p_main->p_intf->b_die = 1;
    teardownwindow = true;
    return( false );
}

/*****************************************************************************
 * VLCView::VLCView
 *****************************************************************************/
VLCView::VLCView(BRect bounds) : BView(bounds, "", B_FOLLOW_ALL, B_WILL_DRAW)
{
SetViewColor(B_TRANSPARENT_32_BIT);
}

/*****************************************************************************
 * VLCView::~VLCView
 *****************************************************************************/
VLCView::~VLCView()
{

}

/*****************************************************************************
 * VLCVIew::~VLCView
 *****************************************************************************/
void VLCView::MouseDown(BPoint point)
{
VideoWindow *w = (VideoWindow *) Window();
if(w->is_zoomed)
	{
	BWindow *win = Window();
	win->Zoom();
	}
}

extern "C"
{

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  vout_Probe      ( probedata_t *p_data );
static int  vout_Create     ( struct vout_thread_s * );
static int  vout_Init       ( struct vout_thread_s * );
static void vout_End        ( struct vout_thread_s * );
static void vout_Destroy    ( struct vout_thread_s * );
static int  vout_Manage     ( struct vout_thread_s * );
static void vout_Display    ( struct vout_thread_s * );

static int  BeosOpenDisplay ( vout_thread_t *p_vout );
static void BeosCloseDisplay( vout_thread_t *p_vout );

/*****************************************************************************
 * Functions exported as capabilities. They are declared as static so that
 * we don't pollute the namespace too much.
 *****************************************************************************/
void _M( vout_getfunctions )( function_list_t * p_function_list )
{
    p_function_list->pf_probe = vout_Probe;
    p_function_list->functions.vout.pf_create     = vout_Create;
    p_function_list->functions.vout.pf_init       = vout_Init;
    p_function_list->functions.vout.pf_end        = vout_End;
    p_function_list->functions.vout.pf_destroy    = vout_Destroy;
    p_function_list->functions.vout.pf_manage     = vout_Manage;
    p_function_list->functions.vout.pf_display    = vout_Display;
    p_function_list->functions.vout.pf_setpalette = NULL;
}

/*****************************************************************************
 * vout_Probe: probe the video driver and return a score
 *****************************************************************************
 * This function tries to initialize SDL and returns a score to the
 * plugin manager so that it can select the best plugin.
 *****************************************************************************/
static int vout_Probe( probedata_t *p_data )
{
    if( TestMethod( VOUT_METHOD_VAR, "beos" ) )
    {
        return( 999 );
    }

    return( 100 );
}

/*****************************************************************************
 * vout_Create: allocates BeOS video thread output method
 *****************************************************************************
 * This function allocates and initializes a BeOS vout method.
 *****************************************************************************/
int vout_Create( vout_thread_t *p_vout )
{
    /* Allocate structure */
    p_vout->p_sys = (vout_sys_t*) malloc( sizeof( vout_sys_t ) );
    if( p_vout->p_sys == NULL )
    {
        intf_ErrMsg( "error: %s", strerror(ENOMEM) );
        return( 1 );
    }
    
    /* Set video window's size */
    p_vout->i_width =  main_GetIntVariable( VOUT_WIDTH_VAR,
                                            VOUT_WIDTH_DEFAULT );
    p_vout->i_height = main_GetIntVariable( VOUT_HEIGHT_VAR,
                                            VOUT_HEIGHT_DEFAULT );

    /* Open and initialize device */
    if( BeosOpenDisplay( p_vout ) )
    {
        intf_ErrMsg("vout error: can't open display");
        free( p_vout->p_sys );
        return( 1 );
    }

    return( 0 );
}

/*****************************************************************************
 * vout_Init: initialize BeOS video thread output method
 *****************************************************************************/
int vout_Init( vout_thread_t *p_vout )
{
    VideoWindow * p_win = p_vout->p_sys->p_window;
    u32 i_page_size;


    i_page_size =   p_vout->i_width * p_vout->i_height * p_vout->i_bytes_per_pixel;
    
    p_vout->p_sys->i_width =         p_vout->i_width;
    p_vout->p_sys->i_height =        p_vout->i_height;    

/*    if(p_win->fUsingOverlay)
    	{
	    if(p_win->bitmap[0] != NULL)
	    	{
		    vout_SetBuffers( p_vout, (byte_t *)p_win->bitmap[0]->Bits(),
    	                 (byte_t *)p_win->bitmap[0]->Bits());
	    	delete p_win->bitmap[0];
	    	p_win->bitmap[0] = NULL;
	    	}
    	}
    else
   		{
	    if((p_win->bitmap[0] != NULL) && (p_win->bitmap[1] != NULL))
	    	{
	    	vout_SetBuffers( p_vout, (byte_t *)p_win->bitmap[0]->Bits(),
   	                 (byte_t *)p_win->bitmap[1]->Bits());
   	        }
    	}*/
	    if((p_win->bitmap[0] != NULL) && (p_win->bitmap[1] != NULL))
	    	{
	    	vout_SetBuffers( p_vout, (byte_t *)p_win->bitmap[0]->Bits(),
   	                 (byte_t *)p_win->bitmap[1]->Bits());
   	        }
    
    return( 0 );
}

/*****************************************************************************
 * vout_End: terminate BeOS video thread output method
 *****************************************************************************/
void vout_End( vout_thread_t *p_vout )
{
}

/*****************************************************************************
 * vout_Destroy: destroy BeOS video thread output method
 *****************************************************************************
 * Terminate an output method created by DummyCreateOutputMethod
 *****************************************************************************/
void vout_Destroy( vout_thread_t *p_vout )
{
    BeosCloseDisplay( p_vout );
    
    free( p_vout->p_sys );
}

/*****************************************************************************
 * vout_Manage: handle BeOS events
 *****************************************************************************
 * This function should be called regularly by video output thread. It manages
 * console events. It returns a non null value on error.
 *****************************************************************************/
int vout_Manage( vout_thread_t *p_vout )
{
VideoWindow * p_win = p_vout->p_sys->p_window;
rgb_color key;
float minWidth, minHeight, maxWidth, maxHeight; 

if( (p_vout->i_width  != p_vout->p_sys->i_width) ||
             (p_vout->i_height != p_vout->p_sys->i_height) )
    {
        /* If video output size has changed, change interface window size */
        intf_DbgMsg( "resizing output window" );
        if(p_win->fUsingOverlay)
        	{
	        p_win->Lock();
	        p_win->view->ClearViewOverlay();
	        delete p_win->bitmap[0];
	        delete p_win->bitmap[1];
	        p_vout->p_sys->i_width =    p_vout->i_width;
	        p_vout->p_sys->i_height =   p_vout->i_height;;
			p_win->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 
			p_win->SetSizeLimits((float) p_vout->p_sys->i_width, maxWidth, (float) p_vout->p_sys->i_height, maxHeight);       
	        p_win->ResizeTo(p_vout->p_sys->i_width, p_vout->p_sys->i_height);
	        p_win->bitmap[0] = new BBitmap(p_win->Bounds(),
        					B_BITMAP_WILL_OVERLAY|B_BITMAP_RESERVE_OVERLAY_CHANNEL,
        					B_YCbCr422);
	        p_win->bitmap[0] = new BBitmap(p_win->Bounds(),
        					B_BITMAP_WILL_OVERLAY, B_YCbCr422);
			memset(p_win->bitmap[0]->Bits(), 0, p_win->bitmap[0]->BitsLength());
			memset(p_win->bitmap[1]->Bits(), 0, p_win->bitmap[1]->BitsLength());
			p_win->view->SetViewOverlay(p_win->bitmap[0], p_win->bitmap[0]->Bounds(), p_win->Bounds(), &key, B_FOLLOW_ALL,
					B_OVERLAY_FILTER_HORIZONTAL|B_OVERLAY_FILTER_VERTICAL|B_OVERLAY_TRANSFER_CHANNEL);
			p_win->view->SetViewColor(key);
			p_win->Unlock();
		    vout_SetBuffers( p_vout, (byte_t *)p_win->bitmap[0]->Bits(),
	   	                 (byte_t *)p_win->bitmap[0]->Bits());
	   	    delete p_win->bitmap[0];
	   	    }
	    }
return( 0 );
}

/*****************************************************************************
 * vout_Display: displays previously rendered output
 *****************************************************************************
 * This function send the currently rendered image to BeOS image, waits until
 * it is displayed and switch the two rendering buffers, preparing next frame.
 *****************************************************************************/
void vout_Display( vout_thread_t *p_vout )
{
    VideoWindow * p_win = p_vout->p_sys->p_window;
    
   	p_win->i_buffer_index = p_vout->i_buffer_index;
   	if(p_win->fUsingOverlay)
   		p_vout->i_buffer_index = p_vout->i_buffer_index & 1;
   	else
   		p_vout->i_buffer_index = ++p_vout->i_buffer_index & 1;
    p_win->fDirty = true;
}

/* following functions are local */

/*****************************************************************************
 * BeosOpenDisplay: open and initialize BeOS device
 *****************************************************************************
 * XXX?? The framebuffer mode is only provided as a fast and efficient way to
 * display video, providing the card is configured and the mode ok. It is
 * not portable, and is not supposed to work with many cards. Use at your
 * own risk !
 *****************************************************************************/

static int BeosOpenDisplay( vout_thread_t *p_vout )
{ 
    p_vout->p_sys->p_window =
        new VideoWindow(  BRect( 80, 50, 80+p_vout->i_width-1, 50+p_vout->i_height-1 ), NULL, p_vout );
    if( p_vout->p_sys->p_window == 0 )
    {
        free( p_vout->p_sys );
        intf_ErrMsg( "error: cannot allocate memory for VideoWindow" );
        return( 1 );
    }   
    VideoWindow * p_win = p_vout->p_sys->p_window;
    
    p_vout->i_screen_depth =         p_win->i_screen_depth;
    p_vout->i_bytes_per_pixel =      p_win->i_bytes_per_pixel;
    p_vout->i_bytes_per_line =       p_vout->i_width*p_win->i_bytes_per_pixel;
    
    switch( p_vout->i_screen_depth )
    {
    case 8:
        intf_ErrMsg( "vout error: 8 bit mode not fully supported" );
        break;
    case 15:
        p_vout->i_red_mask =        0x7c00;
        p_vout->i_green_mask =      0x03e0;
        p_vout->i_blue_mask =       0x001f;
        break;
    case 16:
        p_vout->i_red_mask =        0xf800;
        p_vout->i_green_mask =      0x07e0;
        p_vout->i_blue_mask =       0x001f;
        break;
    case 24:
    case 32:
    default:
        p_vout->i_red_mask =        0xff0000;
        p_vout->i_green_mask =      0x00ff00;
        p_vout->i_blue_mask =       0x0000ff;
        break;
    }
    return( 0 );
}

/*****************************************************************************
 * BeosDisplay: close and reset BeOS device
 *****************************************************************************
 * Returns all resources allocated by BeosOpenDisplay and restore the original
 * state of the device.
 *****************************************************************************/
static void BeosCloseDisplay( vout_thread_t *p_vout )
{    
    /* Destroy the video window */
    p_vout->p_sys->p_window->Lock();
    p_vout->p_sys->p_window->Quit();
}

} /* extern "C" */