/* brushlib - The MyPaint Brush Library (demonstration project)
 * Copyright (C) 2013 POINTCARRE SARL / Sebastien Leon email: sleon at pointcarre.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 */

#include <QColorDialog>

#include "MypaintView.h"
#include "Tile.h"

#include <assert.h>

//-------------------------------------------------------------------------
static MypaintView* s_view = NULL;

uint16_t* CallBack_MyPaintTiledSurfaceGetTileFunction (MyPaintTiledSurface *self, int tx, int ty, gboolean readonly)
{
  Tile* tile = s_view->GetTileFromIdx(QPoint(tx,ty));
  return tile ? tile->Bits(readonly) : NULL;
}

void CallBack_MyPaintTiledSurfaceUpdateTileFunction (MyPaintTiledSurface *self, int tx, int ty, uint16_t * tile_buffer)
{
  Tile* tile = s_view->GetTileFromIdx(QPoint(tx,ty));
  if (tile) tile->update();
}

void CallBack_MyPaintTiledSurfaceAreaChanged (MyPaintTiledSurface *self, int bb_x, int bb_y, int bb_w, int bb_h)
{
  // we already register an update for the touched tile...
}
//-------------------------------------------------------------------------

void MypaintView::Init()
{
  assert(s_view == NULL);
  s_view = this;
  // allocate an empty brush:
  mp_brush = mypaint_brush_new();
  // clean it
  memset( m_tileTable, 0, sizeof(m_tileTable));
  // clean the TiledSurface
  memset( &m_tile, 0, sizeof(MyPaintTiledSurface));
  // Init (required ?)
  mypaint_tiled_surface_init(&m_tile);
  // Let's assign few callback (plain C interface / no C++ virtual interface)
  m_tile.get_tile     = CallBack_MyPaintTiledSurfaceGetTileFunction;
  m_tile.update_tile  = CallBack_MyPaintTiledSurfaceUpdateTileFunction;
  m_tile.area_changed = CallBack_MyPaintTiledSurfaceAreaChanged;

  m_scene.setSceneRect ( QRect(QPoint(), QSize(k_tile_dim*k_max, k_tile_dim*k_max)) );
  setScene     (&m_scene);
  setAlignment (0);
  setVerticalScrollBarPolicy   (Qt::ScrollBarAlwaysOn);
  setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
  centerOn( GetTilePos(QPoint(k_center, k_center)) );
}

MypaintView::~MypaintView ()
{
  mypaint_brush_destroy(mp_brush);
}

#define CTILT(t) ((float)t/60) // CONVERT QT TILT to MYPAINT TILT

void MypaintView::tabletEvent(QTabletEvent *e)
{
  switch (e->type())
  {
    case QEvent::TabletPress:
      if (e->pointerType() == QTabletEvent::Pen)
      {
        mypaint_brush_reset (mp_brush);
        GetDTime();
        e->accept();
      }
      break;
    case QEvent::TabletRelease:
      if (e->pointerType() == QTabletEvent::Pen)
      {
        // How do I finalize the stroke sequence ?
        // may be nothing special...
        e->accept();
      }
      break;
    case QEvent::TabletMove:
      if (e->pointerType() == QTabletEvent::Pen)
      {
        QPointF pt(mapToScene(e->pos()));
        mypaint_brush_stroke_to(mp_brush, &m_tile.parent, pt.x(), pt.y(), e->pressure(), CTILT(e->xTilt()),  CTILT(e->yTilt()), GetDTime());
        e->accept();
      }
      break;
   }
}

void MypaintView::mousePressEvent   ( QMouseEvent * e )
{
  mypaint_brush_reset (mp_brush);
  GetDTime();
}
void MypaintView::mouseMoveEvent    ( QMouseEvent * e )
{
  QPointF pt(mapToScene(e->pos()));
  mypaint_brush_stroke_to(mp_brush, &m_tile.parent, pt.x(), pt.y(), 1.0, 0, 0, GetDTime());

  // TEST CODE with basic drawing:
  //QPoint pt(mapToScene(e->pos()).toPoint());
  //Tile* tile = GetTileFromPos (pt);
  //if (tile) { tile->DrawPoint(pt.x() % k_tile_dim, pt.y() % k_tile_dim, 1<<15, 0 , 0, 1<<15); tile->update(); }
}
void MypaintView::mouseReleaseEvent ( QMouseEvent * e )
{
  // How do I finalize the stroke sequence ?
  // may be nothing special...
}

void MypaintView::BrushSelected (const QByteArray& content)
{
  // I get a crash after several calls to mypaint_brush_from_string(p_brush, content.constData())
  // I do not see why... May be with some proper API doc I could fix that.
  // May be the desallocation code is a bit wrong and/or it is a MSVC compilation issue :
  // HEAP[test.exe]: HEAP: Free Heap block 31bfd28 modified at 31c2a34 after it was freed

  // user selected another brush. Time to reload it :
  if (!mypaint_brush_from_string(mp_brush, content.constData()))
  {
    // Not able to load the selected brush. Let's execute some backup code...
    qDebug("Trouble when reading the selected brush !");
  }
}

// Very basic Tile management (fixed number of maximum tiles)
Tile* MypaintView::GetTileFromIdx (const QPoint& idx)
{
  Tile* result = NULL;
  // Which tile index is it ?
  if (CheckIndex(idx.x()) && CheckIndex(idx.y())) // out of range ?
  {
    // Ok, valid index. Does it exist already ?
    result = m_tileTable [idx.x()][idx.y()];
    if (!result)
    {
      // Time to allocate it, update table and insert it as a QGraphicsItem in scene:
      result = new Tile();
      m_tileTable [idx.x()][idx.y()] = result;
      m_scene.addItem(result);
      QPoint tilePos ( GetTilePos(idx) );
      result->setPos(tilePos);
      result->show();
    }
  }
  return result;
}

void MypaintView::BtnChgColorPressed ()
{
  QPushButton* p_btn = dynamic_cast<QPushButton*>(sender());
  if (p_btn)
  {
    QColor newColor = QColorDialog::getColor ( m_color, window(), "Select the brush color", QColorDialog::ShowAlphaChannel );
    if (newColor.isValid())
    {
      p_btn->setStyleSheet( QString("color: %1; background-color: %2;").arg((newColor.lightnessF()>0.5)?"black":"white").arg(newColor.name()) );
      m_color = newColor;
      // Update libMyPaint with the new color...
      // Damn, how do I do that ???
    }
  }
}

#if defined Q_WS_WIN
  #include <windows.h>
#else
  #include <sys/time.h> // Mac & Linux
#endif

float MypaintView::GetDTime()
{
  // MyPaint is expecting :
  // Floating-point number of seconds since the last call to this,
  // function, for motion interpolation etc.
  float now; // current time is different according OS. But we just care about delta:
#if defined Q_WS_WIN
  now = (float)::GetTickCount() / 1000;
#else
  struct timeval val;
  gettimeofday(&val, NULL);
  now = (float)val.tv_sec+((float)val.tv_usec/1000000);
#endif
  float delta = now - m_lastTimeEvent;
  m_lastTimeEvent = now;
  return delta;
}