/* -*- c++ -*- */
/*
 * Copyright 2006,2010,2012 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio 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 3, or (at your option)
 * any later version.
 *
 * GNU Radio 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 GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "sink_uc_impl.h"
#include <gr_io_signature.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
#include <stdexcept>
#include <string.h>


namespace gr {
  namespace video_sdl {

    sink_uc::sptr
    sink_uc::make(double framerate, int width, int height,
		  unsigned int format, int dst_width, int dst_height)
    {
      return gnuradio::get_initial_sptr
	(new sink_uc_impl(framerate, width, height, format, dst_width, dst_height));
    }

    sink_uc_impl::sink_uc_impl(double framerate, int width, int height,
			       unsigned int format, int dst_width, int dst_height)
      : gr_sync_block("video_sdl_sink_uc",
		      gr_make_io_signature(1, 3, sizeof(unsigned char)),
		      gr_make_io_signature(0, 0, 0)),
	d_chunk_size(width*height), d_framerate(framerate),
	d_wanted_frametime_ms(0), d_width(width), d_height(height),
	d_dst_width(dst_width), d_dst_height(dst_height),
	d_format(format),
	d_current_line(0), d_screen(NULL), d_image(NULL),
	d_avg_delay(0.0), d_wanted_ticks(0)
    {
      if(framerate <= 0.0)
	d_wanted_frametime_ms = 0; //Go as fast as possible
      else
	d_wanted_frametime_ms = (int)(1000.0/framerate);

      if(dst_width < 0)
	d_dst_width = d_width;

      if(dst_height < 0)
	d_dst_height = d_height;

      if(0==format)
	d_format= IMGFMT_YV12;

      atexit(SDL_Quit); //check if this is the way to do this
      if(SDL_Init(SDL_INIT_VIDEO) < 0) {
	std::cerr << "video_sdl::sink_uc: Couldn't initialize SDL:"
		  << SDL_GetError() << " \n SDL_Init(SDL_INIT_VIDEO) failed\n";
	throw std::runtime_error ("video_sdl::sink_uc");
      }

      /* accept any depth */
      d_screen = SDL_SetVideoMode(dst_width, dst_height, 0,
				  SDL_SWSURFACE|SDL_RESIZABLE|SDL_ANYFORMAT);//SDL_DOUBLEBUF |SDL_SWSURFACE| SDL_HWSURFACE||SDL_FULLSCREEN

      if(d_screen == NULL) {
	std::cerr << "Unable to set SDL video mode: " << SDL_GetError()
		  <<"\n SDL_SetVideoMode() Failed \n";
	exit(1);
      }

      if(d_image) {
	SDL_FreeYUVOverlay(d_image);
      }

      /* Initialize and create the YUV Overlay used for video out */
      if(!(d_image = SDL_CreateYUVOverlay(d_width, d_height, SDL_YV12_OVERLAY, d_screen))) {
	std::cerr << "SDL: Couldn't create a YUV overlay: \n"<< SDL_GetError() <<"\n";
	throw std::runtime_error("video_sdl::sink_uc");
      }

      printf("SDL screen_mode %d bits-per-pixel\n",
	     d_screen->format->BitsPerPixel);
      printf("SDL overlay_mode %i \n",
	     d_image->format);

      d_chunk_size = std::min(1,16384/width); //width*16;
      d_chunk_size = d_chunk_size*width;
      //d_chunk_size = (int)(width);
      set_output_multiple(d_chunk_size);

      /* Set the default playback area */
      d_dst_rect.x = 0;
      d_dst_rect.y = 0;
      d_dst_rect.w = d_dst_width;
      d_dst_rect.h = d_dst_height;
      //clear the surface to grey

      if(SDL_LockYUVOverlay(d_image)) {
	std::cerr << "SDL: Couldn't lock YUV overlay: \n" << SDL_GetError() << "\n";
	throw std::runtime_error ("video_sdl::sink_uc");
      }

      memset(d_image->pixels[0], 128, d_image->pitches[0]*d_height);
      memset(d_image->pixels[1], 128, d_image->pitches[1]*d_height/2);
      memset(d_image->pixels[2], 128, d_image->pitches[2]*d_height/2);
      SDL_UnlockYUVOverlay(d_image);
    }

    sink_uc_impl::~sink_uc_impl()
    {
      SDL_Quit();
    }

    void
    sink_uc_impl::copy_line_pixel_interleaved(unsigned char *dst_pixels_u,
					      unsigned char *dst_pixels_v,
					      const unsigned char *src_pixels,
					      int src_width)
    {
      for(int i=0;i<src_width;i++) {
	dst_pixels_u[i]=src_pixels[i*2];
	dst_pixels_v[i]=src_pixels[i*2+1];
      }
    }

    void
    sink_uc_impl::copy_line_line_interleaved(unsigned char *dst_pixels_u,
					     unsigned char *dst_pixels_v,
					     const unsigned char *src_pixels,
					     int src_width)
    {
      memcpy(dst_pixels_u, src_pixels, src_width);
      memcpy(dst_pixels_v, src_pixels+src_width, src_width);
    }

    void
    sink_uc_impl::copy_line_single_plane(unsigned char *dst_pixels,
					 const unsigned char *src_pixels,
					 int src_width)
    {
      memcpy(dst_pixels, src_pixels, src_width);
    }

    void
    sink_uc_impl::copy_line_single_plane_dec2(unsigned char *dst_pixels,
					      const unsigned char *src_pixels,
					      int src_width)
    {
      for(int i = 0,j = 0; i < src_width; i += 2, j++) {
	dst_pixels[j] = (unsigned char)src_pixels[i];
      }
    }

    int
    sink_uc_impl::copy_plane_to_surface(int plane,int noutput_items,
					const unsigned char * src_pixels)
    {
      const int first_dst_plane = (12==plane || 1122 == plane) ? 1 : plane;
      const int second_dst_plane = (12==plane || 1122 == plane) ? 2 : plane;
      int current_line = (0 == plane) ? d_current_line : d_current_line/2;

      unsigned char *dst_pixels = (unsigned char*)d_image->pixels[first_dst_plane];
      dst_pixels =& dst_pixels[current_line*d_image->pitches[first_dst_plane]];

      unsigned char * dst_pixels_2 = (unsigned char *)d_image->pixels[second_dst_plane];
      dst_pixels_2 =& dst_pixels_2[current_line*d_image->pitches[second_dst_plane]];

      int src_width = (0 == plane || 12 == plane || 1122 == plane) ? d_width : d_width/2;
      int noutput_items_produced = 0;
      int max_height = (0 == plane)? d_height-1 : d_height/2-1;

      for(int i = 0; i < noutput_items; i += src_width) {
        //output one line at a time
        if(12 == plane) {
          copy_line_pixel_interleaved(dst_pixels, dst_pixels_2, src_pixels, src_width);
          dst_pixels_2 += d_image->pitches[second_dst_plane];
        }
        else if(1122 == plane) {
	  copy_line_line_interleaved(dst_pixels, dst_pixels_2, src_pixels, src_width);
	  dst_pixels_2 += d_image->pitches[second_dst_plane];
	  src_pixels += src_width;
	}
        else if(0 == plane)
          copy_line_single_plane(dst_pixels, src_pixels, src_width);
        else /* 1==plane || 2==plane*/
          copy_line_single_plane_dec2(dst_pixels, src_pixels, src_width); //decimate by two horizontally

        src_pixels += src_width;
        dst_pixels += d_image->pitches[first_dst_plane];
        noutput_items_produced+=src_width;
        current_line++;

        if(current_line > max_height) {
          //Start new frame
          //TODO, do this all in a seperate thread
          current_line = 0;
          dst_pixels = d_image->pixels[first_dst_plane];
          dst_pixels_2 = d_image->pixels[second_dst_plane];
          if(0 == plane) {
            SDL_DisplayYUVOverlay(d_image, &d_dst_rect);
            //SDL_Flip(d_screen);
            unsigned int ticks = SDL_GetTicks();//milliseconds
            d_wanted_ticks += d_wanted_frametime_ms;
            float avg_alpha = 0.1;
            int time_diff = d_wanted_ticks-ticks;
            d_avg_delay = time_diff*avg_alpha + d_avg_delay*(1.0-avg_alpha);
          }
        }
      }

      if(0==plane)
	d_current_line=current_line;

      return noutput_items_produced;
    }

    int
    sink_uc_impl::work(int noutput_items,
		       gr_vector_const_void_star &input_items,
		       gr_vector_void_star &output_items)
    {
      unsigned char *src_pixels_0,*src_pixels_1,*src_pixels_2;
      int noutput_items_produced = 0;
      int plane;
      int delay = (int)d_avg_delay;

      if(0 == d_wanted_ticks)
	d_wanted_ticks=SDL_GetTicks();

      if(delay > 0)
	SDL_Delay((unsigned int)delay);//compensate if running too fast

      if(SDL_LockYUVOverlay(d_image)) {
	return 0;
      }

      switch(input_items.size ()) {
      case 3:		// first channel=Y, second channel is  U , third channel is V
	src_pixels_0 = (unsigned char *) input_items[0];
	src_pixels_1 = (unsigned char *) input_items[1];
	src_pixels_2 = (unsigned char *) input_items[2];
	for(int i = 0; i < noutput_items; i += d_chunk_size) {
	  copy_plane_to_surface (1,d_chunk_size, src_pixels_1);
	  copy_plane_to_surface (2,d_chunk_size, src_pixels_2);
	  noutput_items_produced+=copy_plane_to_surface(0,d_chunk_size, src_pixels_0);
	  src_pixels_0 += d_chunk_size;
	  src_pixels_1 += d_chunk_size;
	  src_pixels_2 += d_chunk_size;
	}
	break;
      case 2:
	if(1) {//if(pixel_interleaved_uv)
	  // first channel=Y, second channel is alternating pixels U and V
	  src_pixels_0 = (unsigned char *) input_items[0];
	  src_pixels_1 = (unsigned char *) input_items[1];
	  for(int i = 0; i < noutput_items; i += d_chunk_size) {
	    copy_plane_to_surface(12, d_chunk_size/2, src_pixels_1);
	    noutput_items_produced += copy_plane_to_surface(0, d_chunk_size, src_pixels_0);
	    src_pixels_0 += d_chunk_size;
	    src_pixels_1 += d_chunk_size;
	  }
	}
	else {
	  // first channel=Y, second channel is alternating lines U and V
	  src_pixels_0 = (unsigned char*)input_items[0];
	  src_pixels_1 = (unsigned char*)input_items[1];
	  for(int i = 0; i < noutput_items; i += d_chunk_size) {
	    copy_plane_to_surface (1222,d_chunk_size/2, src_pixels_1);
	    noutput_items_produced += copy_plane_to_surface(0, d_chunk_size, src_pixels_0);
	    src_pixels_0 += d_chunk_size;
	    src_pixels_1 += d_chunk_size;
	  }
	}
	break;
      case 1:		// grey (Y) input
	/* Y component */
	plane=0;
	src_pixels_0 = (unsigned char*)input_items[plane];
	for(int i = 0; i < noutput_items; i += d_chunk_size) {
	  noutput_items_produced += copy_plane_to_surface(plane, d_chunk_size, src_pixels_0);
	  src_pixels_0 += d_chunk_size;
	}
	break;
      default: //0 or more then 3 channels
	std::cerr << "video_sdl::sink_uc: Wrong number of channels: ";
	std::cerr <<"1, 2 or 3 channels are supported.\n  Requested number of channels is "
		  << input_items.size () <<"\n";
	throw std::runtime_error("video_sdl::sink_uc");
      }

      SDL_UnlockYUVOverlay(d_image);
      return noutput_items_produced;
    }

  } /* namespace video_sdl */
} /* namespace gr */