//
import synth.*;
import java.awt.*;

public class Guitar2 extends BufferedApplet
{
// PUBLIC METHODS

   public void render(Graphics g) {
      if (w == 0) {
         w = bounds().width;
         h = bounds().height;
         initColors();
         synth.setInstrument(0, "Nylon Str Guitar");
      }

      g.setColor(Color.black);
      g.fillRect(0,0,w,h);

      g.setColor(scrimColor);
      int ax = X(0)-25, bx = ax+7, cx = bx+7;
      int ay = Y(st), by = ay+3, cy = by+4;
      g.drawLine(ax,by,cx,by);
      g.drawLine(bx,ay,cx,by);
      g.drawLine(bx,cy,cx,by);

      for (int s = 0 ; s < 6 ; s++) {
         int y = Y(s);
         for (int f = 0 ; f < 16 ; f++) {
            int x = X(f);
            int n = pitch[s] + f + 7;
            g.setColor(colors[n % colors.length]);
            g.fillRect(x, y, 32, 7);
            if (mode == 1 && n % 12 == (ci+3) % 12) {
               g.setColor(Color.white);
               g.fillRect(x, y+7, 32, 1);
            }
            g.setColor(scrimColor);
            String str = notes[n % 12];
            g.drawString(str, x + 15 - 3 * str.length(), y - 4);
         }

         g.setColor(colors[(pitch[s] + fret[s] + 7) % colors.length]);
         int grow = pp[s] > 0 ? 2 : 0;
         g.fillRect(X(17), y+1-grow, X(20) - X(14), 5+2*grow);

         g.setColor(scrimColor);
         if (fret[s] > 0)
            g.fillOval(X(fret[s]) - 17, y-5, 17, 17);
      }

      // DRAW THE CHORD FORMS

      for (int i = 0 ; i < chords.chord.length ; i++) {
         Color chordColor = i == mouseChord ? Color.white : scrimColor;
         int x = CX(i);
         int y = CY(i);
         String name = chordName(chords.chord[i]);
         String str = chordValue(chords.chord[i]);

         g.setColor(chordColor);
         for (int f = 0 ; f < 6 ; f++)
            g.fillRect(x + 5 * f, y, 1, 25);

         for (int s = 0 ; s < 6 ; s++) {
            int ys = y + 5 * s;

            g.setColor(chordColor);
            g.fillRect(x, ys, 25, 1);

            int ch = str.charAt(s);
            if (ch == '-') {
               g.drawLine(x - 6, ys - 2, x - 2, ys + 2);
               g.drawLine(x - 6, ys + 2, x - 2, ys - 2);
            }
            else {
               g.setColor(Color.white);
               int fret = str.charAt(s) - '0';
               if (fret > 0)
                  g.fillRect(x + 5 * fret - 1, ys - 1, 3, 3);
            }
         }
         g.setColor(chordColor);
         g.drawString(name, x + 12 - 4*name.length(), y + 40);
      }
   }

   public boolean keyDown(Event e, int key) {
      if (key >= '1' && key <= '6') {
         playNote(key - '1');
         return true;
      }

      if (key == 'm' || key == '7') {
         String s = chordName(chords.chord[lastChord]) + (char)key;
         if (setChord(s))
            return true;
      }
      if (key >= 'A' && key <= 'G')
         key += 'a' - 'A';
      if (key >= 'a' && key <= 'g') {
         String s = "" + (char)(key + 'A' - 'a');
         if (setChord(s))
            return true;
      }

      switch (key) {
      case '\n':
         String str = chordValue(chords.chord[lastChord]);
         for (int s = 0 ; s < 6 ; s++)
            if (str.charAt(s) != '-')
               playNote(s);
         break;
      case ' ':
         spaceBarIsDown = true;
         break;
      }
      return true;
   }

   public boolean keyUp(Event e, int key) {
      if (key >= '1' && key <= '6') {
         liftNote(key - '1');
         return true;
      }

      if (key >= 'A' || key <= 'G' || key >= 'a' || key <= 'g' || key == 'm' || key == '7')
         mouseChord = -1;

      switch (key) {
      case '\n':
         for (int s = 0 ; s < 6 ; s++)
            liftNote(s);
         break;
      case ' ':
         spaceBarIsDown = false;
         break;
      case 'x':
         mode = (mode + 1) % 2;
         damage = true;
         break;
      case 'z':
         writeChords();
         damage = true;
         break;
      case 1004: // UP
         if (mode == 0)
            st = Math.max(st-1, 0);
         else
            changeColorShift(0.1);
         break;
      case 1005: // DOWN
         if (mode == 0)
            st = Math.min(st+1, 5);
         else
            changeColorShift(-0.1);
         break;
      case 1006: // LEFT
         if (mode == 0)
            if (spaceBarIsDown)
               for (int s = 0 ; s < 6 ; s++)
                  fret[s] = Math.max(0, fret[s]-1);
            else
               fret[st] = Math.max(0, fret[st]-1);
         else
            ci = (ci + 11) % 12;
         break;
      case 1007: // RIGHT
         if (mode == 0)
            if (spaceBarIsDown)
               for (int s = 0 ; s < 6 ; s++)
                  fret[s] = Math.min(15, fret[s]+1);
            else
               fret[st] = Math.min(15, fret[st]+1);
         else
            ci = (ci + 1) % 12;
         break;
      }
      damage = true;
      return true;
   }

   public boolean mouseDown(Event e, int x, int y) {

      plucking = F(x) >= 17;
      mx = x;
      my = y;
      if (! plucking)
         if (settingChord = F(y) >= 6)
            setChord(mouseChord = findChord(x, y));
         else if (spaceBarIsDown)
            makeBar(x, y);
         else
            touchFret(x, y);

      damage = true;
      return true;
   }

   public boolean mouseDrag(Event e, int x, int y) {
      if (settingChord)
         setChord(mouseChord = findChord(x, y));

      else if (plucking) {
         int s0 = S(my), s1 = S(y);
         if (x >= X(17) && Math.abs(s0 - s1) == 1) {
            playNote(Math.max(s0,s1));
            my = y;
         }
      }
      else if (spaceBarIsDown)
         makeBar(x, y);
      else
         touchFret(x, y);

      damage = true;
      return true;
   }

   public boolean mouseUp(Event e, int x, int y) {
      if (settingChord) {
         settingChord = false;
         setChord(mouseChord);
         mouseChord = -1;
      }
      else
         for (int s = 0 ; s < 6 ; s++)
            liftNote(s);
      sp = -1;
      damage = true;
      return true;
   }

// PRIVATE METHODS

   private void initColors() {
      for  (int i = 0 ; i < colors.length ; i++) {
         int j = (i+9) % 12;
         double d = colorShift.shift[j];
         hsv[0] = ((j+d) / 12.0) % 1.0;
         ColorSpace.hsv2rgb(hsv, rgb);
         double t = (double)i / colors.length;
         for (int c = 0 ; c < 3 ; c++)
            rgb[c] = t < 0.5 ? lerp(.1 + 1.6 * t, 0, rgb[c])
                             : lerp(1.6 * (t - .5), rgb[c], 1);
         float r = Math.max(0,Math.min(1,(float)rgb[0]));
         float g = Math.max(0,Math.min(1,(float)rgb[1]));
         float b = Math.max(0,Math.min(1,(float)rgb[2]));
         colors[i] = new Color(r,g,b);
      }
   }

   private int Y(int s) { return 32 * (s + 1); }
   private int X(int f) { return 32 * (f + 1); }
   private int S(int y) { return Math.max(-1, Math.min(5, y / 32 - 1)); }
   private int F(int x) { return Math.max(-1, x / 32 - 1); }
   private int CX(int i) { return 34 + 37 * (i % (chords.chord.length/2)); }
   private int CY(int i) { return Y(6) + 4 + i / (chords.chord.length/2) * 60; }

   private String chordName(String s) { return s.substring(0,s.indexOf(":")); }
   private String chordValue(String s) { return s.substring(s.indexOf(":")+1,s.length()); }

   private void writeChords() {
      classWriter.begin("Chords");
      classWriter.write("chord", chords.chord);
      classWriter.end();
   }

   private boolean setChord(String s) {
      for (int i = 0 ; i < chords.chord.length ; i++)
         if (chordName(chords.chord[i]).equals(s)) {
            setChord(i);
            mouseChord = i;
            return true;
         }
      return false;
   }

   private void changeColorShift(double d) {
      if (mode != 1)
         return;
      colorShift.shift[ci] += d;
      colorShift.shift[ci] = (int)(10 * (colorShift.shift[ci] + 100) - 1000) / 10.0;

      classWriter.begin("ColorShift");
      classWriter.write("shift", colorShift.shift);
      classWriter.end();

      initColors();
      damage = true;
      for (int i = 0 ; i < 12 ; i++)
         System.err.print(colorShift.shift[i] + (i==ci? "* " : " "));
      System.err.println();
   }

   private void playNote(int s) {
      sp = Math.max(0, s);
      synth.noteOff(0,pp[sp],0);
      pp[sp] = 32 + pitch[sp] + fret[sp] + 7;
      synth.noteOn(0,pp[sp],63);
      damage = true;
   }

   private void liftNote(int s) {
      synth.noteOff(0,pp[s],0);
      pp[s] = 0;
      damage = true;
   }

   private void makeBar(int x, int y) {
      int s = Math.max(0, S(y + 7));
      int fmin = 15;
      for (int i = 0 ; i <= s ; i++)
         fmin = Math.min(fmin, fret[i]);
      for (int i = 0 ; i <= s ; i++)
         fret[i] = Math.min(14, fret[i] + F(x) + 1 - fmin);
      damage = true;
   }

   private void touchFret(int x, int y) {
      int s = Math.max(0, Math.min(5, S(y + 16)));
      fret[s] = Math.min(15, F(x) + 1);
      damage = true;
   }

   private int findChord(int x, int y) {
      for (int i = 0 ; i < chords.chord.length ; i++)
         if (x >= CX(i) && y >= CY(i) && x < CX(i) + 36 && y < CY(i) + 36)
            return i;
      return -1;
   }

   private void setChord(int i) {
      if (i < 0)
         return;
      lastChord = i;
      String str = chordValue(chords.chord[i]);
      for (int s = 0 ; s < 6 ; s++)
         fret[s] = str.charAt(s) - '0';
      damage = true;
   }

   private double lerp(double t, double a, double b) { return a + t * (b - a); }

// PRIVATE DATA

   private int w = 0, h = 0, mode = 0, st = 0;
   private int pitch[] = {24, 19, 15, 10, 5, 0};
   private int fret[] = {0,0,1,2,2,0};
   private double rgb[] = {0,0,0}, hsv[] = {0,1,1};
   private String notes[] = {"A","Bb","B","C","C#","D","Eb","E","F","F#","G","G#"};
   private Color colors[] = new Color[49];
   private Color scrimColor = new Color(255,255,255,128);
   private MidiSynth synth = new MidiSynth();
   private ColorShift colorShift = new ColorShift();
   private boolean plucking = false;
   private boolean settingChord = false;
   private boolean spaceBarIsDown = false;
   private int pp[] = new int[6], sp = -1, mx = 0, my = 0;
   private Chords chords = new Chords();
   private ClassWriter classWriter = new ClassWriter();
   private int ci = 0;
   private int mouseChord = -1;
   private int lastChord = 0;
}