// **********************************************
// File: DISPLAY.CPP
// The display module

#include "muzika.h"
#include <stdio.h>
#include <string.h>
#include <values.h>

HWND hEditWnd;                    // The edit window handle
HCURSOR hEditCursor;              // The handle of the edit window cursor
int currStaffHeight;              // Height of the currently displayed staff
STAFFLOC staffLoc;                // Current staff location within multiple
int staffX, staffY;               // Coordinates of the current staff
int scoreMultiplicity, scoreStaves; // Parameters for the score display
int scoreFirstStaff, scoreFirstPart; // Parameters for the score display

// **********************************************
// Following are the Edit window functions

// **********************************************
// RegisterEditClass registers the Edit window class
// during the first-instance initialization (called by
// InitMainFirst in MAIN.CPP).

void RegisterEditClass(HANDLE hInstance)
{
  WNDCLASS wc;

  // Register the edit window class
  wc.lpszClassName = "MUZIKA_edit";
  wc.hInstance = hInstance;
  wc.lpfnWndProc = EditWindowProc;
  wc.hCursor = NULL;
  wc.hIcon = NULL;
  wc.lpszMenuName = NULL;
  wc.hbrBackground = COLOR_APPWORKSPACE+1;
  wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  RegisterClass(&wc);
}

// **********************************************
// CreateEditWindow creates and displays an edit window
// (this is the window where the edited score is displayed).

void CreateEditWindow(HANDLE hInstance)
{
  // Get the main window extents
  RECT mainExt;
  GetClientRect(hMainWnd, &mainExt);

  // Create and display the edit window
  hEditWnd = CreateWindow("MUZIKA_edit",
    NULL,
    WS_CHILDWINDOW | WS_VSCROLL,
    73, 37,
    mainExt.right-72, mainExt.bottom-36,
    hMainWnd,
    NULL,
    hInstance,
    NULL);
  ShowScrollBar(hEditWnd, SB_VERT, FALSE);
  ShowWindow(hEditWnd, SW_SHOWNA);

  // Set the initial cursor
  hEditCursor = LoadCursor(NULL, IDC_ARROW);
}

// **********************************************
// EditWindowProc is the edit window procedure, which responds to messages
// intended for the edit window. The messages processed are described within.

long FAR PASCAL EditWindowProc(
  HWND hWnd, unsigned message, WORD wParam, LONG lParam)
{
  static int Xfrom, Xto, Y, Yfrom, Yto;
  static enum {NEWCONTINUOUS, MOVEOBJECT, MOVESTAFF} capture;

  // Check message type
  switch(message) {
    case WM_PAINT:
      // Process a WM_PAINT message, indicating that the window
      // should be repainted.
      PaintEditWindow(hWnd);
      break;

    case WM_MOUSEMOVE:
      // Process a WM_MOUSEMOVE message, indicating that the cursor
      // has moved in the edit window region; the cursor shape should
      // be changed to the current symbol shape.
      SetCursor(hEditCursor);
      break;

    case WM_LBUTTONDOWN:
      // Process a WM_LBUTTONDOWN message, indicating that the user
      // has clicked the left mouse button. According to what the
      // current edit symbol is, an editing action is performed.
      if (melodyExists)
        if (!scoreDisplay)
          switch (GetActiveSymbol()) {
            case PENCIL:
              // The current symbol is the pencil on a staff:
              // insert a new staff at the current position
              NewMultipleStaff(MAKEPOINT(lParam).y);
              break;

            case ERASER:
              // The current symbol is the eraser:
              // erase the symbols at the current cursor position
              DeleteMusicalObject(MAKEPOINT(lParam).x, MAKEPOINT(lParam).y);
              break;

            case HAND:
              // The current symbol is the hand:
              // capture the mouse until the left button is released,
              // then move the objects to their new place.
              Xfrom = MAKEPOINT(lParam).x;
              Yfrom = MAKEPOINT(lParam).y;
              SetCapture(hEditWnd);
              capture = MOVEOBJECT;
              break;

            default:
              // The current symbol is one of the musical object symbols:
              // insert the appropriate object at the current position
              SymbolClass *currentSymbol = GetCurrentSymbol();
              switch (currentSymbol->GetType()) {
                case POINTOBJECT:
                  // The symbol corresponds to a point object:
                  // just insert the object in the staff
                  NewPointObject(currentSymbol,
                    MAKEPOINT(lParam).x, MAKEPOINT(lParam).y);
                  break;

                case CONTINUOUSOBJECT:
                  // The symbol corresponds to a continuous object:
                  // capture the mouse until the left button is released,
                  // then create the object between its two extents.
                  Xfrom = MAKEPOINT(lParam).x;
                  Y = MAKEPOINT(lParam).y;
                  SetCapture(hEditWnd);
                  capture = NEWCONTINUOUS;
                  break;
              }
          }
        else
          // No editing is allowed on a score display
          MessageBox(hWnd, "Cannot edit a score display", NULL,
            MB_ICONSTOP | MB_OK);
      break;

    case WM_LBUTTONUP:
      // Process a WM_LBUTTONUP message, indicating that the
      // left mouse button has been released. In case the mouse cursor
      // was captured (presumably because of an operation that required
      // two points to operate), release the capture and complete
      // the operation.
      if (GetCapture() == hEditWnd) {
        switch (capture) {
          case NEWCONTINUOUS:
            // The capture was due to insertion of a continuous object:
            // insert a new continuous object between the two points
            Xto = MAKEPOINT(lParam).x;
            if (Xto < Xfrom) {
              int temp = Xto;
              Xto = Xfrom;
              Xfrom = temp;
            }
            if (Xto != Xfrom)
              NewContinuousObject(GetCurrentSymbol(), Xfrom, Xto, Y);
            break;

          case MOVEOBJECT:
            // The capture was due to an object moving operation:
            // move a musical object to its new place
            Xto = MAKEPOINT(lParam).x;
            Yto = MAKEPOINT(lParam).y;
            MoveMusicalObject(Xfrom, Yfrom, Xto, Yto);
            break;

          case MOVESTAFF:
            // The capture was due to a staff moving operation:
            // move the staff to its new place
            Yto = MAKEPOINT(lParam).y;
            MoveStaff(Yfrom, Yto);
            break;
        }
        ReleaseCapture();
      }
      break;

    case WM_LBUTTONDBLCLK:
      // Process a WM_LBUTTONDBLCLK, indicating that the user has
      // double-clicked the left mouse button. According to what the
      // current edit symbol is, an editing action is performed.
      if (melodyExists)
        if (!scoreDisplay)
          switch(GetActiveSymbol()) {
            case ERASER:
              // The current symbol is the eraser:
              // erase the multiple staff
              DeleteMultipleStaff(MAKEPOINT(lParam).y);
              break;

            case HAND:
              // The current symbol is the hand:
              // capture the mouse until the left button is released,
              // then move the staff to its new place.
              Yfrom = MAKEPOINT(lParam).y;
              SetCapture(hEditWnd);
              capture = MOVESTAFF;
              break;
          }
      break;

    case WM_VSCROLL:
      // Process a WM_VSCROLL message, indicating that the user
      // has clicked on the scroll bar. The screen should be updated
      // according to the new position of the scroll bar thumb.
      unsigned newY = GetScrollPos(hEditWnd, SB_VERT);
      int minScroll, maxScroll;
      GetScrollRange(hEditWnd, SB_VERT, &minScroll, &maxScroll);
      RECT editRect;
      GetClientRect(hEditWnd, &editRect);

      // Process the various possibilities of the scroll bar notification
      switch (wParam) {
        case SB_BOTTOM:
          SetScrollPos(hEditWnd, SB_VERT, maxScroll, TRUE);
          break;

        case SB_LINEDOWN:
          SetScrollPos(hEditWnd, SB_VERT,
            newY+(scoreDisplay ? 1 : pixelsPerStaff), TRUE);
          break;

        case SB_LINEUP:
          SetScrollPos(hEditWnd, SB_VERT,
            newY-(scoreDisplay ? 1 : pixelsPerStaff), TRUE);
          break;

        case SB_PAGEDOWN:
          SetScrollPos(hEditWnd, SB_VERT,
            newY+editRect.bottom/(scoreDisplay ? pixelsPerStaff : 1), TRUE);
          break;

        case SB_PAGEUP:
          SetScrollPos(hEditWnd, SB_VERT,
            newY-editRect.bottom/(scoreDisplay ? pixelsPerStaff : 1), TRUE);
          break;

        case SB_THUMBPOSITION:
          SetScrollPos(hEditWnd, SB_VERT, LOWORD(lParam), TRUE);
          break;

        case SB_TOP:
          SetScrollPos(hEditWnd, SB_VERT, 0, TRUE);
          break;

        case SB_ENDSCROLL:
          if (scoreDisplay)
            scoreFirstStaff = newY;
          else
            ((Part *) &melody.part[displayedPart])->SetPartY(newY);
          InvalidateRect(hEditWnd, NULL, TRUE);
          break;
      }
      break;

    default:
      // Unrecognized message: just let Windows take care of it.
      return DefWindowProc(hWnd, message, wParam, lParam);
  }

  return 0L;
}

// **********************************************
// PaintEditWindow is the edit window painting function,
// activated whenever the edit window receives a WM_PAINT message.
// It redraws the edit window, including staves and objects.

void PaintEditWindow(HWND hWnd)
{
  PAINTSTRUCT ps;
  HDC hDC;

  // Obtain a display context
  hDC = BeginPaint(hWnd, &ps);

  // Draw the page according to the current settings
  if (melodyExists) {
    if (!scoreDisplay) {
      // Display a single part
      Part &p = *((Part *) &melody.part[displayedPart]);
      int firstStaff = 0, lastStaff = 0;

      // Display the page
      for (int index = 0; index < p.staff.number(); ++index) {
        int firstY, lastY;
        if (index % p.multiplicity() == 0) {
          firstY = -1;
          lastY = MAXINT-24;
        }
        Staff &s = *((Staff *) &p.staff[index]);

        // Draw the staff itself
        if (s.Draw(hDC, p.GetPartY(), TRUE)) {
          if (!firstStaff) firstStaff = index+p.multiplicity();
          lastStaff = index+p.multiplicity();
          staffX = s.X();
          staffY = s.Y()-p.GetPartY();

          // Check whether first or last staff in a group
          staffLoc = MIDSTAFF;
          if (index % p.multiplicity() == 0) {
            firstY = staffY;
            staffLoc = FIRSTSTAFF;
          }
          if ((index+1) % p.multiplicity() == 0) {
            lastY = staffY;
            staffLoc = LASTSTAFF;
          }
          if (p.multiplicity() == 1)
            staffLoc = SINGLESTAFF;
          currStaffHeight = (index+1 < p.staff.number()) ?
            ((Staff *) &p.staff[index+1])->Y()-s.Y() : pixelsPerStaff;

          // Draw the point objects inside the staff
          IndexedList &pointList = s.pointObject;
          for (int i = 0; i < pointList.number(); ++i)
            ((PointObject *) &pointList[i])->Draw(hDC);

          // Draw the continuous objects inside the staff
          IndexedList &contList = s.continuousObject;
          for (i = 0; i < contList.number(); ++i)
            ((ContinuousObject *) &contList[i])->Draw(hDC);
        }

        // Check if the multiple staff is complete
        if ((index+1)%p.multiplicity() == 0) {
          if (firstY != -1 || lastY != MAXINT-24) {
            // Draw the connecting lines of a multiple staff
            MoveTo(hDC, s.X(), firstY);
            LineTo(hDC, s.X(), lastY+24);
            MoveTo(hDC, s.X()+s.width()-1, firstY);
            LineTo(hDC, s.X()+s.width()-1, lastY+24);
          }

          // Show the marked block
          int i = index/p.multiplicity()*p.multiplicity();
          if (i >= markBeginStaff && i <= markEndStaff) {
            int markFrom, markTo;
            if (i == markBeginStaff)
              markFrom = markBeginX-pixelsPerObject/2;
            if (i > markBeginStaff)
              markFrom = 0;
            if (i == markEndStaff)
              markTo = markEndX+pixelsPerObject/2;
            if (i < markEndStaff)
              markTo = s.width();
            PatBlt(hDC, markFrom+s.X(),
              ((Staff *) &p.staff[i])->Y()-p.GetPartY(),
              markTo-markFrom, s.Y()+25-((Staff *) &p.staff[i])->Y(),
              DSTINVERT);
          }
        }
      }

      // Display the status information
      char strnum[15];
      HDC hStatusDC = GetDC(hMainWnd);
      HBRUSH hBrush, hOldBrush;
      hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
      hOldBrush = SelectObject(hStatusDC, hBrush);
      Rectangle(hStatusDC, 108, -1, GetSystemMetrics(SM_CXSCREEN), 37);
      SetBkColor(hStatusDC, GetSysColor(COLOR_WINDOW));
      SetTextAlign(hStatusDC, TA_UPDATECP);
      MoveTo(hStatusDC, 110, 2);
      TextOut(hStatusDC, 0, 0, "Current part: ", 14);
      TextOut(hStatusDC, 0, 0, p.name(), strlen(p.name()));
      TextOut(hStatusDC, 0, 0, ", Staves ", 9);
      sprintf(strnum, "%d-%d",
        firstStaff/p.multiplicity(), lastStaff/p.multiplicity());
      TextOut(hStatusDC, 0, 0, strnum, strlen(strnum));
      SelectObject(hStatusDC, hOldBrush);
      DeleteObject(hBrush);
      ReleaseDC(hMainWnd, hStatusDC);
    }
    else {
      // Display score
      int line = 0;
      int staffLeftX = 32;
      int staffRightX = 32+melody.GetStaffWidth();
      BOOL screenFull = FALSE;

      for (int scoreIndex = scoreFirstStaff;
        scoreIndex < scoreStaves && !screenFull; ++scoreIndex) {
        // Draw a multiple staff
        for (int partIndex = scoreFirstPart; partIndex < melody.part.number();
          ++partIndex) {
          Part &p = *((Part *) &melody.part[partIndex]);
          for (int staffIndex = scoreIndex*p.multiplicity();
            staffIndex < (scoreIndex+1)*p.multiplicity(); ++staffIndex) {
            // Draw a single staff
            Staff &s = *((Staff *) &p.staff[staffIndex]);
            int tempY = s.Y();
            s.Y() = line += pixelsPerStaff;
            screenFull = !s.Draw(hDC, 0, TRUE);
            staffX = staffLeftX;
            staffY = line;

            // Draw the point objects inside the staff
            IndexedList &pointList = s.pointObject;
            for (int i = 0; i < pointList.number(); ++i)
              ((PointObject *) &pointList[i])->Draw(hDC);

            // Draw the continuous objects inside the staff
            IndexedList &contList = s.continuousObject;
            for (i = 0; i < contList.number(); ++i)
              ((ContinuousObject *) &contList[i])->Draw(hDC);

            s.Y() = tempY;
          }
        }

        // Draw the connecting lines of the score multiple staff
        MoveTo(hDC, staffLeftX, line-(scoreMultiplicity-1)*pixelsPerStaff);
        LineTo(hDC, staffLeftX, line+24);
        MoveTo(hDC, staffRightX, line-(scoreMultiplicity-1)*pixelsPerStaff);
        LineTo(hDC, staffRightX, line+24);
      }

      // Display the status information
      char strnum[15];
      HDC hStatusDC = GetDC(hMainWnd);
      HBRUSH hBrush, hOldBrush;
      hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
      hOldBrush = SelectObject(hStatusDC, hBrush);
      Rectangle(hStatusDC, 108, -1, GetSystemMetrics(SM_CXSCREEN), 37);
      SetBkColor(hStatusDC, GetSysColor(COLOR_WINDOW));
      SetTextAlign(hStatusDC, TA_UPDATECP);
      MoveTo(hStatusDC, 110, 2);
      TextOut(hStatusDC, 0, 0, "Score display, Staff ", 21);
      sprintf(strnum, "%d", scoreFirstStaff+1);
      TextOut(hStatusDC, 0, 0, strnum, strlen(strnum));
      SelectObject(hStatusDC, hOldBrush);
      DeleteObject(hBrush);
      ReleaseDC(hMainWnd, hStatusDC);
    }
  }

  EndPaint(hWnd, &ps);
}

// **********************************************
// Staff::Draw is the Staff class's Draw function,
// which draws a staff in a display context, returning TRUE
// if the staff was not entirely clipped.

BOOL Staff :: Draw(HDC hDC, int editYMin, BOOL clip)
{
  int line = Y();
  RECT staffRect = {0, line-editYMin, melody.GetStaffWidth(), line-editYMin+24};
  RECT windowRect, dummyRect;

  // Check whether staff is within screen range
  if (clip) GetClientRect(hEditWnd, &windowRect);
  if (!clip || IntersectRect(&dummyRect, &staffRect, &windowRect)) {
    // Either no clip checking or the staff is within clipping boundaries:
    // draw the staff lines
    for (; line < Y()+5*6; line += 6 ) {
      MoveTo(hDC, X(), line-editYMin);
      LineTo(hDC, X()+width(), line-editYMin);
    }
    return TRUE;
  }

  return FALSE;
}
