class WebcamVideoStream < Qt::Object

  private_class_method :new

  @@cached_stream = nil

  attr_reader :resolutions,
              :resolution,
              :device,
              :window_id

  private

  def initialize(dev, parent = nil)
    super(parent)

    create_pipeline

    @webcam_src.device = dev
    @device = dev

    find_resolutions
  end

  def create_webcam_bin
    @webcam_src = Gst::ElementFactory.make('v4l2src')

    @webcam_filter = Gst::ElementFactory.make('capsfilter')

    tee = Gst::ElementFactory.make('tee')

    @webcam_bin = create_bin(@webcam_src, @webcam_filter, tee)

    @webcam_bin.add_pad Gst::GhostPad.new('src1', tee.get_request_pad('src1'))
    @webcam_bin.add_pad Gst::GhostPad.new('src2', tee.get_request_pad('src2'))
  end

  def create_video_display_bin
    queue = Gst::ElementFactory.make('queue')
    
    tee = Gst::ElementFactory.make('tee')

    @video_display_bin = create_bin(queue, create_pixelformat_filter, tee)

    @video_display_bin.add_pad Gst::GhostPad.new('sink', queue.get_pad('sink'))

    @video_sinks = []

    ViewWidgetFactory.widgets.each do |vw|
      queue = Gst::ElementFactory.make('queue')

      video_scale = Gst::ElementFactory.make('videoscale')

      if $settings.accelerate_video_render
        xwindow_sink = Gst::ElementFactory.make('xvimagesink')
      else
        xwindow_sink = Gst::ElementFactory.make('ximagesink')
      end

      @video_sinks << xwindow_sink

      xwindow_sink.set_xwindow_id_with_buswatch(@pipeline.bus, vw.window_id)

      @video_display_bin.add(queue, video_scale, xwindow_sink)
      tee >> queue >> video_scale >> xwindow_sink
    end
    
  end

  def create_photo_save_bin
    queue = Gst::ElementFactory.make('queue')

    pixelformat = { 'name' => 'video/x-raw-rgb',
                    'bpp' => 24,
                    'depth' => 24 }

    @photo_sink = Gst::ElementFactory.make('fakesink')

    @photo_save_bin = create_bin(queue, create_pixelformat_filter(pixelformat), @photo_sink)
    
    @photo_save_bin.add_pad Gst::GhostPad.new('sink', queue.get_pad('sink'))
  end

  def create_pipeline
    @pipeline = Gst::Pipeline.new

    create_webcam_bin
    create_video_display_bin
    create_photo_save_bin

    @pipeline.add(@webcam_bin, @video_display_bin, @photo_save_bin)

    @webcam_bin >> @video_display_bin
    @webcam_bin >> @photo_save_bin


  end

  def create_pixelformat_filter(filter_hash = nil)
    colorspace_filter = Gst::ElementFactory.make('ffmpegcolorspace')
    return colorspace_filter unless filter_hash

    caps_filter = Gst::ElementFactory.make('capsfilter')
    caps_filter.caps = create_caps(filter_hash)

    bin = create_bin(colorspace_filter, caps_filter)
    bin.add_pad Gst::GhostPad.new('sink', colorspace_filter.get_pad('sink'))
    bin.add_pad Gst::GhostPad.new('src', caps_filter.get_pad('src'))

    return bin
  end

  def create_caps(*filters)
    caps_string = ''

    filters.each do |filter|
      caps_string << ';' unless caps_string.empty?
      caps_string << filter['name']
      filter.each { |key, val| caps_string << ',' << key << '=' << val.to_s unless key == 'name'}
    end

    return Gst::Caps.parse(caps_string)
  end

  def create_bin(*elements)
    bin = Gst::Bin.new
    bin.add(*elements)

    elements.each_index do |index|
      elements[index] >> elements[index.next] if elements[index.next]
    end

    return bin
  end

  def find_resolutions
    @webcam_src.play

    @resolutions = []

    @webcam_src.get_pad('src').caps.each do |struct|
      if struct.name.include?('rgb') or struct.name.include?('yuv')
        @pixelformat = struct.name unless @pixelformat
      else
        next
      end

      next unless @pixelformat == struct.name

      res = [struct['width'], struct['height']]
      @resolutions.push(res) unless @resolutions.include?(res)
    end

    @resolutions.sort!

    set_resolution
  end

  public

  def play
    @pipeline.play
  end

  def pause
    @pipeline.pause
  end

  def stop
    @pipeline.stop
    @pipeline.get_state
  end

  def save_frame(file_name, format, quality)
    data = @photo_sink.last_buffer.data
    image_24 = Qt::Image.new(data, @resolution[0], @resolution[1],Qt::Image::Format_RGB888)
    image_32 = image_24.convert_to_format(Qt::Image::Format_ARGB32)
    image_32.save(file_name, format, quality)

    image_24.dispose
    image_32.dispose
    GC.start
  end

  def set_resolution(resolution = nil)
    resolution ||= @resolutions.max
    return false if @resolution == resolution
    @resolution = resolution

    @pipeline.ready

    caps_hash = { 'name' => @pixelformat,
                  'width' => resolution[0],
                  'height' => resolution[1] }
    @webcam_filter.caps = create_caps(caps_hash)

    ViewWidgetFactory.widgets.each {|vw| vw.update}

    return true
  end

  def self.destroy
    return unless @@cached_stream
    @@cached_stream.stop
    @@cached_stream = nil
  end

  def self.instance(dev = nil)
    return nil unless $dev_pool.webcam?

    dev = $settings.webcam['device'] unless dev

    return @@cached_stream if (@@cached_stream and @@cached_stream.device == dev)

    WebcamVideoStream.destroy
    @@cached_stream = new(dev)
  end

  def self.exist?
    return @@cached_stream ? true : false
  end

end
