#ifdef WIN32
#include <winsock2.h>
#include <windows.h>

#include <assert.h>
#include <sys/time.h>
#include <getopt.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <string>
#include <stdlib.h>

#include "vn_player.h"
#include "vn_renderer.h"

#include <gdiplus.h>
using namespace Gdiplus;

static bool verbose;
static unsigned buffer_len = 0; // in ms
static bool use_hw_decoder = false;
static bool use_rtp_over_tcp = false;

float contrast_scale = 1.0f;
float brightness_scale = 0.0f;
float saturation_scale = 1.0f;
VN_IMAGE_FILL_MODE fill_mode = VN_IMAGE_FILL_ASPECT;

bool zoom_mode = false;
float pan_x = 0.5f;
float pan_y = 0.5f;
float scale = 1.0f;
VN_IMAGE_ZOOM_MODE zoom_filter = VN_IMAGE_ZOOM_BILENEAR;

class Media_Player
{
public:
    Media_Player(unsigned player_num, HWND hwnd);
    ~Media_Player();

    void open(const char *url);
    void play();
    void pause();
    bool is_paused() { return paused_; } 
    void resume();
    void step_mode(bool value);
    
    vn_renderer_context_t *get_renderer() { return r_ctx_; }

private:
   static void new_frame(const vn_player_frame_t*, const vn_player_cache_boundaries_t*, void *client_data);
   
   static void buffer_changed(const vn_player_cache_boundaries_t*, void *client_data);  
        
   static void state_changed(VN_PLAYER_STREAM_STATE, const vn_player_result_status_t*, void *client_data);
       
   static void new_stream(const vn_player_stream_info_t*, void *client_data); 
        
   static void stream_removed(const vn_player_stream_info_t*, void *client_data); 
   
   static void msg_log(const char *msg, void *client_data);

private:
    struct vn_player_config_t conf_;	
    struct vn_player_context_t   *ctx_;
    struct vn_renderer_context_t *r_ctx_;

    unsigned frame_num_;
    unsigned player_num_;

    HWND hwnd_;
    bool paused_;
};

Media_Player::Media_Player(unsigned player_num, HWND hwnd)
    : frame_num_(0), player_num_(player_num), hwnd_(hwnd), paused_(false)
{
    r_ctx_ = vn_renderer_create((void*)hwnd_);

    memset(&conf_, 0, sizeof(conf_));
    conf_.cache_size = 0;
    conf_.decoder_type = use_hw_decoder ? DECODER_HW : DECODER_SW_MT;
    conf_.pixel_format = vn_renderer_get_preferred_pixel_format(r_ctx_);//PIX_YUV420P;
    conf_.rtp_transport = use_rtp_over_tcp? RTP_TRANSPORT_TCP : RTP_TRANSPORT_UDP;
    conf_.client_data = this;
    conf_.buffer_len  = buffer_len;

    struct vn_player_callbacks_t callbacks = {};
    callbacks.buffer_changed = buffer_changed;
    callbacks.new_frame      = new_frame;
    callbacks.new_stream     = new_stream;
    callbacks.state_changed  = state_changed;
    callbacks.stream_removed = stream_removed;
    callbacks.msg_log        = msg_log;

    ctx_ = vn_player_create(&conf_);
    vn_player_set_callbacks(ctx_, &callbacks);


    assert(r_ctx_);
}

Media_Player::~Media_Player()
{
    vn_player_destroy(ctx_);
    vn_renderer_destroy(r_ctx_);
}

void Media_Player::open(const char *url)
{
    vn_player_open(ctx_, url);
}

void Media_Player::play()
{
    vn_player_play(ctx_);
    paused_ = false;
}

void Media_Player::pause()
{
    paused_ = true;
    vn_player_pause(ctx_);
}

void Media_Player::resume()
{
    vn_player_resume(ctx_, 1);
    paused_ = false;
}

void Media_Player::step_mode(bool value)
{
    vn_player_set_step_mode(ctx_, value);
}

void Media_Player::new_frame(const vn_player_frame_t *frame, const vn_player_cache_boundaries_t *bounds, void *client_data)
{
    Media_Player *p = static_cast<Media_Player*>(client_data);

    if (++p->frame_num_ % 100 == 0)
    {
        printf("[%d] Got %d frames (%dx%d)\n", p->player_num_, p->frame_num_, frame->width, frame->height);
    }

    if (verbose)
    {
        struct timeval tv;
        gettimeofday(&tv, 0);
        printf("%ld.%ld Got frame (%s). Time: %ld.%ld\n", tv.tv_sec, tv.tv_usec, frame->stream_info->type == AUDIO ? "audio" : "video", frame->captured_time->tv_sec, frame->captured_time->tv_usec);
        if (frame->objects_data)
        {
            printf("Metadata: %s\n", frame->objects_data);
        }
    }

    int res = vn_renderer_process(p->r_ctx_, frame);

    if (res < 0)
        fprintf(stderr, "res < 0\n");
    
    InvalidateRect(p->hwnd_, 0, FALSE);
    
}

void Media_Player::buffer_changed(const vn_player_cache_boundaries_t *bounds, void *client_data)
{
//	printf("buffer_changed\n");
}
        
void Media_Player::state_changed(VN_PLAYER_STREAM_STATE state, const vn_player_result_status_t *result_status, void *client_data)
{
    Media_Player *p = static_cast<Media_Player*>(client_data);

    struct timeval tv;
    gettimeofday(&tv, 0);
//   if (result_status->error_code)
    printf("[%d] %ld.%ld State_changed: %s (%d). result: %s(%d)\n", p->player_num_, tv.tv_sec, tv.tv_usec,
           vn_player_state_to_str(state), state, result_status->error_str, result_status->error_code);


     if (state == OPENED)
     {
         p->play();
     }
     
/*
        if (result_status->error_code && result_status->error_code < 10000)
        {
           char err_buf[512];
           strerror_r(result_status->error_code, err_buf, sizeof(err_buf));
           printf("ERROR: %s\n", err_buf);
        }
*/
}
       
void Media_Player::new_stream(const vn_player_stream_info_t *stream_info, void *client_data)
{
    printf("New stream: %s. Duration: %d ms\n", stream_info->type == AUDIO ? "audio" : "video", stream_info->duration);
}
        
void Media_Player::stream_removed(const vn_player_stream_info_t *stream_info, void *client_data)
{
//	printf("stream_removed\n");
}

void Media_Player::msg_log(const char *msg, void *client_data)
{
    Media_Player *p = static_cast<Media_Player*>(client_data);

    if (verbose)
        printf("[%d] %s", p->player_num_, msg);
}

void print_usage(char* argv[], int exit_code)
{
   fprintf(stderr, "Usage: %s options\nOptions:\n", argv[0]);
   fprintf(stderr,
           "-u, --url              RTSP URL        \n"
           "-b, --buffer-len       Playout buffer length (in ms)\n"
           "-x, --use-hw-decoder   Use hardware decoder\n"
           "-p, --use-rtp-over-tcp Force RTP-over-TCP\n"
           "-v, --verbose        Be verbose\n"
           "-h, --help           Show this help\n"
           "Example: ./simple_play -u \"rtsp://10.0.0.1:8554/xmedia?dev=101&authorizationid=0\"\n"
    );

   exit(exit_code);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


Media_Player *player = 0;

// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
//                    LPSTR lpCmdLine, int iCmdShow)
int main(int argc, char* argv[])
{
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;

    int next_option;
    const char* const short_options = "hu:b:vxp";
    const struct option long_options[] = {
    {"help",             0, NULL, 'h'},
    {"verbose",          0, NULL, 'v'},
    {"url",              1, NULL, 'u'},
    {"buffer-len",       1, NULL, 'b'},
    {"use-hw-decoder",   1, NULL, 'x'},
    {"use-rtp-over-tcp", 0, NULL, 'p'},
    {NULL,               0, NULL, 0}};

    std::string url;

    do {
        next_option = getopt_long(argc, argv, short_options, long_options, NULL);

        switch (next_option)
        {
            case 'h': /* -h or --help */
                print_usage(argv, EXIT_SUCCESS);
                
	    case 'p':
               use_rtp_over_tcp = true;  
               break;
            case 'u':
                if (optarg)
                    url = optarg;
                else print_usage(argv, EXIT_FAILURE);                
                break;

           case 'v':
               verbose = true;
               break;

           case 'x':
               use_hw_decoder = true;
               break;

           case 'b':
               if (optarg)
                   buffer_len = atol(optarg);
	       else print_usage(argv, EXIT_FAILURE);
               break;

            case '?': /* The user specified an invalid option*/
               print_usage(argv, EXIT_FAILURE);

            case -1: /* Done with options */
                break;
        }
    } while (next_option != -1);
   
    if (url.empty())
        print_usage(argv, EXIT_FAILURE);

    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
    // register window class
    wc.style = CS_OWNDC | CS_HREDRAW|CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = GetModuleHandle(0);//hInstance;
    wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "D3dSample";
    RegisterClass( &wc );
	
    // create main window
    hWnd = CreateWindow( 
        "D3dSample", "D3dSample", 
        WS_CAPTION | WS_TILEDWINDOW| WS_VISIBLE,
        0, 0, 800, 600,
        NULL, NULL, /*hInstance*/GetModuleHandle(0), NULL );

    //ShowWindow(hWnd, 3);
    
    player = new Media_Player(0, hWnd);
    player->open(url.c_str());

    while( GetMessage( &msg, NULL, 0, 0 ) > 0 )
        DispatchMessage( &msg );

    delete player;
    
    // destroy the window explicitly
    DestroyWindow( hWnd );	
    return msg.wParam;
}

// Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{	
    switch (message)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            /*HDC hdc = */BeginPaint(hwnd, &ps);
            if (player)
            {
                struct vn_image_calibration_t ic = {brightness_scale, contrast_scale, saturation_scale};
                struct vn_image_pan_zoom_t pz = {pan_x, pan_y, scale, zoom_filter};
                if (pan_x < 0)
                    pan_x = 0;
                if (pan_y < 0)
                    pan_y = 0;
                if (pan_x > 1)
                    pan_x = 1;
                if (pan_y > 1)
                    pan_y = 1;
                
                if (vn_renderer_paint(player->get_renderer(), fill_mode, &ic, zoom_mode ? &pz : 0) < 0)
                {
                    fprintf(stderr, "vn_renderer_paint < 0\n");
                }
            }
        
            // Graphics graphics(hdc);
            // Pen      pen(Color(255, 0, 0, 255));
            // graphics.DrawLine(&pen, 0, 0, 200, 100);
            
            EndPaint(hwnd, &ps);
            return 0;
        }

        case WM_CREATE:
            return 0;
		
	case WM_CLOSE:
            PostQuitMessage( 0 );
            return 0;
		
	case WM_DESTROY:
            return 0;
		
	case WM_KEYDOWN:
            switch ( wParam )
            {
                case VK_SPACE:
                    if (player->is_paused()) player->resume(); else player->pause();
                    return 0;

                case VK_UP:
                    if (zoom_mode)
                        pan_y -= 0.1;
                    else
                        contrast_scale += 0.01;
                    InvalidateRect(hwnd, 0, FALSE);

                    return 0;

                case VK_DOWN:
                    if (zoom_mode)
                        pan_y += 0.1;
                    else
                        contrast_scale -= 0.01;
                    InvalidateRect(hwnd, 0, FALSE);

                    return 0;

                case VK_LEFT:
                     if (zoom_mode)
                        pan_x -= 0.1;
                     else
                         brightness_scale -= 0.01;
                     InvalidateRect(hwnd, 0, FALSE);
                    return 0;

                case VK_RIGHT:
                     if (zoom_mode)
                        pan_x += 0.1;
                     else
                         brightness_scale += 0.01;
                     InvalidateRect(hwnd, 0, FALSE);
                    return 0;

                case 189/*-*/:
                    saturation_scale -= 0.01;
                    InvalidateRect(hwnd, 0, FALSE);
                    return 0;

                case 187/*+*/:
                    saturation_scale += 0.01;
                    InvalidateRect(hwnd, 0, FALSE);
                    return 0;

                case 82 /*'r'*/:
                    fill_mode = (fill_mode == VN_IMAGE_FILL_ASPECT ? VN_IMAGE_FILL_STRETCH : VN_IMAGE_FILL_ASPECT);
                    InvalidateRect(hwnd, 0, FALSE);
                    return 0;

                case 90 /*z*/:
                {
                    if (zoom_mode)
                    {
                        if (scale >= 10.0)
                        {
                            pan_x = 0.5f;
                            pan_y = 0.5f;
                            scale = 1.0f;
                            zoom_mode  = false;
                        }
                        else
                        {
                            scale += .5f;                            
                        }
                        InvalidateRect(hwnd, 0, FALSE);
                    }
                    else
                    {
                        pan_x = 0.5f;
                        pan_y = 0.5f;
                        scale = 2.0f;
                        zoom_mode  = true;
                        InvalidateRect(hwnd, 0, FALSE);
                    }
                    return 0;;
                }
                case 70 /*f*/:
                {
                    zoom_filter = (zoom_filter == VN_IMAGE_ZOOM_BILENEAR ? VN_IMAGE_ZOOM_NONE : VN_IMAGE_ZOOM_BILENEAR);
                    InvalidateRect(hwnd, 0, FALSE);
                    return 0;
                }
                    
//		case VK_ESCAPE:
                    //PostQuitMessage(0);
//                    return 0;

                default:
                    printf("pressed %d key\n", wParam);
                    break;
                    
            }
            return 0;
	
	default:
            return DefWindowProc( hwnd, message, wParam, lParam );
            
    }
	
}
#else
#include <stdio.h>
int main(int argc, char* argv[])
{
    fprintf(stderr, "Windows only support D3D\n");
    return 1;
}
#endif



