//
//  Viewey.m
//  try_empty_window
//
//  Created by david van brink on 7/22/10.
//  Copyright 2010 omino.com. All rights reserved.
//

#import "Viewey.h"
#include "OmCoreGraphicsUtils.h"
#include "OmUiUtils.h"
#include "OmPincher.h"
#import "OmUtils.h"
#import "OmController.h"
#import "OmPlaceEdit.h"
#import "OmPrefs.h"

#include "OmLocColors.h"

@implementation Viewey

//@synthesize locationManager;


// +-------------------------------------------------
// | Constants
// |

#define PI 3.14159265358979
#define DEGREES_PER_RADIAN 57.29577951

#define MSECS_PER_POINT (5000)
#define MSECS_FADE_OUT (1000)
#define MSECS_HOLD (10000)

#define FRAME_RATE_HIGH 24
#define FRAME_RATE_LOW 8
#define FRAME_RATE_VLOW 4

#define MSECS_TIL_SLOW_FRAME_RATE 2000
#define MSECS_TIL_VSLOW_FRAME_RATE 10000


#define MSECS_SHOW_SCALE (1400)
#define MSECS_SHOW_SCALE_FADE_OUT (400)

#define MSECS_TIL_RECENTER_MAP 6200
#define TOO_LONG_SINCE_LAST_UPDATE 12000

#define MAP_TOP 0
#define MAP_LEFT 0
#define MAP_WIDTH 320
#define MAP_HEIGHT 320
#define MAP_BOTTOM (MAP_TOP + MAP_HEIGHT)
#define MAP_RIGHT (MAP_LEFT + MAP_WIDTH)


// while locked, if you select elsewhere, return to locked after this.
#define LOCKED_SNAPBACK_DELAY 1200

// Buttons (actions)
enum
{
    B_CLEAR = 100,
    B_LOG,
    B_ADD_RANDOM_LOCATIONS,
    B_MOVE_A_BIT,
    B_INFO,
    B_SAVE,
    B_LOAD,
    B_EDIT,
    B_PREFS,
    
    blast
};




// +-------------------------------------------------
// | Prototypes
// |
void selectPoint(Viewey *v,int ix);
void changeFrameRate(Viewey *v,int newFrameRate);
void setSlowFrameRate(Viewey *v,int newFrameRate);
void setHighFrameRate(Viewey *v);
void editCurrentPlace(Viewey *viewey);


#ifdef DEBUG
#define TOUCHES_MAX 5
int touchCount;
CGPoint touches[TOUCHES_MAX];
#endif

static double dist(double x1,double y1,double x2,double y2)
{
    x1 -= x2;
    y1 -= y2;
    double d = sqrt(x1 * x1 + y1 * y1);
    return d;
}

static double slewAngle(double current,double desired,double slewRate)
{
    double del = desired - current;
    while(del > PI)
        del -= 2*PI;
    while(del < -PI)
        del += 2*PI;
    if(del > slewRate)
        current += slewRate;
    else if(del < -slewRate)
        current -= slewRate;
    else
        current += del * .7;

    if(current > PI)
        current -= 2*PI;
    if(current <-PI)
        current += 2*PI;
    
    return current;
}

static void slew(Viewey *v,double *value,double desired,double slewRatePerSecond)
{
    double slewRate = slewRatePerSecond / v->frameRate;
    double diff = desired - *value;
    if(diff > slewRate)
        diff = slewRate;
    else if(diff < -slewRate)
        diff = -slewRate;
    *value += diff;
}

static void addCurrentLocation(Viewey *v)
{
    if(v->location)
    {
        OmWayPoint p = {0};
        p.lat = v->location.coordinate.latitude;
        p.lon = v->location.coordinate.longitude;
        NSDate *date = [NSDate date];
        p.t = [date timeIntervalSince1970];
        [v->places addWayPoint:&p];
    }
}


UIImage *GResizeImage(UIImage *src,CGRect srcRect,CGSize dstSize,bool scale)
{
    UIGraphicsBeginImageContext(dstSize);
    CGRect dstRect;
    if(scale)
        dstRect = CGRectMake(0, 0, dstSize.width, dstSize.height);
    else
        dstRect = CGRectMake((dstSize.width - srcRect.size.width)/2.0,(dstSize.height - srcRect.size.height)/2,srcRect.size.width,srcRect.size.height);
    
    [src drawInRect:dstRect];
    UIImage *dst = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndPDFContext();
    return dst;
}

- (void)showScale
{
    self->msecsShowScale = MSECS_SHOW_SCALE;
}

- (void)reload
{
    bool result = [self->places loadFromFile];
    NSLog(@"result:%d",result);
    self->pixelsPerMeter = 1.5;
    [self showScale];
}

- (void)resave
{
    bool result = [self->places saveToFile];
    NSLog(@"result:%d",result);
}


// +-----------------------------------
// | After successfully snapping a picture, end up here
// | We resize and affiliate with the point.
// |
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    CGSize imgSize = image.size;
    int a = imgSize.width;
    if(a < imgSize.height)
        a = imgSize.height;
    image = GResizeImage(image,CGRectMake(0,0,a,a),CGSizeMake(160, 160),true);
    
    [image retain] ;
//    OmWayPoint *p = [places getWayPoint:0];
    OmWayPoint *p = [places getWayPoint:([places getWayPointCount]-1)];
    p->image = image;
    [controller dismissModalViewControllerAnimated:NO];
    [picker release];
    editCurrentPlace(self);
    selectPoint(self,10000); // select "last" one
    [self resave];
    
    
}


- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    // canceled the Log, delete the last point. We added it. 
    [self->places clearLast];
    selectPoint(self,0);
    [controller dismissModalViewControllerAnimated:YES];
}



- (void) snap
{
    // no camera? just go home.
    if(![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
        return;
    
    UIImagePickerController *im = [[UIImagePickerController alloc] init];
    im.sourceType = UIImagePickerControllerSourceTypeCamera;
    im.delegate = self;
    im.allowsEditing = NO;
    [controller presentModalViewController:im animated:YES];
}

void editCurrentPlace(Viewey *viewey)
{
    OmPlaceEdit *oe = [OmPlaceEdit alloc];
    [oe setPlaceList:viewey->places index:viewey->selectedPoint];
    [OmController animateViewSlide:oe parentController:viewey->controller];
}

void prefs(Viewey *viewey)
{
    OmPrefs *oe = [OmPrefs alloc];
    [oe setPlaceList:viewey->places lat:viewey->lat lon:viewey->lon acc:viewey->locationHorizontalAccuracy];
    [OmController animateViewSlide:oe parentController:viewey->controller];
}


- (void) buttonAction:(UIButton *)sender
{
    int tag = [sender tag];
    if(tag == B_CLEAR)
    {
        self->deleteLastEntryActionSheet = OmActionAskNoYes(self, @"Cancel", @"Delete", "Permanently delete place %d?",[self->places getWayPointCount]);
    }
    
    if(tag == B_LOG)
    {
        // add the way point, and THEN snap a photo (which might be tossed)
        addCurrentLocation(self);
        selectPoint(self,[places getWayPointCount] - 1);
        [self snap];
        [self resave];
    }
    
    if(tag == B_EDIT)
    {
        editCurrentPlace(self);
    }
    if(tag == B_PREFS)
    {
        prefs(self);
    }
    
    if(tag == B_ADD_RANDOM_LOCATIONS)
    {
        double metersRange = 100; // radius to scatter
        
        for(int i = 0; i < 3; i++)
        {
            int rx = (lrand48() & 0x00ff) - 0x80;
            int ry = (lrand48() & 0x00ff) - 0x80;

            OmWayPoint p = {0};
            p.lat = location.coordinate.latitude + rx * metersRange / 128.0 / metersPerLatitude;
            p.lon = location.coordinate.longitude + ry * metersRange / 128.0 / metersPerLongitude;
            p.t = [[NSDate date] timeIntervalSince1970];
            [self->places addWayPoint:&p];
        }
    }
    
    if(tag == B_MOVE_A_BIT)
    {
        double metersRange = 30; // radius to scatter
        int rx = (lrand48() & 0x00ff) - 0x80;
        int ry = (lrand48() & 0x00ff) - 0x80;
        
        double newLat = location.coordinate.latitude + rx * metersRange / 128.0 / metersPerLatitude;
        double newLon = location.coordinate.longitude + ry * metersRange / 128.0 / metersPerLongitude;
        [location release];
        location = [CLLocation alloc];
        [location initWithLatitude:newLat longitude:newLon];
    }
    
    if(tag == B_INFO)
    {
        OmAlertf("gpsish","lat:%.4f lon:%.4f h-acc:%.4f alt:%.0f v-acc:%.3f dir:%.0f c-acc:%.0f",
                 self->lat,
                 self->lon,
                 self->locationHorizontalAccuracy,
                 self->alt,
                 self->locationVerticalAccuracy,
                 self->compass * DEGREES_PER_RADIAN,
                 self->compassAccuracy
                 );
    }
    
    
    if(tag == B_SAVE)
    {
        [self resave];
    }

    if(tag == B_LOAD)
    {
        [self reload];
    }
    
}

// after backgrounding, we do a *partial* init.
// reset the map size, and dont assume gps is working yet
- (void)applicationWillEnterForeground
{
    self->compassAvailable = [CLLocationManager headingAvailable];
    self->compass = 0;
    self->compassSlew = 0;
    self->compassAccuracy = 0;
    
    self->lastLocationUpdate = self->msec + 2 * TOO_LONG_SINCE_LAST_UPDATE; // some safety margin to get started...
    self->lastCompassUpdate = self->msec + 2 * TOO_LONG_SINCE_LAST_UPDATE; // some safety margin to get started...
    self->msecsTilShowNextLocation = 0;
    
    self->draggingMap = false;
    self->mapOffset.x = 0;
    self->mapOffset.y = 0;
    self->pixelsPerMeter = 3.0;
    self->locationHorizontalAccuracy = 10000;
    
}

- (void)initStuff
{
    frame = 0;
    drawCount = 0;
    
    self->msec = 0;
    
    [self applicationWillEnterForeground];
    
    // Initialization code
    self->locationUpdates = 0;
    
    {
        // clear the history
        self->histNextMsec = 0;
        self->histCurPos = 0;
        for(int i = 0; i < HIST_COUNT; i++)
        {
            self->histPoints[i].lat = 0;
            self->histPoints[i].lon = 0;
        }
    }
    
    self->places = [[OmPlacesList alloc] initNewMap:MAP_NAME_1];
    
    self->locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;
    [locationManager startUpdatingLocation];
    [locationManager startUpdatingHeading];
    
    self->location = 0; // most recent location
    
#define BSIZE 44
#define BMGN 9
    CGRect br = CGRectMake(MAP_BOTTOM - BMGN - BSIZE,MAP_RIGHT - BMGN - BSIZE,BSIZE,BSIZE);
    logButton = [self addButton:@"log" tag:B_LOG rect:br];
    br.origin.y -= BSIZE + BMGN;
    self->editButton = [self addButton:@"edit" tag:B_EDIT rect:br];
    br.origin.y -= BSIZE + BMGN;
    [self addButton:@"prefs" tag:B_PREFS rect:br];
    
#ifdef DEBUG
    br.origin.y -= BSIZE + BMGN;
    [self addButton:@"gps" tag:B_INFO rect:br];
    [self addButton:@"clear last" tag:B_CLEAR x:70 y:290 w:80 h:20];
              [self addButton:@"+" tag:B_ADD_RANDOM_LOCATIONS x:10 y:290 w:20 h:20];
              [self addButton:@">" tag:B_MOVE_A_BIT x:40 y:290 w:20 h:20];
    //addButton(self,@"save",B_SAVE,10,220,40,40);
    //addButton(self,@"load",B_LOAD,10,170,40,40);
#endif
    
    [self startTimer];
//    [self setBgRgb:0x808090];
    [self setBgRgb:0x000000];
    [self setMultipleTouchEnabled:YES]; // means we handle multi-touch locally
    
    self->pincher = [OmPincher alloc];
    self->pixelsPerMeter = 1.0;
    
    [self reload];
}


- (id)initWithFrame:(CGRect)rect {
    if ((self = [super initWithFrame:rect])) {
    }
    return self;
}


- (BOOL)textFieldShouldReturn:(UITextField *)tf
{
    [tf resignFirstResponder];
    return YES;
}
                               



void changeFrameRate(Viewey *v,int newFrameRate)
{
    if(newFrameRate == v->frameRate && v->timer != 0)
        return;

    if(v->timer)
    {
        [v->timer invalidate]; // stops it, removes from all run loops
        //[v->timer release];
    }
    v->frameRate = newFrameRate;
    v->msecPerFrame = 1000 / v->frameRate;
    v->timer = [NSTimer timerWithTimeInterval:(1.0f/v->frameRate) target:v selector:@selector(animationTimer:) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:v->timer forMode:NSDefaultRunLoopMode];
}

void setSlowFrameRate(Viewey *v,int newFrameRate)
{
    changeFrameRate(v,newFrameRate);
}
void setHighFrameRate(Viewey *v)
{
    changeFrameRate(v,FRAME_RATE_HIGH);
    v->msecsSinceLastTouch = 0;
}

- (void) startTimer
{
    self->timer = 0;
    setHighFrameRate(self);
}

// per-window timer function, basic time based animation performed here
- (void)animationTimer:(NSTimer *)timer
{
    frame++;
    self->msec += self->msecPerFrame;
    
    // update our recently-been history
    if(self->msec > self->histNextMsec)
    {
        self->histNextMsec = self->msec + HIST_MSEC;

        // track history points only if we're pretty darned accurate.
        if(self->locationHorizontalAccuracy < 20)
        {
            self->histCurPos = (self->histCurPos + 1) % HIST_COUNT;
            OmLatLon *oll = self->histPoints + self->histCurPos;
            oll->lat = self->lat;
            oll->lon = self->lon;
        }
    }
    
    self->msecsSinceLastTouch += self->msecPerFrame;
    int newFrameRate = FRAME_RATE_HIGH;
    if(self->msecsSinceLastTouch > MSECS_TIL_SLOW_FRAME_RATE)
        newFrameRate = FRAME_RATE_LOW;
    if(self->msecsSinceLastTouch > MSECS_TIL_VSLOW_FRAME_RATE)
        newFrameRate = FRAME_RATE_VLOW;
    setSlowFrameRate(self,newFrameRate);
    
    // maybe start returning to center
    if(self->msecsSinceLastTouch > MSECS_TIL_RECENTER_MAP)
    {
        self->mapOffset.x *= 0.9;
        self->mapOffset.y *= 0.9;
    }
    
    if(!self->lockedLocation)
    {
        msecsTilShowNextLocation -= msecPerFrame;
        if(msecsTilShowNextLocation < 0)
        {
            int k = [places getWayPointCount];
            if(k > 1)
                selectPoint(self,(selectedPoint + 1) % k);
        }
    }
    else if(self->lockedSnapbackMsec < self->msec)
        selectPoint(self,self->lockedPoint);
    
    // slew the compass, for tidiness
    self->compassSlew = slewAngle(self->compassSlew, self->compass, 2.0 / self->frameRate);
    
    [self setNeedsDisplayInRect:[self bounds]];
}

- (void)dealloc {
    [super dealloc];
}


#define WAY_POINT_HIT_RADIUS 24
bool selectWayPoint(Viewey *v,NSSet *touches)
{
//    if(v->lockedLocation)
//        return false; // discard selections when locked!

    if([touches count] != 1)
        return false;
	UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:v];
    if(touchLocation.y > MAP_HEIGHT) // TODO constants for rectangles.
        return false;
    double closestDist = 1e23;
    int closestIx = -1;
    for(int i = 0; i < [v->places getWayPointCount]; i++)
    {
        OmWayPoint *p = [v->places getWayPoint:i];
        double d = dist(p->screenPoint.x,p->screenPoint.y,touchLocation.x,touchLocation.y);
        if(d < WAY_POINT_HIT_RADIUS && d < closestDist)
        {
            closestDist = d;
            closestIx = i;
        }
    }
    
    bool did = closestIx >= 0;
    if(did)
    {
        selectPoint(v,closestIx);
    }
    return did;
}

void selectPoint(Viewey *v,int ix)
{
    int k = [v->places getWayPointCount];
    if(!k)
        return;

    if(ix >= k)
        ix = k - 1;
    v->selectedPoint = ix;
    v->msecsTilShowNextLocation = MSECS_HOLD;
    v->lockedSnapbackMsec = v->msec + LOCKED_SNAPBACK_DELAY;
}

static void logTouches(Viewey *viewey,NSSet *touches, char *msg)
{
#ifdef DEBUG
    NSLog(@"Event: %s\n",msg);
    int count = [touches count];
    NSEnumerator *e = [touches objectEnumerator];
    for(int i = 0; i < count; i++)
    {
        UITouch *touch = [e nextObject];
        CGPoint touchP = [touch locationInView:viewey];
        NSLog(@"%d  (%f,%f)\n",i,touchP.x,touchP.y);
    }
#endif
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    logTouches(self,touches,"touchesBegan");
    [super touchesBegan:touches withEvent:event];
    setHighFrameRate(self);
    
	self->selectingWayPoints = selectWayPoint(self,touches);
    
    
//    addCurrentLocation(self);
    
    self->pixelsPerMeterBeforePinch = self->pixelsPerMeter;
    self->mapOffsetBeforePinch = self->mapOffset;
    [self->pincher begin:touches view:self];

    CGRect tnR = CGRectMake(0,320,160,160); //thumbnail Rect
    CGRect mapR = CGRectMake(MAP_LEFT,MAP_TOP,MAP_WIDTH,MAP_HEIGHT); // map rect

    int touchCount = [touches count];
    if(touchCount == 1)
    {
        UITouch *touch = [touches anyObject];
        CGPoint touchP = [touch locationInView:self];
        if(CGRectContainsPoint(tnR, touchP))
        {
            // Touch inside the picture display (thumbnail rect)
            if(self->msecsTilShowNextLocation > MSECS_PER_POINT && !self->lockedLocation)
            {
                // we're already "holding" on the single-white rect... lock on.
                self->lockedLocation = true;
                self->lockedPoint = self->selectedPoint;
            }
            else
            {
                self->msecsTilShowNextLocation = MSECS_HOLD;
                self->lockedLocation = false;
                self->lockedPoint = -1;
            }
        }
        else if(CGRectContainsPoint(mapR,touchP))
        {
            self->mapOffsetBeforeDrag = self->mapOffset;
            self->draggingMap = true;
            self->mapDragStartPoint = touchP;
        }
    }

}


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
{
    logTouches(self,touches,"touchesBegan");
    setHighFrameRate(self);
    //int touchCount = [touches count];
    
    if(self->selectingWayPoints)
        selectWayPoint(self,touches);

    // two-finger skid? stop dragging.
    //if(touchCount != 1)
    //    self->draggingMap = false;

    if(self->draggingMap)
    {
        UITouch *touch = [touches anyObject];
        CGPoint touchP = [touch locationInView:self];
        float newMapOffsetX = self->mapOffsetBeforeDrag.x + touchP.x - self->mapDragStartPoint.x;
        float newMapOffsetY = self->mapOffsetBeforeDrag.y + touchP.y - self->mapDragStartPoint.y;
#define SUSPICIOUS_DRAG_DISTANCE 66
        double dragDistance = dist(self->mapOffset.x, self->mapOffset.y, newMapOffsetX, newMapOffsetY);
        if(dragDistance < SUSPICIOUS_DRAG_DISTANCE)
        {
            self->mapOffset.x = self->mapOffsetBeforeDrag.x + touchP.x - self->mapDragStartPoint.x;
            self->mapOffset.y = self->mapOffsetBeforeDrag.y + touchP.y - self->mapDragStartPoint.y;
        }
    }
    
    OmPinchData *pinchData = [self->pincher move:touches];
    if(pinchData->pinching)
    {
        self->draggingMap = false;
        self->pixelsPerMeter = self->pixelsPerMeterBeforePinch * pinchData->ratio;
        self->mapOffset.x = self->mapOffsetBeforePinch.x * pinchData->ratio;
        self->mapOffset.y = self->mapOffsetBeforePinch.y * pinchData->ratio;
        [self showScale];
    }
}

// Calculate scale & offset values for transforming lat/lon to pixel positions
// these are reused for waypoints and several other markers.
static void setupScreenWayPointXform(Viewey *viewey)
{
    viewey->scPixelsPerLatitude = viewey->metersPerLatitude * viewey->pixelsPerMeter;
    viewey->scPixelsPerLongitude = viewey->metersPerLongitude * viewey->pixelsPerMeter;
    viewey->scViewCenterX = 160 + viewey->mapOffset.x;
    viewey->scViewCenterY = 160 + viewey->mapOffset.y;
    
    viewey->scCompassSin = sin(viewey->compassSlew);
    viewey->scCompassCos = cos(viewey->compassSlew);
}

static void update1Coord(Viewey *viewey,double lat,double lon,CGPoint *screenPointOut)
{
    double x = (lon - viewey->lon) * viewey->scPixelsPerLongitude;
    double y = (lat - viewey->lat) * viewey->scPixelsPerLatitude;
    
    // now we're in pixels from zero, positive up. Rotate for compas.
    {
        double x1 = x * viewey->scCompassCos + y * viewey->scCompassSin;
        y = -x * viewey->scCompassSin + y * viewey->scCompassCos;
        x = x1;
    }

    screenPointOut->x = x + viewey->scViewCenterX;
    // y is down, but north is up. negate, negate.
    screenPointOut->y = -y + viewey->scViewCenterY;
}
static void updateScreenWayPoints(Viewey *viewey)
{

//    OmWayPoint now;
//    now.lat = viewey->lat; //viewey->location.coordinate.latitude;
//    now.lon = viewey->lon; //viewey->location.coordinate.longitude;
//    now.alt = viewey->location.altitude;
//    
//    OmWayPoint *center = &now;//[places getWayPoint:0];
    
    
    for(int i = 0; i < [viewey->places getWayPointCount]; i++)
    {
        OmWayPoint *p = [viewey->places getWayPoint:i];
        update1Coord(viewey,p->lat,p->lon,&p->screenPoint);
    }
}

double distanceFromLocation(CLLocation *location,double lat,double lon)
{
    CLLocation *tmp = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
    double result = [tmp distanceFromLocation:location];
    [tmp release];
    return result;
}


void rotatePoint(CGPoint *p,double s,double c,double scale,double tx,double ty)
{
    double x = p->x * c + p->y * s;
    double y = -p->x * s + p->y * c;
    x = x * scale + tx;
    y = y * scale + ty;
    p->x = x;
    p->y = y;
}

- (void)drawRect:(CGRect)rect
{
    drawCount++;
    CGContextRef g = UIGraphicsGetCurrentContext();


    /* text needs this :( */
    CGAffineTransform tt = CGAffineTransformMake(1.0,0.0, 0.0, -1.0, 0.0, 0.0);
    CGContextSetTextMatrix(g, tt);
    
    double viewCenterX = 160 + mapOffset.x;
    double viewCenterY = 160 + mapOffset.y;

    
    
    bool lost = self->msec - self->lastLocationUpdate > TOO_LONG_SINCE_LAST_UPDATE;
    bool directionless = self->compassAvailable && (self->msec - self->lastCompassUpdate > TOO_LONG_SINCE_LAST_UPDATE);

    if(lost)
    {
        self->locationHorizontalAccuracy = 1000;
    }
    
    if(directionless)
    {
        self->compassAccuracy = -1.0;
    }
    
    // disable the log button if its too far out of whack
    [self->logButton setEnabled:self->locationHorizontalAccuracy < 100];
    [self->editButton setEnabled:[self->places getWayPointCount] > 0];
        

    // populate some necessary math values, for screen rotation &c
    setupScreenWayPointXform(self);
    
    updateScreenWayPoints(self);

    
    
    
    // draw the earth oval
    // radius of the earth = 6378.1km == 637810m
    GSetFillColor(g,CO_BIGGEST_CIRCLE);
        GDrawPip(g,viewCenterX,viewCenterY,637810 * self->pixelsPerMeter,true);

    // map center determines where to draw the radii
    double mapCenterX = viewCenterX;
    double mapCenterY = viewCenterY;
    int mapCenterIndex = [self->places getMapCenter];
    if(mapCenterIndex >= 0)
    {
        OmWayPoint *ctrP = [self->places getWayPoint:mapCenterIndex];
        mapCenterX = ctrP->screenPoint.x;
        mapCenterY = ctrP->screenPoint.y;
    }
    
    // draw the "10 minute" oval
    GSetFillColor(g, CO_TEN_MINUTE_CIRCLE);
    double largerRingRadius = 10 * WALKING_METERS_PER_MINUTE;
    largerRingRadius = 636; // approximate radius from Man to Esplenade
    GDrawPip(g,mapCenterX,mapCenterY,largerRingRadius * self->pixelsPerMeter,true);

    // draw the "1 minute" oval
    GSetFillColor(g, CO_ONE_MINUTE_CIRCLE);
    double smallerRingRadius = WALKING_METERS_PER_MINUTE;
    GDrawPip(g,mapCenterX,mapCenterY,smallerRingRadius * self->pixelsPerMeter,true);

    // background of the moving brick suggests location accuracy
    {
        int ungoodColor = 0;
        int height = 5;
        int ha = location.horizontalAccuracy; // in meters
        if(lost || ha > 200) // basically not working
            ungoodColor = 0xff0000;
        else if(ha > 100) // 162 ish, not happy yet
            ungoodColor = 0x800000;
        else if(location.horizontalAccuracy > 50)
            ungoodColor = 0x603000;
        else if(location.horizontalAccuracy > 20)
            ungoodColor = 0x204000;
        else if(location.horizontalAccuracy > 10)
            ungoodColor = 0x007000;
        else {
            ungoodColor = 0x00ff00;
            height = 8;
        }

        if(ungoodColor)
            GFillRectR(g,CGRectMake(0,0,rect.size.width,height),ungoodColor);
    }
    
    
    // draw pips for recent history points
    {
        GSetFillColor(g, 0x9000ff);
        float ixBump = 2;
        int ix = 0;
        while(ix < HIST_COUNT)
        {
            OmLatLon *oll = self->histPoints + ((self->histCurPos - ix + HIST_COUNT) % HIST_COUNT);
            ix += ixBump;
            ixBump *= 1.5;
            CGPoint screenPoint;
            update1Coord(self,oll->lat,oll->lon,&screenPoint);
            GDrawPip(g, screenPoint.x, screenPoint.y, 2, false);
        }
    }
    
    // Your Icon
    {
        GSetStrokeColor(g, 0xff0000);
        double uSize = 6;
        CGContextMoveToPoint(g, viewCenterX - uSize, viewCenterY - uSize * 1.5);
        CGContextAddArc(g,viewCenterX,viewCenterY,uSize,PI,0,1);
        CGContextAddLineToPoint(g, viewCenterX + uSize,viewCenterY - uSize * 1.5);
        CGContextSetLineWidth(g, 3.3);
        CGContextStrokePath(g);
    }


    
    // screen pips,broad bg
    // dimmer (except for active) if locked
    if(self->lockedLocation)
        GSetFillColor(g, 0x9f9f90);
    else
        GSetFillColor(g, 0xffffc0);

    
    lat = 23;//loc.coordinate.latitude;
    lon = 45.67;//loc.coordinate.longitude;
    if(location)
    {
        lat = location.coordinate.latitude;
        lon = location.coordinate.longitude;
    }
    

    // draw lines path-to-path...
    if(((int)(frame / frameRate)) % 3 == 0 && [places getWayPointCount] > 0)
    {
        GSetStrokeColor(g, 0xffffff);
        // ramp alpha up & down...
        int fm = (frame % frameRate);
        if(fm > frameRate / 2)
            fm = frameRate - fm;
        CGContextSetAlpha(g,fm / (double)frameRate);
        CGContextBeginPath (g);
        OmWayPoint *p = [places getWayPoint:0];
        CGContextMoveToPoint(g, p->screenPoint.x, p->screenPoint.y);
        for(int i = 1; i < [places getWayPointCount]; i++)
        {
            OmWayPoint *p = [places getWayPoint:i];
            CGContextAddLineToPoint(g, p->screenPoint.x, p->screenPoint.y);
        }
        CGContextSetLineWidth(g, 1);
        CGContextStrokePath(g);
        CGContextSetAlpha(g,(1.0));
    }

    // highlighted pip
    if(selectedPoint >= 0 && selectedPoint < [places getWayPointCount])
    {
        OmWayPoint *p = [places getWayPoint:selectedPoint];
        int blinkPhase = (self->msec / 250) % 7;
        
        // if its locked AND selected, draw it special (else, draw regular, maybe snap back soon)
        if(self->lockedLocation && self->lockedPoint == self->selectedPoint)
        {
            GSetFillColor(g,CO_LOCKED_MARKER);
            GDrawPip(g,p->screenPoint.x,p->screenPoint.y,9 - blinkPhase / 3.0,true);
        }
        else
        {
            GSetFillColor(g,CO_SELECTED_MARKER);
            GDrawPip(g,p->screenPoint.x,p->screenPoint.y,8 - blinkPhase / 3.0 ,true);
//            GSetFillColor(g,0xffd044);
//            GDrawPip(g,p->screenPoint.x,p->screenPoint.y,5.5,true);
        }
    }
//    // in any case, draw the center of the locked point if any (even if another yellow one is selected, for later snapback)
//    if(self->lockedLocation)
//    {
//        OmWayPoint *p = [places getWayPoint:self->lockedPoint];
//        GSetFillColor(g,0x40c000);
//        GDrawPip(g,p->screenPoint.x,p->screenPoint.y,4.6,true);
//    }

    GSetFillColor(g, 0x400010);
    for(int i = 0; i < [places getWayPointCount]; i++)
    {
        [places drawWayPoint:i context:g];
    }
    
    // moving "live" brick (influenced by location accuracy and frame rate
    if(!lost)
    {
        float tAcross = 2.0; // seconds for pip to cross the screen
        int segments = frameRate * tAcross;
        int segment = (int)(msec / tAcross * segments / 1000) % segments;
        int x1 = rect.size.width * segment / segments;
        int x2 = rect.size.width * (segment + 1) / segments;
        
        // call 40 meters the worst
        int red = 256 * location.horizontalAccuracy / 40;
        if(red > 0xff)
            red = 0xff;
        red = red << 16;
        
        GFillRectR(g,CGRectMake(x1,0,x2-x1,5),red + 0x00e010);
        //        GFillRectR(g,CGRectMake(x1,0,x2-x1,5),0x901010);
    }
    
    

    // compute scale
    double w = MAP_WIDTH / pixelsPerMeter / WALKING_METERS_PER_MINUTE;
    bool angs = (w < .00001); // are we down in the deep subatomics?
    
    // maybe print the scale...
    if(self->msecsShowScale > 0)
    {
        self->msecsShowScale -= msecPerFrame;
        
        if(self->msecsShowScale < MSECS_SHOW_SCALE_FADE_OUT)
            CGContextSetAlpha(g, (float)self->msecsShowScale / MSECS_SHOW_SCALE_FADE_OUT);

        // the fading out screen-scale indicator
        int vPos = 20;
        int hPos = 35;
        
        GSetFillColor(g,0x10FF30);
        GFillWedge(g,10,vPos,2,vPos + 10,-1);
        GFillWedge(g,310,vPos,318,vPos + 10,-1);
        GFillRectR(g,CGRectMake(5,vPos + 4,hPos - 7,3),-1);
        GSetFont(g, 1, 17);
        w = floor(w * 2) / 2;
        CGPoint rtP;
        if(!angs)
        {
            if(w > 85000000)
            {
                w = w / 42075901.3;
                rtP = GPrintf(g,hPos,vPos + 11,"%.0f lifetimes walk",w);
            }
            else
                rtP = GPrintf(g, hPos,vPos + 11, "%.1f minutes walk",w);
        }
        else
        {
            w = MAP_WIDTH / pixelsPerMeter * 10000000000.0; // 10 billion angstroms per meter
            rtP = GPrintf(g, hPos,vPos + 11, "%.5f Angstroms",w);
        }
        GFillRectR(g,CGRectMake(rtP.x + 2,vPos + 4,MAP_WIDTH - 7 - rtP.x,3),-1);
        CGContextSetAlpha(g,1.0);
        
    }
    if(angs) // subatomic map view
    {
        // The H nucleus, 1.75fm, 1.75e-15
        int k = frame % 5;
        if(k == 0 || k == 2)
        {
            CGContextSetAlpha(g,1.0);
            GSetFillColor(g,0xff0000);
            GDrawPip(g, viewCenterX, viewCenterY, 1.75e-15 * pixelsPerMeter, true);
        }
    }
    
    

    // ----------------------------------------------------
    // lower half
    if(selectedPoint >= 0 && selectedPoint < [places getWayPointCount])
    {
        OmWayPoint *p = [places getWayPoint:selectedPoint];
        
        
        int top = 325;
        int left = 165;
        int y = top;
        NSDate *d = [NSDate date];
        long t = [d timeIntervalSince1970];
        long age = t - p->t;
        long ageMinutes = (age + 30) / 60;
        double metersAway = distanceFromLocation(location,p->lat,p->lon);
        int minutesToWalk = (WALKING_METERS_PER_MINUTE/2 + metersAway) / WALKING_METERS_PER_MINUTE;
        int paces = (metersAway + 1) * .66;

        int pivotX = 240;
        int pivotY = 400;
        
        // And draw.
        GFillRectR(g,CGRectMake(0, 320, 320, 160),0x444470);
        GSetFillColor(g, 0xffffff);

        // choose color for compass pointer, if we're On Target Somewhere
        int co;
        if(self->lockedLocation && self->lockedPoint == selectedPoint)
            co = 0x007f40;
        else
            co = 0x808080;
        
        GSetFillColor(g,co);
        GSetStrokeColor(g,co);
        
        // draw the compass pointer (or Here We Are pip)
        if(paces > 15)
        {
            double direction = atan2(self->lat - p->lat,self->lon - p->lon);
            pointerRotation = slewAngle(pointerRotation, direction, 3.0 / frameRate);
            // TODO understand up down left right... meanwhile fudge
            direction = pointerRotation + PI;
            direction -= self->compassSlew;
            double s = sin(direction);
            double c = cos(direction);
            
#define COMPASS_POINTER_RADIUS 70
#define COMPASS_POINTER_CENTER_X 240
#define COMPASS_POINTER_CENTER_Y 400
            
            
            CGPoint p1 = CGPointMake(1.0, 0.0);
            CGPoint p2 = CGPointMake(-1.0,-.3);
            CGPoint p3 = CGPointMake(-1.0,0.3);
            
            rotatePoint(&p1,s,c,COMPASS_POINTER_RADIUS,COMPASS_POINTER_CENTER_X,COMPASS_POINTER_CENTER_Y);
            rotatePoint(&p2,s,c,COMPASS_POINTER_RADIUS,COMPASS_POINTER_CENTER_X,COMPASS_POINTER_CENTER_Y);
            rotatePoint(&p3,s,c,COMPASS_POINTER_RADIUS,COMPASS_POINTER_CENTER_X,COMPASS_POINTER_CENTER_Y);
            
            CGContextBeginPath (g);
            CGContextMoveToPoint(g, p1.x, p1.y);
            CGContextAddLineToPoint(g, p2.x, p2.y);
            CGContextAddLineToPoint(g, p3.x, p3.y);
            CGContextAddLineToPoint(g, p1.x, p1.y);
            CGContextFillPath(g);
            
            // stroke the uncertainty
            if(self->compassAvailable)
            {
                if(self->compassAccuracy < 0)
                    GSetStrokeColor(g,0xff0000);
                CGContextAddArc(g, COMPASS_POINTER_CENTER_X, COMPASS_POINTER_CENTER_Y, COMPASS_POINTER_RADIUS * .7, -direction - self->compassAccuracy, -direction + self->compassAccuracy, 0);
                CGContextSetLineWidth(g, 4);
                CGContextStrokePath(g);
            }
            // stroke north
            s = sin(-PI/2 + self->compassSlew);
            c = cos(-PI/2 + self->compassSlew);
            GSetStrokeColor(g, 0x222238);
            CGContextMoveToPoint(g, COMPASS_POINTER_CENTER_X, COMPASS_POINTER_CENTER_Y);
            CGContextAddLineToPoint(g, COMPASS_POINTER_CENTER_X + c * COMPASS_POINTER_RADIUS * .7, COMPASS_POINTER_CENTER_Y + s * COMPASS_POINTER_RADIUS * .7);
            CGContextStrokePath(g);
        }
        else 
        {
            GDrawPip(g, pivotX, pivotY, 25, true);
        }

        
        GSetFont(g, 3, 13);
#define LINE_BUMP 14
        GSetFillColor(g,CO_LABEL_TEXT);
        y += LINE_BUMP; GPrintf(g,left,y,"%d of %d",self->selectedPoint + 1,[self->places getWayPointCount]);
        if(p->desc && [p->desc length])
        {
            NSSTRING_TO_C(p->desc,descC)
            y += LINE_BUMP; GPrintf(g,left,y,"\"%s\"",descC);
        }
        y += LINE_BUMP;
        if(ageMinutes < 100)
            GPrintf(g, left, y, "%d minutes ago",ageMinutes);
        else
            GPrintf(g,left,y,"%s",p->timeDescription);

        y += LINE_BUMP; 
        if(minutesToWalk > 0)
            GPrintf(g,left,y,"%d minutes to walk",minutesToWalk);
        else
            GPrintf(g,left,y,"%d paces",paces);
#ifdef DEBUG
        y += LINE_BUMP; 
        y += LINE_BUMP; GPrintf(g,left,y,"locs %d",self->locationUpdates);
        y += LINE_BUMP; GPrintf(g,left,y,"comp acc %.2f",self->compassAccuracy * DEGREES_PER_RADIAN);
        y += LINE_BUMP; GPrintf(g,left,y,"draws: %d",self->drawCount);
#endif
        GSetFont(g, 1, 12);
        GSetFillColor(g,0x000020);
        y = 474; GPrintf(g,left,y,"It is %s.",nowString());
        
        
        
        CGRect tnR = CGRectMake(0,320,160,160); //thumbnail Rect
        
        // Draw the image, or the placeholder
        if(p->image)
        {
            [p->image drawInRect:tnR];
//            CGImageRef cgim = p->image.CGImage;
//            CGContextDrawImage(g,CGRectMake(0,320,160,160),cgim);
        }
        else
        {
            GFillRectR(g,tnR,CO_MISSING_COLOR);
            CGRect r2 = tnR;
            r2.origin.x += 20;
            r2.size.width -= 40;
            r2.origin.y += 70;
            r2.size.height -= 140;
            GFillRectR(g, r2, 0x000030);
            GSetFont(g,1,40);
            GPrintf(g,20,460,"%d",selectedPoint);
        }
        
        // enable the fadeout, if any
        if(msecsTilShowNextLocation < MSECS_FADE_OUT)
        {
            // draw a black square on the fadeout
            CGContextSetAlpha(g, 1.0 - msecsTilShowNextLocation / (double)MSECS_FADE_OUT);
            GFillRectR(g, tnR, 0);
            CGContextSetAlpha(g, 1);
        }
        
        // cover the curious interactions of locked, and finger-selection to temporarily see other places
        if(self->lockedLocation && self->selectedPoint == self->lockedPoint)
        {
            // locked, and presently showing! draw extra frame
            GFrameRectR(g,tnR,2.5,0x00FF80);
            CGRect tnInsetR = tnR;
            GInsetRect(&tnInsetR,5,5);
            GFrameRectR(g,tnInsetR,2.2,0x007f40);
        }
        else if (msecsTilShowNextLocation > MSECS_PER_POINT && !self->lockedLocation)
        {
            // the "little lock", just an extension before cycling to the next
            GFrameRectR(g,tnR,3,0xdddddd);
        }
    }
    else {
        // no poins or selected points, but do blank the zones
        GFillRectR(g,CGRectMake(0,320,320,160),0x101010);
    }

    // Draw the danger question mark??
    if(lost)
    {
        CGContextSetAlpha(g,0.7);
        GFillRectR(g, CGRectMake(MAP_LEFT,MAP_TOP, MAP_WIDTH, MAP_HEIGHT), 0x000000);
        CGContextSetAlpha(g,1.0);
        CGPoint ctr = CGPointMake(160,160);
        double phase = (self->msec - self->lastLocationUpdate - TOO_LONG_SINCE_LAST_UPDATE) % 800 / 800.0;
        double phase2 = (self->msec - self->lastLocationUpdate - TOO_LONG_SINCE_LAST_UPDATE) % 4116 / 4116.0;
        GSetStrokeColor(g, 0xff0000);
        double radius = 80;
        double startAngle = -PI/2;
        double endAngle = startAngle + 2.9 * PI * phase;
        
        // soothing more... less violent.
        endAngle = startAngle + 2.1 * PI;
        
        CGContextAddArc (g,ctr.x,ctr.y,radius,startAngle,endAngle,0);
        CGContextSetLineWidth(g, 3 + 12 * (1 - phase));
        GSetStrokeColor(g, 0xff0000);
        CGContextStrokePath(g);
        if(phase2 > .7)
        {
            GSetFillColor(g, 0x00ff00);
            GSetFont(g, 3, 19);
            GPrintf(g, ctr.x - radius + 6, ctr.y + 6, "Lost.");
        }
    }
    

    
    [super drawRect:rect];
    
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation 
{
    if(location)
    {
        [location release];
        location = 0;
    }
    location = newLocation;
    [location retain];
    self->locationUpdates++;
    self->lastLocationUpdate = self->msec;
    
    self->lat = location.coordinate.latitude;
    self->lon = location.coordinate.longitude;
    self->alt = location.altitude;
    self->locationHorizontalAccuracy = location.horizontalAccuracy;
    self->locationVerticalAccuracy = location.verticalAccuracy;
    self->metersPerLatitude = distanceFromLocation(location,lat + 1,lon);
    self->metersPerLongitude = distanceFromLocation(location,lat,lon + 1);
}

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
    self->lastCompassUpdate = self->msec;
    CLLocationDirection direction = newHeading.trueHeading;
    // clockwise, north, in degrees.
    // make it counterclockwise, in radians, kthx. (since it's deviation from North, "up", we needn't shift anything).
    self->compass = (-direction) / DEGREES_PER_RADIAN;
    self->compassAccuracy = newHeading.headingAccuracy / DEGREES_PER_RADIAN;
}


- (void) setController:(UIViewController *)newController;
{
    self->controller = newController;
}


// for handling modal dialogs
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    // 0 means destruct, 1 meant cancel, for an action sheet
    if(actionSheet == self->deleteLastEntryActionSheet)
    {
        if(buttonIndex == 0) // yes!
        {
            [places clearLast];
            selectPoint(self,10000); // select "last" one
            [self resave];
        }
        self->deleteLastEntryActionSheet = nil;
    }
}


@end
