/*
 * ChannelView.cpp
 *
 * Copyright (C) 1999 Stephen F. White
 * 
 * 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 (see the file "COPYING" for details); if 
 * not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
 * Cambridge, MA 02139, USA.
 */

#include <stdlib.h>

#include "stdafx.h"

#include "ChannelView.h"
#include "swt.h"
#include "Scene.h"
#include "Node.h"
#include "Interpolator.h"
#include "Path.h"

#define GRID_MIN        15    // minimum distance between grid lines
#define BORDER_WIDTH    20    // border at right and bottom edges
#define X_RULER_WIDTH   20    // width of horizontal ruler
#define Y_RULER_WIDTH   40    // width of vertical ruler
#define TICK_SIZE        3     // size of "ticks" in ruler

#define TO_WORLD_X(x)  (((x) - _rect.left) / (float) _rect.Width())
#define TO_WORLD_Y(y)  ((_rect.bottom - (y)) / (float) _rect.Height())
#define TO_VALUE(y)    ((TO_WORLD_Y(y) / _yScale) + _yMin)
#define TO_SCREEN_X(x) (_rect.left + (int) ((x) * _rect.Width() + 0.5f))
#define TO_SCREEN_Y(y) (_rect.bottom - (int) ((y) * _rect.Height() + 0.5f))

#define AUTOSCROLL_MARGIN   10
#define AUTOSCROLL_AMOUNT   20

static int      currentX, currentY;

// maximum size for drawing to bitmap
#define MAX_BACK_WIDTH 500
#define MAX_BACK_HEIGHT 500

#define MoveTo(dc, x, y)        { currentX = x; currentY = y; }
#define LineTo(dc, x, y)        { swDrawLine(dc, currentX, currentY, x, y); \
                                  currentX = x; currentY = y; }
#ifdef BLINKTIMER
static int
timerCallback(void *data)
{
    return ((ChannelView *) data)->OnBlinkTimer();
}
#endif

static int
autoScrollCallback(void *data)
{
    return ((ChannelView *) data)->OnAutoScrollTimer();
}

ChannelView::ChannelView(Scene *scene, SWND wnd)
  : SceneView(scene, wnd)
{
    int width, height;
    swGetSize(_wnd, &width, &height);

    _state = NORMAL;
    _interpolator = NULL;
    _selectedChannel = -1;
    _selectedKey = -1;

    _yMin = _yMax = 0.0f;
    _yScale = _xScale = 1.0f;
    _anchor = 0;
    _selMin = _selMax = 0;

#ifdef BLINKTIMER
    _timer = swSetTimer(_wnd, 500, timerCallback, this);
#endif
    _autoScrollTimer = NULL;
    _cursorOn = true;
    _autoScrolling = false;

    _rect.left = Y_RULER_WIDTH;
    _rect.top = X_RULER_WIDTH;
}

ChannelView::~ChannelView()
{
#ifdef BLINKTIMER
    if (_timer) swKillTimer(_timer);
#endif
    if (_autoScrollTimer) swKillTimer(_autoScrollTimer);
}

void ChannelView::OnSize(int width, int height)
{
    swInvalidateWindow(_wnd);
}

void ChannelView::OnDraw(int x, int y, int width, int height)
{
    SDC dc = swCreateDC(_wnd);
//    swSetClipRect(dc, x, y, width, height);
    swSetFGColor(dc, swGetWindowColor(_wnd, SW_COLOR_WINDOW_BG));
    swFillRect(dc, x, y, width, height);

    int w, h;
    swGetSize(_wnd, &w, &h);

    _rect.right = w - BORDER_WIDTH;
    _rect.bottom = h - BORDER_WIDTH;

    if (_interpolator && _rect.Width() > 0 && _rect.Height() > 0) {
        // draw selection
        DrawSelection(dc);
        DrawRulers(dc, 0.0f, 1.0f, _yMin, _yMax);
        DrawKeys(dc);
    }
    swDestroyDC(dc);
}

void
ChannelView::AutoScale()
{
    if (!_interpolator) return;

    int numChannels = _interpolator->getNumChannels();
    int numKeys = _interpolator->getNumKeys();

    _yMin = 0.0f;
    _yMax = 1.0f;

    for (int chan = 0; chan < numChannels; chan++) {
        for (int k = 0; k < numKeys; k++) {
            float value = _interpolator->getKeyValue(chan, k);
            if (value < _yMin) _yMin = value;
            if (value > _yMax) _yMax = value;
        }
    }

    _yScale = 1.0f / (_yMax - _yMin);
}

// reduce drawing/use of MFVec?f Interpolators
// only draw/use channels with different keyValues

bool                
ChannelView::isDrawableChannel(int chan)
{
    if (_interpolator->getNumChannels() > 4)
        if (_multipleValuesInChannel[chan])
            return true;
        else
            return false;
    return true;
}

void
ChannelView::findDrawableChannels(void)
{
    int numChannels = _interpolator->getNumChannels();
    int numKeys = _interpolator->getNumKeys();

    _multipleValuesInChannel.resize(numChannels);
    int maxNumChannels = TheApp->GetMaxKeysInChannelView();
    int channelCount = 0;

    for (int chan = 0; chan < numChannels; chan++) {
        _multipleValuesInChannel[chan] = false;
        if (maxNumChannels > 0)
            if (channelCount > maxNumChannels)
                continue; 
        float firstKey;
        if (numKeys > 0)
            firstKey = _interpolator->getKeyValue(chan, 0);
        else 
            continue;
        for (int j = 1; j < numKeys; j++)
            if (_interpolator->getKeyValue(chan, j) != firstKey) {
                _multipleValuesInChannel[chan] = true;
                channelCount++;
                break;
            }
    }
    // if no channel is changed, use first channel
    if ((numChannels > 0) && (channelCount == 0))
        _multipleValuesInChannel[0] = true;
}

void
ChannelView::DrawKeys(SDC dc)
{
    int numChannels = _interpolator->getNumChannels();
    int numKeys = _interpolator->getNumKeys();

    for (int chan = 0; chan < numChannels; chan++) {
        if (!isDrawableChannel(chan))
            continue;

        int y = _rect.bottom;
        float key;

        if ((numChannels == 4) && (chan == 3))
            swSetFGColor(dc, 0x00FF00FF); // only for SFRotation
        else
            swSetFGColor(dc, 0x000000FF << ((chan % 3) * 8));
        MoveTo(dc, _rect.left, y);
        for (int k = 0; k < numKeys; k++) {
            key = _interpolator->getKey(k);
            float value = _interpolator->getKeyValue(chan, k);
            int x = TO_SCREEN_X(key);

            // normalize value into range 0..1
            float sv = (value - _yMin) * _yScale;

            y = TO_SCREEN_Y(sv);
            if (k == 0) MoveTo(dc, _rect.left, y);
            LineTo(dc, x, y);
            swFillRect(dc, x - 2, y - 2, 5, 5);
        }
        LineTo(dc, _rect.right, y);
    }
}

static float getStep(float ratio)
{
    float step = 1.0f;

    while (ratio > GRID_MIN) {
        ratio *= 0.1f;
        step *= 0.1f;
    }

    while (ratio < GRID_MIN) {
        ratio *= 10.0f;
        step *= 10.0f;
    }

    return step;
}

void
ChannelView::DrawRulers(SDC dc, float xMin, float xMax, float yMin, float yMax)
{
    float xRatio = (float) _rect.Width() / (xMax - xMin);
    float yRatio = (float) _rect.Height() / (yMax - yMin);

    // find nearest power of 10 

    float xStep = getStep(xRatio);
    float yStep = getStep(yRatio);

    int grey = swGetWindowColor(_wnd, SW_COLOR_BSHADOW);
    char buf[128];
    int theight = swGetFontHeight(swGetDefaultFont());

    swSetFGColor(dc, grey);
    swFillRect(dc, 0, 0, _rect.left, _rect.bottom +BORDER_WIDTH);
    swFillRect(dc, _rect.left, 0, _rect.right+BORDER_WIDTH, _rect.top);
    swDrawLine(dc, _rect.left, _rect.top-1, _rect.right, _rect.top-1);
    for (float x = xStep * (float) floor(xMin / xStep); x < xMax + 0.001;
         x += xStep) {
        int scrx = (int) (_rect.left + x * xRatio + 0.5f);
        swSetFGColor(dc, 0);
        swDrawLine(dc, scrx, _rect.top, scrx, _rect.top - TICK_SIZE);
        mysnprintf(buf, 128, "%g", x);
        int twidth = swGetStringWidth(swGetDefaultFont(), buf);
        swDrawText(dc, scrx - twidth / 2, _rect.top - TICK_SIZE,
                   buf);
    }
    MoveTo(dc, _rect.left-1, _rect.top);
    LineTo(dc, _rect.left-1, _rect.bottom);
    for (float y = yStep * (float) floor(yMin / yStep); y < yMax + 0.001;
         y += yStep) {
        int scry = (int) (_rect.bottom - (y - yMin) * yRatio + 0.5f);
        swSetFGColor(dc, 0);
        swDrawLine(dc, _rect.left, scry, _rect.left - TICK_SIZE, scry);
        mysnprintf(buf, 128, "%g", y);
        int twidth = swGetStringWidth(swGetDefaultFont(), buf);
        swDrawText(dc, _rect.left - TICK_SIZE - twidth, scry + theight / 2,
                   buf);
    }
}

void
ChannelView::DrawSelection(SDC dc)
{
    if (_selMin != _selMax || _cursorOn) {
        swSetFGColor(dc, 0);
        swFillRect(dc, _rect.left + _selMin, _rect.top,
                   _selMax - _selMin+1, _rect.Height());
    }
}

void ChannelView::OnLButtonDown(int px, int py, int modifiers) 
{
    const Interpolator   *node = _interpolator;

    if (node == NULL) return;

    int numChannels = node->getNumChannels();
    int numKeys = node->getNumKeys();

    for (int i = 0; i < numKeys; i++) {
        float key = _interpolator->getKey(i);
        int x = TO_SCREEN_X(key);
        if (px >= x - 3 && px <= x + 3) {
            for (int chan = numChannels - 1; chan >= 0; chan--) {
                if (!isDrawableChannel(chan))
                    continue;

                float value = node->getKeyValue(chan, i);

                // normalize value into range 0..1
                float sv = (value - _yMin) * _yScale;
                int y = TO_SCREEN_Y(sv);

                if (py >= y - 3 && py <= y + 3) {
                    _selectedChannel = chan;
                    _selectedKey = i;
                    _state = DRAGGING;
                    swSetCapture(_wnd);
                    _interpolator->backupKey(_selectedKey);
                    return;
                }
            }
        }
    }

    // no keys picked; check for line picking
    for (int chan = numChannels - 1 ; chan >= 0; chan--) {
        if (!isDrawableChannel(chan))
            continue;
        int x1 = _rect.left, y1 = TO_SCREEN_Y(0.0f);
        int k;

        for (k = 0; k < numKeys; k++) {
            float key = node->getKey(k);
            float value = node->getKeyValue(chan, k);
            int x2 = TO_SCREEN_X(key);

            // normalize value into range 0..1
            float sv = (value - _yMin) * _yScale;

            int y2 = TO_SCREEN_Y(sv);

            if (k == 0) y1 = y2;

            if (PointNearLine(px, py, x1, y1, x2, y2)) {
                AddKey(chan, k, px);
                return;
            }
            x1 = x2;  y1 = y2;
        }

        // check last line segment
        if (PointNearLine(px, py, x1, y1, _rect.right, y1)) {
            AddKey(chan, k, px);
            return;
        }
    }

    // no keys picked; do a drag-select

    _anchor = _lastX = (px - _rect.left);
    SetSelection(_anchor);
    _interpolator->sendInterpolatedValue(0.0, _selMin / (float) (_rect.Width() - 1));
    _state = SELECTING;
    _scene->setViewOfLastSelection(this);
    swSetCapture(_wnd);
}

void ChannelView::AddKey(int chan, int key, int x)
{
    // picked a line; create a new key at that point
    float newKey = TO_WORLD_X(x);
    float *values = new float[_interpolator->getNumChannels()];
    _interpolator->interpolate(newKey, values);
    _interpolator->insertKey(key, newKey, values);
    delete [] values;
    _selectedChannel = chan;
    _selectedKey = key;
    _state = DRAGGING;
    swSetCapture(_wnd);
}

void ChannelView::OnLButtonUp(int x, int y, int modifiers) 
{
    if (_autoScrolling) {   // stop scrolling
        swKillTimer(_autoScrollTimer);
        _autoScrollTimer = NULL;
        _autoScrolling = false;
        AutoScale();
        swInvalidateWindow(_wnd);
    }
    if (_state == DRAGGING || _state == SELECTING) {
        swReleaseCapture(_wnd);
        _state = NORMAL;
    }
}

void ChannelView::OnMouseMove(int x, int y, int modifiers) 
{
    if (_state == DRAGGING || _state == SELECTING) {
        CheckAutoScroll(x, y);
    }
    DoMouseMove(x, y);
}

void ChannelView::DoMouseMove(int px, int py)
{
    if (_state == DRAGGING) {
        float key = TO_WORLD_X(px);
        float value = TO_VALUE(py);
        if (_selectedKey > 0) {
            float prevKey = _interpolator->getKey(_selectedKey - 1);
            if (key < prevKey) key = prevKey;
        }
        if (_selectedKey < _interpolator->getNumKeys() - 1) {
            float nextKey = _interpolator->getKey(_selectedKey + 1);
            if (key > nextKey) key = nextKey;
        }
        _interpolator->setKey(_selectedKey, key);
        _interpolator->setKeyValue(_selectedChannel, _selectedKey, value);
        _interpolator->sendInterpolatedValue(0.0, _interpolator->getFraction());
    } else if (_state == SELECTING) {
        int x = px - _rect.left;
        x = CLAMP(x, 0, _rect.Width() -1);
        int min = MIN(_lastX, x);
        int max = MAX(_lastX, x);
        swInvalidateRect(_wnd, _rect.left + min, _rect.top,
                         max - min + 1, _rect.Height());
        if (x < _anchor) {
            _selMin = x;
            _selMax = _anchor;
        } else {
            _selMin = _anchor;
            _selMax = x;
        }
        _lastX = x;
        _interpolator->sendInterpolatedValue(0.0, x / (float) (_rect.Width() - 1));
    }
}

void
ChannelView::InvalidateSelection()
{
    swInvalidateRect(_wnd, _rect.left + _selMin, _rect.top,
                     _selMax - _selMin + 1, _rect.Height());
}

#ifdef BLINKTIMER
int ChannelView::OnBlinkTimer() 
{
    if (_selMin == _selMax && !_scene->isRunning()) {
        _cursorOn = !_cursorOn;
        InvalidateSelection();
    }
    return TRUE;
}
#endif

int ChannelView::OnAutoScrollTimer()
{
    AutoScale();
    DoMouseMove(_autoScrollPX, _autoScrollPY);
    return TRUE;
}

void ChannelView::OnUpdate(SceneView* sender, int type, Hint *hint) 
{
    const Path *sel = _scene->getSelection();
    Node *node = sel ? sel->getNode() : NULL;
    Interpolator *interp = NULL;
    if (node->isInterpolator())
        interp = dynamic_cast_Interpolator(node);
    FieldUpdate *fieldUpdate;
    NodeUpdate *nodeUpdate;

    switch (type) {
      CASE_UPDATE(UPDATE_ALL)
        swInvalidateWindow(_wnd);
        break;
      CASE_UPDATE(UPDATE_SELECTION)
        if (interp != NULL && _interpolator != interp) {
            _interpolator = interp;
            AutoScale();
            findDrawableChannels();
            swInvalidateWindow(_wnd);
        } 
        break;
      CASE_UPDATE(UPDATE_FIELD)
        fieldUpdate = (FieldUpdate *) hint;
        if (fieldUpdate->node == _interpolator) {
            AutoScale();
            findDrawableChannels();
            swInvalidateWindow(_wnd);
        }
        break;
      CASE_UPDATE(UPDATE_REMOVE_NODE)
        nodeUpdate = (NodeUpdate *) hint;
        if (nodeUpdate->node == _interpolator) {
            _interpolator = NULL;
            swInvalidateWindow(_wnd);
        }
        break;
      CASE_UPDATE(UPDATE_TIME)
        if (_interpolator) {
            SetSelection((int) (_interpolator->getFraction() * 
                                (_rect.Width() - 1)));
        }
        break;
    }
}

bool ChannelView::PointNearLine(int x, int y, int x1, int y1, int x2, int y2) const
{
    if (x1 != x2) {
        float slope = (y2 - y1) / (float) (x2 - x1);
        float v = y1 + slope * (x - x1);

        if (x >= x1 && x <= x2 && y >= v - 3.0f && y <= v + 3.0f) {
            return true;
        }
    }
    return false;
}

void ChannelView::OnFastForward() 
{
    if (_interpolator) {
        _interpolator->sendInterpolatedValue(0.0, 1.0f);
        SetSelection(_rect.Width()-1);
    }
}

void ChannelView::OnRewind() 
{
    if (_interpolator) {
        _interpolator->sendInterpolatedValue(0.0, 0.0f);
        SetSelection(0);
    }
}

void ChannelView::SetSelection(int pos)
{
    InvalidateSelection();
    _selMin = _selMax = pos;
    _cursorOn = true;
    InvalidateSelection();
}

void ChannelView::OnEditDelete() 
{
    if (_interpolator) {
        int min = _interpolator->findKeyInclusive(_selMin / (float) (_rect.Width() - 1));
        int max = _interpolator->findKey(_selMax / (float) (_rect.Width() - 1));
        _interpolator->deleteKeys(min, max);
    }
}

#if 0
void ChannelView::OnUpdateEditDelete(CCmdUI* pCmdUI) 
{
    if (_interpolator) {
        int min = _interpolator->findKeyInclusive(_selMin / (float) (_rect.Width() - 1));
        int max = _interpolator->findKey(_selMax / (float) (_rect.Width() - 1));

        pCmdUI->Enable(min < max);
    } else {
        pCmdUI->Enable(FALSE);
    }
}
#endif

void ChannelView::CheckAutoScroll(int px, int py)
{
    int width, height;
    bool autoScroll = true;

    swGetSize(_wnd, &width, &height);
    _autoScrollPX = px;
    _autoScrollPY = py;

    if (px < AUTOSCROLL_MARGIN) {
        autoScroll = true;
    } else if (px > width - AUTOSCROLL_MARGIN) {
        autoScroll = true;
    } else if (py < AUTOSCROLL_MARGIN) {
        autoScroll = true;
    } else if (py > height - AUTOSCROLL_MARGIN) {
        autoScroll = true;
    } else {
        autoScroll = false;
    }

    if (autoScroll && !_autoScrolling) {   // start scrolling
        _autoScrollTimer = swSetTimer(_wnd, 100, autoScrollCallback, this);
        _autoScrolling = true;
    }
    if (!autoScroll && _autoScrolling) {   // stop scrolling
        swKillTimer(_autoScrollTimer);
        _autoScrollTimer = NULL;
        _autoScrolling = false;
    }
}

void ChannelView::DeleteLastSelection(void)
{
    OnEditDelete();
}
