#import "LinesView.h"

#import <dpsclient/wraps.h>	// For PS and DPS function prototypes
#import <appkit/Application.h>  // For NX_BASETHRESHOLD and peek event
#import <appkit/Control.h>	// For intValue, etc
#import <appkit/Window.h>	// We flushWindow at some point
#import <appkit/Menu.h>
#import <appkit/MenuCell.h>
#import <appkit/Form.h>		// For Inspector.
#import <appkit/Slider.h>	// For Inspector.
#import <appkit/nextstd.h>	// For MIN MAX
#import <libc.h>                // For random(), etc...
#import <soundkit/Sound.h>
#import <strings.h>
#import <ldsyms.h>
#import <sys/loader.h>


#define RANDINT(n) (random() % (n+1))		// Return random integer 0..n

#define XVEL corners[cnt].xVel  // Some slimy shortcuts, asuuming we're
#define YVEL corners[cnt].yVel  // using "cnt" as corner counter.
#define XLOC corners[cnt].xLoc
#define YLOC corners[cnt].yLoc

#define MAXVEL 12			// Maximum velocity of corners

static struct mach_header  *header;	// The header used for loading
static id   customSubmenu;		// Submenu handle for possible extension

static id   bouncePanel;		// Variables needed for inspector.
static id   bounceWidth;
static id   bounceHeight;
static id   bounceNumVertex;

@implementation LinesView

+ setCustomComponentData:(struct mach_header *)hd customMenu:(id)subMenu
{
    id  box;
    id  mCell;
    id  classObj;
    header = hd;
    customSubmenu = subMenu;
    mCell = [customSubmenu addItem:"Bouncing Lines"
                           action:@selector(showAlert)
                           keyEquivalent:0];
    classObj = [[LinesView alloc] init];
    [mCell setTarget:classObj];
    [customSubmenu display];

//  Setup Inspector.  N.B. This technique provides one inspector for all instances.
    
    [NXApp loadNibSection:"BounceLineInspect.nib" owner:classObj
                          withNames:YES fromHeader:header];
     
    bouncePanel = NXGetNamedObject("bounceLinesInspectorPanel" , classObj);
    box = NXGetNamedObject("sizeBox" , bouncePanel);
    bounceWidth = NXGetNamedObject("boxSizeWidth" , box);
       [bounceWidth setAction:@selector(setBounceWidth:)];
    bounceHeight = NXGetNamedObject("boxSizeHeight" , box);
       [bounceHeight setAction:@selector(setBounceHeight:)];
    box = NXGetNamedObject("vertexBox" , bouncePanel);
    bounceNumVertex = NXGetNamedObject("numVertexSlider" , box);
       [bounceNumVertex setAction:@selector(setBounceNumVertex:)];
       
    return classObj;
}

- (void) showAlert
{
    NXRunAlertPanel(NULL, "Custom Menu Activated", NULL, NULL, NULL);
}

- initCustomComponent
{
    NXRect   frm;
    int      ldata;
    char     *sd, *sectdata;
    char     *soundFile=malloc(512);
    
    sd = (char *)getsectdatafromheader(header, "__MVCUST", "initdata", &ldata);
    sectdata = malloc(ldata + 1);
    strcpy(sectdata, sd);
    
    sscanf(sectdata, "%f %f %f %f", &frm.origin.x, &frm.origin.y,
                                    &frm.size.width, &frm.size.height);
    self = [super initFrame:&frm];
    NXSetRect(&drawRect,bounds.origin.x,bounds.origin.y,
                        bounds.size.width,bounds.size.height);
    NXInsetRect(&drawRect,2.0,2.0);
    running = NO;
    srandom(time(0));  				// Randomize
    numCorners = 5;
    [self createOpsArray];
    [self resetBoundingBox];
    [self initCorners];
    [self initPath];
    [self display];
    
    if( sscanf(strchr(sectdata,'\n')+1, "%s", soundFile) != EOF ) {
       viewSound = [Sound newFromSoundfile:soundFile];
       [viewSound setDelegate:self];
       playing = NO;
    }
    free(sectdata);
    return self;
}

- (void) initPath
{
    int    cnt;
      for (cnt = 0; cnt < numCorners; cnt++) {
      XLOC += XVEL;
      YLOC += YVEL;
      if (XLOC >= drawRect.size.width) {
	XLOC = drawRect.size.width-1;
	XVEL = -1-RANDINT(MAXVEL);
      } else if (XLOC < drawRect.origin.x) {
	XLOC = drawRect.origin.x;
	XVEL = 1+RANDINT(MAXVEL);
      }
      if (YLOC >= drawRect.size.height) {
	YLOC = drawRect.size.height-1;
	YVEL = -1-RANDINT(MAXVEL);
      } else if (YLOC < drawRect.origin.y) {
	YLOC = drawRect.origin.y;
        YVEL = 1+RANDINT(MAXVEL);
      }
      if (XLOC < boundingBox[0]) boundingBox[0] = XLOC-1; 
      if (YLOC < boundingBox[1]) boundingBox[1] = YLOC-1;
      if (XLOC > boundingBox[2]) boundingBox[2] = XLOC+1;
      if (YLOC > boundingBox[3]) boundingBox[3] = YLOC+1;

      data[cnt * 2] = XLOC;
      data[cnt * 2 + 1] = YLOC;
    }
}

- initCorners
{
    int cnt;
    for (cnt = 0; cnt < MAXNUMCORNERS; cnt++) {
      XLOC = (int)(drawRect.size.width) / 2;      
      YLOC = (int)(drawRect.size.height) / 2;      
      XVEL = (RANDINT(1) ? 1 : -1) * (1 + RANDINT(MAXVEL/2));
      YVEL = (RANDINT(1) ? 1 : -1) * (1 + RANDINT(MAXVEL/2));
    }
    return self;
}
  
- (void) createOpsArray 
{
    int cnt;
    ops[0] = dps_moveto;
    for (cnt = 1; cnt < numCorners; cnt++) ops[cnt] = dps_lineto;
    ops[numCorners] = dps_closepath;
}

  void DrawAll (DPSTimedEntry te, double timeNow, void *data)
{
    [(id)data animate];
}

/*
- adjustScroll:(NXRect *)newVisible
{
    BOOL  visible;
    
    visible = NXIntersectsRect(&frame,newVisible);
    if( visible && !running ) {
       [self toggleRun];
       [self togglePlay];
    }
    if( !visible && running ) {
       [self toggleRun];
       [self togglePlay];
    }
    return self;
}
*/

- (BOOL)acceptsFirstMouse
{
    return YES;
}

- mouseDown:(NXEvent *)e
{
    if( e->flags & NX_COMMANDMASK ) {
       [self togglePlay];
    } else {
       [self toggleRun];
    }
    return self;
}

- toggleRun
{
    if (running) {
      DPSRemoveTimedEntry (linesTimedEntry);
      running = NO;
    } else {
      linesTimedEntry=DPSAddTimedEntry(0.1, &DrawAll,self,20);
      running = YES;
    }
    return self;
}

- togglePlay
{
    if( viewSound ) {
       if( playing) {
         playing = NO;
         [viewSound stop];
       } else {
         playing = YES;
         [viewSound play];
       }
    }
    return self;
}

- free
{
    if ( running ) DPSRemoveTimedEntry (linesTimedEntry);
    if ( playing ) [self togglePlay];
    return [super free];
}

- resetBoundingBox
{
    boundingBox[0] = drawRect.origin.x;
    boundingBox[1] = drawRect.origin.y;
    boundingBox[2] = drawRect.origin.x + drawRect.size.width;
    boundingBox[3] = drawRect.origin.y + drawRect.size.height;
    return self;
}
    
- drawSelf:(NXRect *)rects :(int)rectCount
{
    NXEraseRect (&bounds);
    [self resetBoundingBox];
    NXFrameRectWithWidth(&bounds,2.0);
    return self;
}

- (void) animate
{
  int cnt;  
  NXEvent dummyEvent;  // For peeking at the event queue. 

  [self lockFocus];
  do {
  
    [self eraseCurrentPath];
    boundingBox[0] = 10000;   // min x
    boundingBox[1] = 10000;   // min y
    boundingBox[2] = 0;       // max x
    boundingBox[3] = 0;       // max y

    for (cnt = 0; cnt < numCorners; cnt++) {
      XLOC += XVEL;
      YLOC += YVEL;
      if (XLOC >= drawRect.size.width) {
	XLOC = drawRect.size.width-1;
	XVEL = -1-RANDINT(MAXVEL);
      } else if (XLOC < drawRect.origin.x) {
	XLOC = drawRect.origin.x;
	XVEL = 1+RANDINT(MAXVEL);
      }
      if (YLOC >= drawRect.size.height) {
	YLOC = drawRect.size.height-1;
	YVEL = -1-RANDINT(MAXVEL);
      } else if (YLOC < drawRect.origin.y) {
	YLOC = drawRect.origin.y;
        YVEL = 1+RANDINT(MAXVEL);
      }
      if (XLOC < boundingBox[0]) boundingBox[0] = XLOC-1; 
      if (YLOC < boundingBox[1]) boundingBox[1] = YLOC-1;
      if (XLOC > boundingBox[2]) boundingBox[2] = XLOC+1;
      if (YLOC > boundingBox[3]) boundingBox[3] = YLOC+1;

      data[cnt * 2] = XLOC;
      data[cnt * 2 + 1] = YLOC;
    }
    [self drawCurrentPath:NX_BLACK];
    [[self window] flushWindow];

  } while ([NXApp peekNextEvent:NX_ALLEVENTS
   		  into:&dummyEvent 
		  waitFor:0.0
		  threshold:NX_BASETHRESHOLD] == NULL);
  [self unlockFocus];
}

- drawCurrentPath:(float)color
{
  PSsetgray (color);
  PSsetlinewidth (0.0);
  DPSDoUserPath (data, numCorners*2, dps_short, ops, numCorners+1,
		 boundingBox, dps_ustroke);
  return self;
}  

- (void) eraseCurrentPath
{
  NXEraseRect (&drawRect);
}

// Sound delegate.
- didPlay:sender
{
    if( playing ) [viewSound play];
    return self;
}

// Archiving methods

- write:(NXTypedStream *)stream
{
    int  lTE = (int)linesTimedEntry;
    [super write:stream];
    NXWriteTypes(stream,"ccii",&running,&playing,&numCorners,&lTE);
    NXWriteRect(stream,&drawRect);
    NXWriteArray(stream,"{iiii}",MAXNUMCORNERS,corners);
    NXWriteArray(stream,"c",MAXNUMCORNERS+1,ops);
    NXWriteArray(stream,"s",MAXNUMCORNERS*2,data);
    NXWriteArray(stream,"s",4,boundingBox);
    NXWriteObject(stream,viewSound);
    return self;
}

- read:(NXTypedStream *)stream
{
    int  lTE;
    [super read:stream];
    NXReadTypes(stream,"ccii",&running,&playing,&numCorners,&lTE);
    linesTimedEntry = (DPSTimedEntry) lTE;
    NXReadRect(stream,&drawRect);
    NXReadArray(stream,"{iiii}",MAXNUMCORNERS,corners);
    NXReadArray(stream,"c",MAXNUMCORNERS+1,ops);
    NXReadArray(stream,"s",MAXNUMCORNERS*2,data);
    NXReadArray(stream,"s",4,boundingBox);
    viewSound = NXReadObject(stream);
    return self;
}

//  Inspector Methods

- inspectCustomComponent
{
    [bounceWidth setTarget:self];
    [bounceHeight setTarget:self];
    [bounceNumVertex setTarget:self];

    [bounceNumVertex setIntValue:numCorners];
    [bounceWidth setIntValue:bounds.size.width at:0];
    [bounceWidth selectTextAt:0];
    [bounceHeight setIntValue:bounds.size.height at:0];
    [bouncePanel display];
    [bouncePanel makeKeyAndOrderFront:self];
    return self;
}

- setBounceWidth:sender
{
    NXRect  frm;
    [self getFrame:&frm];
    frm.size.width = [sender intValue];
    frm.size.height = [bounceHeight intValue]; 
    [self setFrame:&frm];
    [sender selectTextAt:0];
    NXSetRect(&drawRect,bounds.origin.x,bounds.origin.y,
                        bounds.size.width,bounds.size.height);
    NXInsetRect(&drawRect,2.0,2.0);
    [[self superview] display];
    return self;
}

- setBounceHeight:sender
{
    NXRect  frm;
    [self getFrame:&frm];
    frm.size.height = [sender intValue];
    frm.size.width = [bounceWidth intValue];
    [self setFrame:&frm];
    [sender selectTextAt:0];
    NXSetRect(&drawRect,bounds.origin.x,bounds.origin.y,
                        bounds.size.width,bounds.size.height);
    NXInsetRect(&drawRect,2.0,2.0);
    [[self superview] display];
    return self;
}

- setBounceNumVertex:sender
{
    numCorners = MIN(MAXNUMCORNERS,MAX([sender intValue],MINNUMCORNERS));
    [self createOpsArray];
    [self resetBoundingBox];
    [self display];
    return self;
}


@end
