/****************************************************************************
    
    scopewidget.hpp - an Oscilloscope widget for gtkmm
    
    Copyright (C) 2005  Lars Luthman <larsl@users.sourceforge.net>
    
    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 01222-1307  USA

****************************************************************************/

#include <cmath>
#include <cstring>
#include <iostream>

#include "scopewidget.hpp"


ScopeWidget::ScopeWidget(shared_data_t* shared_data)
  : DrawingArea(), m_shared_data(shared_data), m_buffer1(NULL),
    m_buffer2(NULL), m_frames_read(0), m_trigger_level(0), 
    m_trigger_up(true), m_y1_scale(1), m_y2_scale(1), m_y1_offset(0), 
    m_y2_offset(0), m_x_offset(0), m_frozen(false), m_additive(false), 
    m_scope_state(WaitingForTrigger), m_samples_read(0), m_timeout(30),
    m_window_size(30), m_sample_rate(0), m_channel1(NULL), m_channel2(NULL) {
  
  m_sample_rate = m_shared_data->sample_rate;
  m_channel1 = new LADSPA_Data[m_sample_rate];
  m_channel2 = new LADSPA_Data[m_sample_rate];
  memset(m_channel1, 0, m_sample_rate * sizeof(LADSPA_Data));
  memset(m_channel2, 0, m_sample_rate * sizeof(LADSPA_Data));
  m_buffer1 = &(m_shared_data->channel1.rb);
  m_buffer2 = &(m_shared_data->channel2.rb);

  m_colormap = Colormap::get_system();
  m_bg_color.set_rgb(10000, 10000, 10000);
  m_fg_color.set_rgb(0, 60000, 0);
  m_fg_color2.set_rgb(60000, 0, 0);
  m_trig_color.set_rgb(60000, 60000, 0);
  m_grid_color1.set_rgb(0, 30000, 30000);
  m_grid_color2.set_rgb(0, 15000, 15000);
  m_colormap->alloc_color(m_bg_color);
  m_colormap->alloc_color(m_fg_color);
  m_colormap->alloc_color(m_fg_color2);
  m_colormap->alloc_color(m_trig_color);
  m_colormap->alloc_color(m_grid_color1);
  m_colormap->alloc_color(m_grid_color2);

  add_events(BUTTON_PRESS_MASK);

  signal_timeout().connect(mem_fun(*this, &ScopeWidget::read_data), m_timeout);
}


ScopeWidget::~ScopeWidget() {
  delete [] m_channel1;
  delete [] m_channel2;
}


void ScopeWidget::set_trigger_level(LADSPA_Data trigger_level) {
  m_trigger_level = trigger_level;
  queue_draw();
}


void ScopeWidget::set_trigger_direction(bool up) {
  m_trigger_up = up;
}


void ScopeWidget::set_y1_scale(double scale) {
  m_y1_scale = scale;
  queue_draw();
}


void ScopeWidget::set_y2_scale(double scale) {
  m_y2_scale = scale;
  queue_draw();
}


void ScopeWidget::set_x_offset(double offset) {
  m_x_offset = offset;
  queue_draw();
}


void ScopeWidget::set_y1_offset(double offset) {
  m_y1_offset = offset;
  queue_draw();
}


void ScopeWidget::set_y2_offset(double offset) {
  m_y2_offset = offset;
  queue_draw();
}


void ScopeWidget::set_frozen(bool frozen) {
  m_frozen = frozen;
}


void ScopeWidget::set_additive(bool additive) {
  m_additive = additive;
}


void ScopeWidget::set_time_scale(double ms) {
  m_window_size = int(6 * ms * m_sample_rate / 1000);
  queue_draw();
}


void ScopeWidget::on_realize() {
  // create drawing tools for the widget
  DrawingArea::on_realize();
  m_win = get_window();
  m_gc = GC::create(m_win);
  clear();
}


bool ScopeWidget::on_expose_event(GdkEventExpose* event) {
  clear();
  draw_data();
  return true;
}


bool ScopeWidget::on_button_press_event(GdkEventButton* event) {
  if (event->button == 1)
    set_frozen(!m_frozen);
  else if (event->button == 2)
    middle_button_pressed();
  else if (event->button == 3)
    set_additive(!m_additive);
  return true;
}


void ScopeWidget::clear() {
  m_gc->set_foreground(m_bg_color);
  m_win->draw_rectangle(m_gc, true, 0, 0, 3, 200);
  for (int x = 0; x < 300; x += 50) {
    for (int y = 0; y < 200; y += 50) {
      int xx = x + 3, yy = y, w = 49, h = 49;
      if (x != 0)
	++xx;
      if (x == 0 || x == 250)
	++w;
      if (y != 0)
	++yy;
      if (y == 0 || y == 150)
	++h;
      m_win->draw_rectangle(m_gc, true, xx, yy, w, h);
    }
  }

  m_gc->set_foreground(m_grid_color2);
  for (int x = 0; x < 300; x += 50) {
    if (x != 150)
      m_win->draw_line(m_gc, x + 3, 0, x + 3, 200);
  }
  for (int y = 50; y < 200; y += 50) {
    if (y != 100)
      m_win->draw_line(m_gc, 3, y, 300 + 3, y);
  }
  m_gc->set_foreground(m_grid_color1);
  m_win->draw_line(m_gc, 150 + 3, 0, 150 + 3, 200);
  m_win->draw_line(m_gc, 3, 100, 300 + 3, 100);
  
  m_gc->set_foreground(m_trig_color);
  int y = int(200 / 2 - m_y1_scale * m_trigger_level * 200 / 4 + m_y1_offset);
  m_win->draw_line(m_gc, 0, y, 2, y);
}


bool ScopeWidget::read_data() {
  
  bool got_data = false;
  LADSPA_Data* data1;
  LADSPA_Data* data2;
  
  // search the buffer for a trigger
  if (m_scope_state == WaitingForTrigger) {

    // catch up
    int a = ringbuf_available(m_buffer1);
    int b = ringbuf_available(m_buffer2);
    a = (a <= b ? a : b);
    if (a > 1000) {
      ringbuf_read(m_buffer1, NULL, a - 1000);
      ringbuf_read(m_buffer2, NULL, a - 1000);
    }
      
    data1 = (LADSPA_Data*)ringbuf_get_read_ptr(m_buffer1);
    data2 = (LADSPA_Data*)ringbuf_get_read_ptr(m_buffer2);
    int n = ringbuf_available_contiguous(m_buffer1);
    int m = ringbuf_available_contiguous(m_buffer2);
    n = (n <= m ? n : m);
    int i;
    for (i = 0; i < n; ++i) {
      if ((i > 0) && !m_frozen &&
	  ((m_trigger_up && 
	    (data1[i-1] < m_trigger_level) && (data1[i] >= m_trigger_level)) ||
	   (!m_trigger_up &&
	    (data1[i-1] >= m_trigger_level) && (data1[i] < m_trigger_level)))) {
	m_trigger_phase = abs((m_trigger_level - data1[i-1]) / 
			      (data1[i] - data1[i-1]));
	m_scope_state = ReadingData;
	m_channel1[0] = data1[i-1];
	m_channel2[0] = data2[i-1];
	m_frames_read = 1;
	break;
      }
    }
    ringbuf_read(m_buffer1, NULL, i);
    ringbuf_read(m_buffer2, NULL, i);
  }
  
  // got a trigger, copy data to the image buffer
  if (m_scope_state == ReadingData) {
    int n = ringbuf_available(m_buffer1);
    int m = ringbuf_available(m_buffer2);
    n = (n <= m ? n : m);
    n = (n <= m_window_size -m_frames_read ? n : m_window_size - m_frames_read);
    ringbuf_read(m_buffer1, m_channel1 + m_frames_read, n);
    ringbuf_read(m_buffer2, m_channel2 + m_frames_read, n);
    m_frames_read += n;
    if (m_frames_read >= m_window_size) {
      got_data = true;
      m_scope_state = WaitingForTrigger;
    }
  }
  
  // draw any new data
  if (got_data && !m_frozen) {
    if (!m_additive)
      clear();
    draw_data();
  }

  return true;
}


void ScopeWidget::draw_data() {
  int h = 200;
  m_gc->set_foreground(m_fg_color2);
  double scale = m_window_size / 300.0;
  
  for (int i = 1; i < 300; ++i) {
    if ((i + m_trigger_phase) * scale + 1 >= m_frames_read)
      break;
    double t1 = (i - 1 + m_trigger_phase) * scale;
    double t2 = (i + m_trigger_phase) * scale;
    double y1 = (m_channel2[int(t1)] * (1 - (t1 - int(t1))) + 
		 m_channel2[int(t1) + 1] * (t1 - int(t1)));
    double y2 = (m_channel2[int(t2)] * (1 - (t2 - int(t2))) + 
		 m_channel2[int(t2) + 1] * (t2 - int(t2)));
    m_win->draw_line(m_gc, i - 1 + int(m_x_offset) + 3, 
		     int(h / 2 - m_y2_scale * y1 * h / 4 + m_y2_offset),
		     i + int(m_x_offset) + 3,
		     int(h / 2 - m_y2_scale * y2 * h / 4 + m_y2_offset));
  }
  
  m_gc->set_foreground(m_fg_color);
  for (int i = 1; i < 300; ++i) {
    if (int(i * scale) >= m_frames_read)
      break;
    double t1 = (i - 1 + m_trigger_phase) * scale;
    double t2 = (i + m_trigger_phase) * scale;
    double y1 = (m_channel1[int(t1)] * (1 - (t1 - int(t1))) + 
		 m_channel1[int(t1) + 1] * (t1 - int(t1)));
    double y2 = (m_channel1[int(t2)] * (1 - (t2 - int(t2))) + 
		 m_channel1[int(t2) + 1] * (t2 - int(t2)));
    m_win->draw_line(m_gc, i - 1 + int(m_x_offset) + 3, 
		     int(h / 2 - m_y1_scale * y1 * h / 4 + m_y1_offset),
		     i + int(m_x_offset) + 3,
		     int(h / 2 - m_y1_scale * y2 * h / 4 + m_y1_offset));
  }
}


