#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <unistd.h>
#include <pthread.h>
#endif

#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"

#ifdef WIN32
#define sleep(seconds) Sleep(seconds * 1000)
#else
unsigned int (*_sleep_)(unsigned int) = &sleep;
#define sleep(seconds) _sleep_(seconds)
#endif

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

class Media_Player
{
public:
    Media_Player(unsigned player_num);
    ~Media_Player();

    void open(const char *url);
    void play();
    void pause();
    void resume();
    void speed(float);
    void jump(time_t);
    void step_mode(bool value);
    void start_recording();
    void end_recording();
    void mute_audio(bool value);
	VN_PLAYER_STREAM_STATE state;
	std::string filename;
	unsigned record_time;
	unsigned record_loop;

private:
   static void new_frame(const vn_player_frame_t*, const vn_player_cache_boundaries_t*, int buf_num, 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 recording_status_changed(const vn_player_recording_status_t* status, void* client_data);
   
   static void msg_log(const char *msg, void *client_data);

private:
#ifdef WIN32
	static DWORD WINAPI stop_record_func(LPVOID arg);
#else
	static void* stop_record_func(void* arg);
#endif
	void stop_recording();

    struct vn_player_config_t conf_;
    struct vn_player_context_t *ctx_;
    unsigned frame_num_;
    unsigned player_num_;
	bool recording_thread_active_;
	bool recording_started_;
	bool destroying_;
#ifdef WIN32
	HANDLE recording_thread_handle_;
#else
	pthread_t recording_thread_;
#endif
};

Media_Player::Media_Player(unsigned player_num)
    : state(IDLE), record_time(5), record_loop(0), frame_num_(0), player_num_(player_num)
	, recording_thread_active_(false), recording_started_(false), destroying_(false)
#ifdef WIN32
	, recording_thread_handle_(NULL)
#endif
{
    memset(&conf_, 0, sizeof(conf_));
    conf_.cache_size = 0;

    if (disable_decoder)
        conf_.decoder_type = DECODER_NONE;
    else
        conf_.decoder_type = use_hw_decoder ? DECODER_HW : DECODER_SW;
    conf_.pixel_format = PIX_RGB24;
    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.recording_status_changed = recording_status_changed;
    callbacks.msg_log        = msg_log;

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

Media_Player::~Media_Player()
{
	destroying_ = true;
	if (recording_thread_active_)
	{
#ifdef WIN32
		WaitForSingleObject(recording_thread_handle_, INFINITE);
#else
		pthread_join(recording_thread_, NULL);
#endif
	}
    vn_player_destroy(ctx_);
}

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

void Media_Player::play()
{
    vn_player_play(ctx_);
}

void Media_Player::pause()
{
    vn_player_pause(ctx_);
}

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

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

void Media_Player::mute_audio(bool value)
{
    vn_player_mute_audio(ctx_, value);
}

void Media_Player::speed(float value)
{
    vn_player_set_playback_speed(ctx_, value);
}

void Media_Player::jump(time_t value)
{
    vn_player_move_to_ts(ctx_, value);
}

#ifdef WIN32
DWORD WINAPI Media_Player::stop_record_func(LPVOID arg)
#else
void* Media_Player::stop_record_func(void* arg)
#endif
{
	Media_Player* player = (Media_Player*)arg;
	player->stop_recording();
	return 0;
}

void Media_Player::stop_recording()
{
	recording_thread_active_ = true;
	sleep(record_time);
	end_recording();
	bool startAgain = false;
	if (record_loop && !destroying_)
	{
		sleep(record_loop);
		if (state == PLAY_FROM_SERVER || state == PLAY_FROM_BUFFER || state == BUFFERING)
		{
			startAgain = true;
		}
		else
		{
			recording_started_ = false; // Will be started on state change
		}
	}
	recording_thread_active_ = false;
#ifdef WIN32	
	CloseHandle(recording_thread_handle_);
	recording_thread_handle_ = NULL;
#endif
	if (startAgain)
	{
		start_recording();
	}
}

void Media_Player::start_recording()
{
	if (recording_thread_active_)
	{
		return;
	}
	
	vn_player_start_recording(ctx_, filename.c_str(), NULL);

#ifdef WIN32
	recording_thread_handle_ = CreateThread(NULL, 0, &stop_record_func, this, 0, NULL);
	if (recording_thread_handle_ == NULL)
	{
		printf("Could not create stop thread\n");
	}
#else
	if (pthread_create(&recording_thread_, 0, stop_record_func, this))
	{
		printf("Could not create stop thread\n");
	}
#endif
}

void Media_Player::end_recording()
{
	vn_player_end_recording(ctx_);
}

static std::string unixtsToDatetime(time_t unixts)
{
    char buf[80] = {0};

#ifdef WIN32
    // gmtime is threadsafe in windows because it uses TLS
    struct tm *ptm = gmtime(&unixts);
    strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", ptm);
#else
    struct tm ptm = {};
    gmtime_r(&unixts, &ptm);
    strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", &ptm);
#endif

    return buf;
}

void Media_Player::new_frame(const vn_player_frame_t *frame, const vn_player_cache_boundaries_t *bounds, int buf_num, 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);
        // int mute = rand() % 2;
        // printf("[%d] Mute: %d\n", p->player_num_, mute);
        // p->mute_audio(mute);
    }

    // if (p->frame_num_ % 15 == 0)
    // {
    //     int s = rand() % 32 + 1;

    //     if ((rand() % 2))
    //         s *= -1;
    //     if (s == -1.0f)
    //         s = -2.0f;
    //     printf("[%d] Change speed to %d\n", p->player_num_, s);
    //     p->speed(s);
    // }

    // if (p->frame_num_ % 15 == 0)
    // {
    //     int offset = rand() % 120;

    //     if ((rand() % 2))
    //         offset *= -1;

    //     time_t ts = frame->captured_time->tv_sec + offset;
    //     printf("[%d] Change ts to %ld\n", p->player_num_, ts);
    //     p->pause();
    //     p->jump(ts);
    //     p->resume();
    // }

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

    
}

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);


    p->state = state;

    if (state == OPENED)
    {
        p->play();
    }
     
    if (state == PLAY_FROM_SERVER && !p->recording_started_ && !p->filename.empty())
    {
        p->recording_started_ = true;
        p->start_recording();
    }

    if (result_status->error_code == EENDOFCHUNK && fast_play)
    {
        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);
	if (verbose && stream_info->metadata)
	{
		printf("Metadata:\n");
		for (int i = 0; stream_info->metadata[i] != NULL; i++)
		{
			if (stream_info->metadata[i]->key && stream_info->metadata[i]->value)
			{
				printf("%s: %s\n", (char*)stream_info->metadata[i]->key, (char*)stream_info->metadata[i]->value);
			}
		}
	}
}
        
void Media_Player::stream_removed(const vn_player_stream_info_t *stream_info, void *client_data)
{
//	printf("stream_removed\n");
}

void Media_Player::recording_status_changed(const vn_player_recording_status_t* status, void* client_data)
{
	const char* statusText = NULL;
	switch (status->status)
	{
		case RECORDING_IDLE: statusText = "IDLE"; break;
		case RECORDING_STARTED: statusText = "STARTED"; break;
		case RECORDING_ENDING: statusText = "ENDING"; break;
		case RECORDING_ENDED: statusText = "ENDED"; break;
		case RECORDING_ERROR: statusText = "ERROR"; break;
		case RECORDING_SKIPPED: statusText = "SKIPPED"; break;
	}
	printf("Recording status: %s.", statusText);
	if (status->progress != 0)
	{
		printf(" Progress: %d.", status->progress);
	}
	if (status->error_str)
	{
		printf(" Error: %s", status->error_str);
	}
	printf("\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"
           "-n, --players-num      Number of concurrent players (default 1)\n"
           "-b, --buffer-len       Playout buffer length (in ms)\n"
           "-t, --playback-time    Time of seconds for each iteration (defalut 10secs)\n"
           "-i, --iterations-num   Number of iterations (default infinity)\n"
           "-x, --use-hw-decoder   Use hardware decoder\n"
           "-p, --use-rtp-over-tcp Force RTP-over-TCP\n"
           "-d, --disable-decoding Disable decoding at all. -x does no matter\n"
           "-f, --filename         File name to record video to\n"
           "-r, --record-time      Time to record to file (in seconds) (default 5secs)\n"
           "-l, --record-loop      Recording loop interval (in seconds) (default 0secs - no loop)\n"
           "-y, --fast-play        Archive fast playback\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 4 -t 30\n"
    );

   exit(exit_code);
}

int main(int argc, char* argv[])
{
    int next_option;
    const char* const short_options = "hu:n:t:b:vxi:pdf:r:l:y";
    const struct option long_options[] = {
    {"help",             0, NULL, 'h'},
    {"verbose",          0, NULL, 'v'},
    {"url",              1, NULL, 'u'},
    {"players-num",      1, NULL, 'n'},
    {"playback-time",    1, NULL, 't'},
    {"buffer-len",       1, NULL, 'b'},
    {"iterations-num",   1, NULL, 'i'},
    {"use-hw-decoder",   1, NULL, 'x'},
    {"use-rtp-over-tcp", 0, NULL, 'p'},
    {"disable-decoding", 0, NULL, 'd'},
    {"filename", 		 1, NULL, 'f'},
    {"record-time",		 1, NULL, 'r'},
    {"record-loop",		 1, NULL, 'l'},
    {"fast-play",		 0, NULL, 'y'},
    {NULL,               0, NULL, 0}};

    std::string url;
    unsigned players_num = 1;
    unsigned playback_time = 10;
    unsigned iterations_num = ~0;
	std::string filename;
	unsigned record_time = 5;
	unsigned record_loop = 0;

    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 'n':
                if (optarg)
                    players_num = atol(optarg);
                else print_usage(argv, EXIT_FAILURE);
                break;

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

           case 't':
               if (optarg)
                   playback_time = atol(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 'd':
                disable_decoder = true;
                break;
               
            case 'f':
                if (optarg)
                    filename = optarg;
                else print_usage(argv, EXIT_FAILURE);                
                break;
               
           case 'r':
               if (optarg)
                   record_time = atol(optarg);
               else print_usage(argv, EXIT_FAILURE);
               break;

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

            case 'y':
                fast_play = true;
               break;

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

            case -1: /* Done with options */
                break;
        }
    } while (next_option != -1);

    srand(time(0));

    if (url.empty())
        print_usage(argv, EXIT_FAILURE);

   for (unsigned i = 1; i <= iterations_num; i++)
   {      
       printf("Iteration: %d\n", i);
       Media_Player *player[256];
       for (unsigned p = 0; p < players_num; p++)
       {
           player[p] = new Media_Player(p);
		   if (!filename.empty())
		   {
			    std::string fname = filename;
				if (players_num > 1)
				{
					size_t pos = filename.find_last_of('.');
					std::string name = pos != std::string::npos ? filename.substr(0, pos) : filename;
					std::string ext = pos != std::string::npos ? filename.substr(pos) : "";
					char num[64];
					snprintf(num, sizeof(num), "%d", p);
					fname = name + num + ext;
				}
				player[p]->filename = fname;
				player[p]->record_time = record_time;
				player[p]->record_loop = record_loop;
		   }
           player[p]->open(url.c_str());
       }
	  sleep(playback_time);
      for (unsigned p = 0; p < players_num; p++)
      {             
         delete player[p];
      }
   }

sleep(3);
   return 0;
}
