// **********************************************
// File: EDIT.CPP
// Edit window input module

#include "muzika.h"
#include <stdlib.h>
#include <values.h>

// **********************************************
// InsertEmptyStaff inserts a new empty staff in the specified list
// given the staff Y location and the part multiplicity.
// The list is kept sorted by the Y locations in ascending order.

void InsertEmptyStaff(IndexedList &staffList, unsigned Y, int multiplicity)
{
  // Find the index to insert at
  for (int index = 0;
    index < staffList.number() && ((Staff *) &staffList[index])->Y() < Y;
    index += multiplicity);
  if (index > staffList.number())
    index = staffList.number();

  // Update coordinates of other staves and of the marked block
  for (int other = index; other < staffList.number(); ++other)
    ((Staff *) &staffList[other])->Y() += pixelsPerStaff;
  if (markBeginStaff >= index)
    ++markBeginStaff;
  if (markEndStaff >= index)
    ++markEndStaff;

  // Adjust Y to a multiple of pixelsPerStaff
  if (index)
    Y = ((Staff *) &staffList[index-1])->Y()+pixelsPerStaff;
  else
    Y = pixelsPerStaff;

  // Insert the staff
  staffList.insertAt(*new Staff(Y), index);
}

// **********************************************
// NewMultipleStaff creates a new multiple staff, after the user
// has clicked the pencil-on-staff symbol.
// The staff is inserted in the list, and the scroll bar range
// is readjusted.

void NewMultipleStaff(int Y)
{
  Part &p = *((Part *) &melody.part[displayedPart]);

  // Insert a new staff group in the database and refresh screen
  for (int i = 0; i < p.multiplicity(); ++i)
    InsertEmptyStaff(p.staff, Y+p.GetPartY(), p.multiplicity());
  melodyModified = TRUE;
  SetScrollRange(hEditWnd, SB_VERT, 0,
    ((Staff *) &p.staff[p.staff.number()-1])->Y(), TRUE);
  InvalidateRect(hEditWnd, NULL, TRUE);
}

// **********************************************
// IdentifyStaff finds the index of the staff to which the Y coordinate
// (obtained from the current cursor position) is closest.

int IdentifyStaff(IndexedList &staffList, unsigned Y)
{
  unsigned minDistance = MAXINT;
  int minIndex;
  int staffY;

  // Find a staff that is closest to the clicked point,
  // by minimizing the distance from different staves
  for (int index = 0; index < staffList.number(); ++index) {
    // If point actually on the staff, return it
    if ((staffY = ((Staff *) &staffList[index])->Y()) <= Y &&
      staffY+24 >= Y)
      return index;
    int distance = (staffY > Y) ? staffY-Y : Y-staffY-25;
    if (distance < minDistance) {
      minDistance = distance;
      minIndex = index;
    }
  }

  // Return a staff only if within reasonable distance
  return (minDistance < (pixelsPerStaff-25)/2) ? minIndex : -1;
}

// **********************************************
// DeleteMultipleStaff deletes the multiple staff identified
// by a Y coordinate (obtained from the current cursor position).
// If the staff contains any objects, the user is requested
// to confirm the operation.

void DeleteMultipleStaff(int Y)
{
  // Obtain a pointer to the staff to delete
  Part &p = *((Part *) &melody.part[displayedPart]);
  int staffIndex = IdentifyStaff(p.staff, (Y += p.GetPartY()));
  if (staffIndex < 0) return;
  staffIndex = staffIndex/p.multiplicity()*p.multiplicity();
  Staff *s = (Staff *) &p.staff[staffIndex];

  // Check if the multiple staff contains any objects
  for (int i = staffIndex;
    i/p.multiplicity() == staffIndex/p.multiplicity(); ++i) {
    Staff *s = (Staff *) &p.staff[i];
    if (s->pointObject.number() || s->continuousObject.number())
      if (MessageBox(hEditWnd, "Staff is not empty. Erase anyway?", "WARNING",
        MB_ICONEXCLAMATION | MB_YESNOCANCEL) == IDYES)
        break;
        else return;
  }

  // Erase the staff
  int staffY = s->Y();
  for (i = 0; i < p.multiplicity(); ++i)
    p.staff.destroyAt(staffIndex);
  if (markBeginStaff > staffIndex)
    markBeginStaff -= p.multiplicity();
  if (markEndStaff >= staffIndex)
    markEndStaff -= p.multiplicity();
  if (markEndStaff < markBeginStaff)
    UnmarkBlock();
  if (p.staff.number() > staffIndex) {
    int staffDiff = ((Staff *) &p.staff[staffIndex])->Y()-staffY;
    for (; staffIndex < p.staff.number(); ++staffIndex)
      ((Staff *) &p.staff[staffIndex])->Y() -= staffDiff;
  }

  // Mark the melody as modified and readjust the scroll bar range
  melodyModified = TRUE;
  SetScrollRange(hEditWnd, SB_VERT, 0,
    p.staff.number() ? ((Staff *) &p.staff[p.staff.number()-1])->Y() : 0, TRUE);
  InvalidateRect(hEditWnd, NULL, TRUE);
}

// **********************************************
// NewPointObject creates a point object corresponding to the
// active symbol at the given coordinate (obtained from the current
// cursor position), and inserts it in the staff according
// to the object _location attribute.

void NewPointObject(SymbolClass *symbol, int X, int Y)
{
  // Obtain the staff to insert the object into
  Part &p = *((Part *) &melody.part[displayedPart]);
  int staffIndex = IdentifyStaff(p.staff, (Y += p.GetPartY()));
  Staff *s = (staffIndex >= 0) ? (Staff *) &p.staff[staffIndex] : NULL;
  if (!s || X < s->X() || X >= s->X()+s->width()) return;

  // Create the object and decide where to insert it
  int first, last;
  MusicalObject *obj = symbol->CreateObject(staffIndex, WidthRound(X-s->X()), Y -= s->Y());
  if (obj) {
    X = ((PointObject *) obj)->X();

    // Check the object _location attribute
    switch (obj->location() & ~ONEPERSTAFF) {
      case INSTAFF:
      case ABOVESTAFF:
      case BELOWSTAFF:
        first = last = staffIndex;
        break;

      case ABOVEMULTIPLE:
        first = last = staffIndex/p.multiplicity()*p.multiplicity();
        break;

      case BELOWMULTIPLE:
        first = last = (staffIndex/p.multiplicity()+1)*p.multiplicity()-1;
        break;

      case COMMONMULTIPLE:
        first = staffIndex/p.multiplicity()*p.multiplicity();
        last = (staffIndex/p.multiplicity()+1)*p.multiplicity()-1;
        break;
    }

    // Insert the objects in the required staff or staves
    for (staffIndex = first; staffIndex <= last; ++staffIndex) {
      s = (Staff *) &p.staff[staffIndex];
      IndexedList &list = s->pointObject;
      PointObject *obj1;
      for (int index = 0;
        index < list.number() && (obj1 = (PointObject *) &list[index])->X() <= X;
        ++index)
        if (obj1->location() & ONEPERSTAFF && obj1->X() == X) {
          MessageBox(hEditWnd, "Only one such symbol is allowed per staff", NULL,
            MB_ICONEXCLAMATION | MB_OK);
          return;
        }
      list.insertAt(
        (staffIndex == first) ? *obj : *symbol->CreateObject(staffIndex, X, Y), index);
    }
  }
  melodyModified = TRUE;

  // Refresh screen
  InvalidateRect(hEditWnd, NULL, !obj);
}

// **********************************************
// NewContinuousObject creates a continuous object corresponding to the
// active symbol at the given coordinate (obtained from the current
// cursor position), and inserts it in the staff according
// to the object _location attribute.

void NewContinuousObject(SymbolClass *symbol, int Xleft, int Xright, int Y)
{
  // Obtain the staff to insert the object into
  Part &p = *((Part *) &melody.part[displayedPart]);
  int staffIndex = IdentifyStaff(p.staff, (Y += p.GetPartY()));
  Staff *s = (staffIndex >= 0) ? (Staff *) &p.staff[staffIndex] : NULL;
  if (!s || Xleft < s->X() || Xright >= s->X()+s->width()) return;

  // Create the object and decide where to insert it
  int first, last;
  MusicalObject *obj =
    symbol->CreateObject(staffIndex, WidthRound(Xleft-s->X()), WidthRound(Xright-s->X()));
  if (obj) {
    Xleft = ((ContinuousObject *) obj)->Xleft();
    Xright = ((ContinuousObject *) obj)->Xright();

    // Check the object _location attribute
    switch (obj->location() & ~ONEPERSTAFF) {
      case INSTAFF:
      case ABOVESTAFF:
      case BELOWSTAFF:
        first = last = staffIndex;
        break;

      case ABOVEMULTIPLE:
        first = last = staffIndex/p.multiplicity()*p.multiplicity();
        break;

      case BELOWMULTIPLE:
        first = last = (staffIndex/p.multiplicity()+1)*p.multiplicity()-1;
        break;

      case COMMONMULTIPLE:
        first = staffIndex/p.multiplicity()*p.multiplicity();
        last = (staffIndex/p.multiplicity()+1)*p.multiplicity()-1;
        break;
    }

    // Insert the object in the appropriate staff or staves
    for (staffIndex = first; staffIndex <= last; ++staffIndex) {
      s = (Staff *) &p.staff[staffIndex];
      IndexedList &list = s->continuousObject;
      ContinuousObject *obj1;
      for (int index = 0;
        index < list.number() &&
          (obj1 = (ContinuousObject *) &list[index])->Xleft() <= Xleft;
        ++index)
        if (obj1->location() & ONEPERSTAFF && obj1->Xleft() == Xleft) {
          MessageBox(hEditWnd, "Only one such symbol is allowed per staff", NULL,
            MB_ICONEXCLAMATION | MB_OK);
          return;
        }
      list.insertAt((staffIndex == first) ? *obj :
        *symbol->CreateObject(staffIndex, Xleft, Xright), index);
    }
  }
  melodyModified = TRUE;

  // Refresh screen
  InvalidateRect(hEditWnd, NULL, !obj);
}

// **********************************************
// DeleteMusicalObject deletes the objects that are within
// (pixelsPerObject/2) pixels away from the given coordinate
// (obtained from the current cursor position).

void DeleteMusicalObject(int X, int Y)
{
  // Obtain the staff to delete the objects from
  Part &p = *((Part *) &melody.part[displayedPart]);
  int staffIndex = IdentifyStaff(p.staff, (Y += p.GetPartY()));
  Staff *s = (staffIndex >= 0) ? (Staff *) &p.staff[staffIndex] : NULL;
  if (!s || X < s->X() || X >= s->X()+s->width()) return;

  // Scan the point objects list and delete any objects in range
  X -= s->X();
  Y -= s->Y();
  for (int index = 0; index < s->pointObject.number(); ++index) {
    PointObject *obj = (PointObject *) &s->pointObject[index];
    if (abs(obj->X()-X) < pixelsPerObject/2)

      // Check the object _location attribute to see if
      // any special treatment is required
      switch (obj->location() & ~ONEPERSTAFF) {
        case ABOVESTAFF:
        case ABOVEMULTIPLE:
          if (Y < 0) {
            s->pointObject.destroyAt(index);
            --index;
          }
          break;

        case BELOWSTAFF:
        case BELOWMULTIPLE:
          if (Y > 24) {
            s->pointObject.destroyAt(index);
            --index;
          }
          break;

        case INSTAFF:
          s->pointObject.destroyAt(index);
          --index;
          break;
      }
  }

  // Scan the continuous objects list and delete any objects in range
  for (index = 0; index < s->continuousObject.number(); ++index) {
    ContinuousObject *obj = (ContinuousObject *) &s->continuousObject[index];
    if (abs(obj->Xleft()-X) < pixelsPerObject/2)
      switch (obj->location() & ~ONEPERSTAFF) {
        case ABOVESTAFF:
        case ABOVEMULTIPLE:
          if (Y < 0) {
            s->continuousObject.destroyAt(index);
            --index;
            melodyModified = TRUE;
          }
          break;

        case BELOWSTAFF:
        case BELOWMULTIPLE:
          if (Y > 24) {
            s->continuousObject.destroyAt(index);
            --index;
            melodyModified = TRUE;
          }
          break;

        case INSTAFF:
          s->continuousObject.destroyAt(index);
          --index;
          melodyModified = TRUE;
          break;
      }
  }

  // Scan all staves in group and delete COMMONMULTIPLE objects
  int first = staffIndex/p.multiplicity()*p.multiplicity();
  int last = (staffIndex/p.multiplicity()+1)*p.multiplicity()-1;
  for (staffIndex = first; staffIndex <= last; ++staffIndex) {
    // Delete COMMONMULTIPLE point objects
    s = (Staff *) &p.staff[staffIndex];
    for (index = 0; index < s->pointObject.number(); ++index) {
      PointObject *obj = (PointObject *) &s->pointObject[index];
      if (abs(obj->X()-X) < pixelsPerObject/2 &&
        (obj->location() & ~ONEPERSTAFF) == COMMONMULTIPLE) {
        s->pointObject.destroyAt(index);
        --index;
        melodyModified = TRUE;
      }
    }
    for (index = 0; index < s->continuousObject.number(); ++index) {
      // Delete COMMONMULTIPLE continuous objects
      ContinuousObject *obj = (ContinuousObject *) &s->continuousObject[index];
      if (abs(obj->Xleft()-X) < pixelsPerObject/2 &&
        (obj->location() & ~ONEPERSTAFF) == COMMONMULTIPLE) {
        s->continuousObject.destroyAt(index);
        --index;
        melodyModified = TRUE;
      }
    }
  }

  // Refresh screen
  InvalidateRect(hEditWnd, NULL, TRUE);
}

// **********************************************
// MoveStaff moves a staff from Yfrom to Yto, both coordinates
// obtained from the mouse cursor. Before actually moving,
// the destination is checked to be free from other staves.

void MoveStaff(int Yfrom, int Yto)
{
  // Obtain a pointer to the staff to move
  Part &p = *((Part *) &melody.part[displayedPart]);
  int staffIndex = IdentifyStaff(p.staff, (Yfrom += p.GetPartY()));
  if (staffIndex < 0) return;
  staffIndex = staffIndex/p.multiplicity()*p.multiplicity();
  Staff *s = (Staff *) &p.staff[staffIndex];
  int lastMoved = staffIndex+p.multiplicity()-1;

  // Verify that the destination is not occupied
  Yto += p.GetPartY();
  for (int indexTo = 0; indexTo < p.staff.number(); indexTo += p.multiplicity())
    if (indexTo != staffIndex) {
      Staff &firstTo = *((Staff *) &p.staff[indexTo]);
      Staff &lastTo = *((Staff *) &p.staff[indexTo+p.multiplicity()-1]);
      if (Yto <= lastTo.Y()+24 &&
        ((Staff *) &p.staff[lastMoved])->Y()-s->Y()+Yto+24 >= firstTo.Y()) {
        // The destination is occupied:
        // display an error message
        MessageBox(hEditWnd, "Cannot move onto another staff", NULL,
          MB_ICONEXCLAMATION | MB_OK);
        return;
    }
  }

  // Set the staff new coordinates and move it
  int distance = Yto-s->Y();
  for (; staffIndex <= lastMoved; ++staffIndex)
    ((Staff *) &p.staff[staffIndex])->Y() += distance;
  staffIndex -= p.multiplicity();
  for (indexTo = 0;
    indexTo < p.staff.number() && ((Staff *) &p.staff[indexTo])->Y() < Yto;
    indexTo += p.multiplicity());
  while (staffIndex <= lastMoved) {
    s = (Staff *) &p.staff[staffIndex];

    // Detach and re-insert staves whose index is between
    // the old and new indexes of the moved staff
    p.staff.detachAt(staffIndex);
    if (indexTo > staffIndex) {
      --lastMoved;
      --indexTo;
    }
    p.staff.insertAt(*s, indexTo);
    if (staffIndex >= indexTo) {
      ++staffIndex;
      ++indexTo;
    }
  }

  // Refresh the display
  UnmarkBlock();
  melodyModified = TRUE;
  InvalidateRect(hEditWnd, NULL, TRUE);
  SetScrollRange(hEditWnd, SB_VERT, 0,
    ((Staff *) &p.staff[p.staff.number()-1])->Y(), TRUE);
}

// **********************************************
// MoveMusicalObject moves a musical object by using
// the CutBlock and PasteBlock functions in BLOCK.CPP.

void MoveMusicalObject(int Xfrom, int Yfrom, int Xto, int Yto)
{
  // Verify both points are on the same staff
  Part &p = *((Part *) &melody.part[displayedPart]);
  int staffFrom = IdentifyStaff(p.staff, Yfrom+p.GetPartY());
  int staffTo = IdentifyStaff(p.staff, Yto+p.GetPartY());
  if (staffFrom < 0 || staffTo < 0)
    return;
  staffFrom = staffFrom/p.multiplicity()*p.multiplicity();
  staffTo = staffTo/p.multiplicity()*p.multiplicity();
  int staffX = ((Staff *) &p.staff[staffFrom])->X();

  // Use the cut and paste functions to move the objects
  MarkBlock(staffFrom, WidthRound(Xfrom-staffX),
    staffFrom, WidthRound(Xfrom-staffX));
  CutBlock();
  PasteBlock(staffTo, WidthRound(Xto-staffX));
  InvalidateRect(hEditWnd, NULL, TRUE);
}
