// **********************************************
// File: BLOCK.CPP
// Block operations module

#include "muzika.h"

int markBeginStaff = -1, markBeginX; // Staff and X of mark beginning
int markEndStaff = -1, markEndX;  // Staff and X of mark end

// **********************************************
// MarkBlock marks a block by copying the parameters
// to the marking variables.
// To actually reverse-video the marked area
// is a job of the PaintEditWindow function.

void MarkBlock(int staffFrom, int Xfrom, int staffTo, int Xto)
{
  Part &p = *((Part *) &melody.part[displayedPart]);
  markBeginStaff = staffFrom/p.multiplicity()*p.multiplicity();
  markBeginX = Xfrom;
  markEndStaff = staffTo/p.multiplicity()*p.multiplicity();
  markEndX = Xto;
}

// **********************************************
// UnmarkBlock unmarks a block by setting the marking variables
// to impossible values.

void UnmarkBlock()
{
  markBeginStaff = markEndStaff = -1;
}

// **********************************************
// CutBlock cuts the marked block to the clipboard.

void CutBlock()
{
  // Allocate a memory block (initially of 32K) for the cut operation
  GLOBALHANDLE hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 32767);
  char far *clipboardStart = GlobalLock(hMem);
  char far *clipboard = clipboardStart;
  Part &p = *((Part *) &melody.part[displayedPart]);

  // Write the marked block size into the clipboard
  *((int far *) clipboard)++ = p.multiplicity();
  *((int far *) clipboard)++ = markEndStaff-markBeginStaff;
  *((int far *) clipboard)++ = markBeginX;
  *((int far *) clipboard)++ = markEndX;

  // Write the musical object information into the clipboard
  for (int staffIndex = markBeginStaff;
    staffIndex/p.multiplicity()*p.multiplicity() <= markEndStaff;
    ++staffIndex) {
    Staff &s = *((Staff *) &p.staff[staffIndex]);

    // Cut the point objects:
    // do nothing with unmarked objects,
    // then clip and destroy the marked ones.
    // An object writes its own information to the clipboard
    // in the virtual clipOn function.
    for (int objectIndex = 0;
      objectIndex < s.pointObject.number() &&
        staffIndex/p.multiplicity() == markBeginStaff/p.multiplicity() &&
        ((PointObject *) &s.pointObject[objectIndex])->X() < markBeginX;
      ++objectIndex);
    while (objectIndex < s.pointObject.number() &&
      (staffIndex/p.multiplicity() < markEndStaff/p.multiplicity() ||
        ((PointObject *) &s.pointObject[objectIndex])->X() <= markEndX)) {
      ((PointObject *) &s.pointObject[objectIndex])->clipOn((void far *) clipboard);
      s.pointObject.destroy(objectIndex);
    }

    // Cut the continuous objects, similarly to the point ones
    for (objectIndex = 0;
      objectIndex < s.continuousObject.number() &&
        staffIndex/p.multiplicity() == markBeginStaff/p.multiplicity() &&
        ((ContinuousObject *) &s.continuousObject[objectIndex])->Xleft() < markBeginX;
      ++objectIndex);
    while (objectIndex < s.continuousObject.number() &&
      (staffIndex/p.multiplicity() < markEndStaff/p.multiplicity() ||
        ((ContinuousObject *) &s.continuousObject[objectIndex])->Xright() <= markEndX)) {
      ((ContinuousObject *) &s.continuousObject[objectIndex])->clipOn((void far *) clipboard);
      s.continuousObject.destroy(objectIndex);
    }

    // Put an end-of-staff marker
    *clipboard++ = STAFF;
  }

  // Erase the fully marked staves
  if (markEndStaff-markBeginStaff >= 2*p.multiplicity()) {
    int staffDiff = ((Staff *) &p.staff[markEndStaff])->Y()-
      ((Staff *) &p.staff[markBeginStaff+1])->Y();
    for (staffIndex = markBeginStaff+p.multiplicity();
      staffIndex < markEndStaff;
      ++staffIndex)
      p.staff.destroy(staffIndex);
    for (; staffIndex < p.staff.number(); ++staffIndex)
      ((Staff *) &p.staff[staffIndex])->Y() -= staffDiff;
  }

  // Unmark the block once the cut operation is finished
  UnmarkBlock();

  // Reallocate the 32K block to the actually needed size
  GlobalUnlock(hMem);
  GlobalReAlloc(hMem, clipboard-clipboardStart, 0);

  // Set a clipboard handle to point to the cut block information
  OpenClipboard(hMainWnd);
  EmptyClipboard();
  SetClipboardData(CF_PRIVATEFIRST, hMem);
  CloseClipboard();
}

// **********************************************
// CopyBlock copies the marked block to the clipboard.

void CopyBlock()
{
  // Allocate a memory block (initially of 32K) for the copy operation
  GLOBALHANDLE hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 32767);
  char far *clipboardStart = GlobalLock(hMem);
  char far *clipboard = clipboardStart;
  Part &p = *((Part *) &melody.part[displayedPart]);

  // Write the marked block size into the clipboard
  *((int far *) clipboard)++ = p.multiplicity();
  *((int far *) clipboard)++ = markEndStaff-markBeginStaff;
  *((int far *) clipboard)++ = markBeginX;
  *((int far *) clipboard)++ = markEndX;

  // Write the musical object information into the clipboard
  for (int staffIndex = markBeginStaff;
    staffIndex/p.multiplicity()*p.multiplicity() <= markEndStaff;
    ++staffIndex) {
    Staff &s = *((Staff *) &p.staff[staffIndex]);

    // Clip the point objects.
    // Each object writes its own information
    // in the virtual clipOn function.
    for (int objectIndex = 0; objectIndex < s.pointObject.number();
      ++objectIndex) {
      PointObject &obj = *((PointObject *) &s.pointObject[objectIndex]);
      if ((staffIndex/p.multiplicity() > markBeginStaff/p.multiplicity() ||
        obj.X() >= markBeginX) &&
        (staffIndex/p.multiplicity() < markEndStaff/p.multiplicity() ||
        obj.X() <= markEndX))
        obj.clipOn((void far *) clipboard);
    }

    // Clip the continuous objects, similarly to the point ones
    for (objectIndex = 0; objectIndex < s.continuousObject.number();
      ++objectIndex) {
      ContinuousObject &obj =
        *((ContinuousObject *) &s.pointObject[objectIndex]);
      if ((staffIndex/p.multiplicity() > markBeginStaff/p.multiplicity() ||
        obj.Xleft() >= markBeginX) &&
        (staffIndex/p.multiplicity() < markEndStaff/p.multiplicity() ||
        obj.Xright() <= markEndX))
        obj.clipOn((void far *) clipboard);
    }

    // Put an end-of-staff marker
    *clipboard++ = STAFF;
  }

  // Reallocate the 32K block to the actually needed size
  GlobalUnlock(hMem);
  GlobalReAlloc(hMem, clipboard-clipboardStart, 0);

  // Set a clipboard handle to point to the copied block data
  OpenClipboard(hMainWnd);
  EmptyClipboard();
  SetClipboardData(CF_PRIVATEFIRST, hMem);
  CloseClipboard();
}

// **********************************************
// PasteBlock pastes the block whose handle is in the clipboard
// to the given (staff,X) coordinate.

void PasteBlock(int pasteStaff, int pasteX)
{
  // Obtain a pointer to the clipboard entry
  OpenClipboard(hMainWnd);
  GLOBALHANDLE hMem = GetClipboardData(CF_PRIVATEFIRST);
  if (!hMem)
    return;
  char far *clipboard = GlobalLock(hMem);
  Part &p = *((Part *) &melody.part[displayedPart]);

  // Read the block parameters
  if (*((int far *) clipboard)++ != p.multiplicity())
    MessageBox(hEditWnd, "Cannot paste block - different staff multiplicity",
      NULL, MB_OK);
  else {
    // Read the block parameters to variables
    int staffDiff = *((int far *) clipboard)++;
    int beginX = *((int far *) clipboard)++;
    int endX = *((int far *) clipboard)++;
    pasteX = pasteX+endX-beginX < melody.GetStaffWidth() ? pasteX :
      melody.GetStaffWidth()-endX+beginX-1;
    BOOL singleStaffBlock = !staffDiff;

    // Paste a single-staff or a multiple-staff block
    // Add the height of the pasted block to all subsequent staves
    for (int index = pasteStaff+p.multiplicity();
      index < p.staff.number(); ++index)
      ((Staff *) &p.staff[index])->Y() += staffDiff*pixelsPerStaff;

    // The loop of pasting staves
    staffDiff += p.multiplicity()-1;
    index = pasteStaff;
    int staffY = ((Staff *) &p.staff[index])->Y();
    do {
      // If needed, insert a new staff into the melody
      if (index/p.multiplicity()*p.multiplicity() > pasteStaff)
        p.staff.insertAt(*new Staff(staffY += pixelsPerStaff), index);
      Staff &s = *((Staff *) &p.staff[index]);
      int type;

      // As long as the staff end is not reached, paste objects
      while (*clipboard != STAFF) {
        // Create an object from the information in the clipboard
        MusicalObject *m = PasteObject((void far *) clipboard, &type);
        switch (type) {
          case POINTOBJECT:
            // If the block is less than a single staff,
            // adjust the X coordinate of the pasted point object
            int X = ((PointObject *) m)->X();
            X += singleStaffBlock ? pasteX-beginX : 0;
            for (int i = 0;
              i < s.pointObject.number() &&
                ((PointObject *) &s.pointObject[i])->X() <= X;
              ++i);
            // Insert the object into the point object list
            s.pointObject.insertAt(*m, i);
            ((PointObject *) m)->X() = X;
            break;

          case CONTINUOUSOBJECT:
            // If the block is less than a single staff,
            // adjust the X coordinates of the pasted continuous object
            X = ((ContinuousObject *) m)->Xleft();
            X += singleStaffBlock ? pasteX-beginX : 0;
            for (i = 0;
              i < s.continuousObject.number() &&
                ((ContinuousObject *) &s.continuousObject[i])->Xleft() <= X;
              ++i);
            // Insert the object into the continuous object list
            s.continuousObject.insertAt(*m, i);
            ((ContinuousObject *) m)->Xright() += X-((ContinuousObject *) m)->Xleft();
            ((ContinuousObject *) m)->Xleft() = X;
            break;
        }
      }
      ++clipboard;
      ++index;
    } while (--staffDiff >= 0);
  }

  // Unlock the handle
  GlobalUnlock(hMem);
}
