//
// $Id: WorldMapView.m,v 1.8 1997/10/31 04:52:16 nygard Exp $
//

//
//  This file is a part of Empire, a game of exploration and conquest.
//  Copyright (C) 1996  Steve Nygard
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//  You may contact the author by:
//     e-mail:  nygard@telusplanet.net
//

#import "Empire.h"

RCSID ("$Id: WorldMapView.m,v 1.8 1997/10/31 04:52:16 nygard Exp $");

#import "WorldMapView.h"
#import "Brain.h"
#import "Map.h"
#import "EmpireImageVendor.h"
#import "Map.h"
#import "MapView.h" // To pick up delegate protocol definition (no longer..)
#import "EmpireProtocols.h" // To pick up delegate protocol definition

#define GRIDSIZE 4.0

//======================================================================
// The World Map View provides a small view of the entire world.  I'm
// not sure what the normal map view would look like if it were scaled
// down to this size.
//
// Much of the interface should be the same as MapView.
//
//======================================================================

@implementation WorldMapView

- initWithFrame:(NSRect)frameRect
{
    EmpireImageVendor *vendor = [EmpireImageVendor instance];
    Player p;
  
    [super initWithFrame:frameRect];

    [vendor attach:self];

    for (p = p_neutral; p <= p_player3; p++)
        playerColors[p] = [vendor colorForPlayer:p];

    terrainColors[0] = [[NSColor blackColor] retain];
    terrainColors[1] = [[NSColor colorWithCalibratedRed:0 green:0 blue:153 / 255.0 alpha:1.0] retain];
    terrainColors[2] = [[NSColor colorWithCalibratedRed:0 green:85 / 255.0 blue:0 alpha:1.0] retain];
    terrainColors[3] = [playerColors[0] retain];

    compressEvents = NO;

    return self;
}

//----------------------------------------------------------------------

- (void) dealloc
{
    int l;
  
    [[EmpireImageVendor instance] detach:self];

    for (l = 0; l < 4; l++)
    {
        SNRelease (terrainColors[l]);
    }

    if (map != nil)
        [map detach:self];

    [super dealloc];
}

//----------------------------------------------------------------------

- (BOOL) acceptsFirstResponder
{
    return YES;
}

//----------------------------------------------------------------------

- (BOOL) acceptsFirstMouse:(NSEvent *)theEvent
{
    return YES;
}

//----------------------------------------------------------------------

#define RCOUNT 16

// This builds up lists of RCOUNT rectangles for the different colors,
// and then fills them as a group when full.  This gets pretty good
// performance.

- (void) drawRect:(NSRect)rect
{
    EMMapLocation location1, location2;
    int row, column;
    EMMapSize mapSize;
    NSPoint aPoint;
    MapToken **mapPtrs = [map mapPtrs];
    int l;

    NSRect blackRects[RCOUNT];
    NSRect playerRects[4][RCOUNT];
    NSRect terrainRects[4][RCOUNT];
    int blackCount;
    int playerCount[4];
    int terrainCount[4];

    if (map != nil)
    {
        mapSize = [map mapSize];

        blackCount = 0;
        for (l = 0; l < 4; l++)
            playerCount[l] = terrainCount[l] = 0;

        aPoint.x = NSMinX (rect);
        aPoint.y = NSMinY (rect);
        location1 = [self getLocationForPoint:aPoint];

        aPoint.x += NSWidth (rect);
        aPoint.y += NSHeight (rect);
        location2 = [self getLocationForPoint:aPoint];
        
        for (row = location2.row; row <= location1.row; row++)
        {
            aPoint.y = GRIDSIZE * (float)(mapSize.height - 1 - row);
            for (column = location1.column; column <= location2.column; column++)
            {
                MapToken mapToken = mapPtrs[row][column];
                Terrain terrain;
                Player player;
                BOOL explored;

                EMMapTokenComponents (mapToken, &terrain, &player, NULL, &explored);

                aPoint.x = GRIDSIZE * (float)column;

                if (explored == NO)
                {
                    blackRects[blackCount++] = NSMakeRect(aPoint.x, aPoint.y, GRIDSIZE, GRIDSIZE);
                    if (blackCount == RCOUNT)
                    {
                        PSsetgray (NSBlack);
                        NSRectFillList (blackRects, blackCount);
                        blackCount = 0;
                    }
                }
                else if (player >= p_player1 && player <= p_player3)
                {
                    playerRects[player][playerCount[player]++] = NSMakeRect (aPoint.x, aPoint.y, GRIDSIZE, GRIDSIZE);
                    if (playerCount[player] == RCOUNT)
                    {
                        [playerColors[player] set];
                        NSRectFillList (playerRects[player], playerCount[player]);
                        playerCount[player] = 0;
                    }
                }
                else
                {
                    terrainRects[terrain][terrainCount[terrain]++] = NSMakeRect (aPoint.x, aPoint.y, GRIDSIZE, GRIDSIZE);
                    if (terrainCount[terrain] == RCOUNT)
                    {
                        [terrainColors[terrain] set];
                        NSRectFillList (terrainRects[terrain], terrainCount[terrain]);
                        terrainCount[terrain] = 0;
                    }
                }
            }
        }

        if (blackCount > 0)
        {
            PSsetgray (NSBlack);
            NSRectFillList (blackRects, blackCount);
            blackCount = 0;
        }

        for (l = 0; l < 4; l++)
        {
            if (playerCount[l] > 0)
            {
                [playerColors[l] set];
                NSRectFillList (playerRects[l], playerCount[l]);
                playerCount[l] = 0;
            }

            if (terrainCount[l] > 0)
            {
                [terrainColors[l] set];
                NSRectFillList (terrainRects[l], terrainCount[l]);
                terrainCount[l] = 0;
            }
        }
    }
}

//----------------------------------------------------------------------

- (Map *) map
{
    return map;
}

//----------------------------------------------------------------------

- (void) setMap:(Map *)aMap
{
    EMMapSize mapSize;
    
    if (aMap == nil)
        return;

    if (map != nil)
    {
        [map detach:self];
        [map release];
    }
  
    map = aMap;
    [map attach:self];
    [map retain];

    mapSize = [map mapSize];
    [self setFrameSize:NSMakeSize (GRIDSIZE * (float)mapSize.width, GRIDSIZE * (float)mapSize.height)];

    [self setNeedsDisplay:YES];
}

//----------------------------------------------------------------------

- (void) setCompressEvents:(BOOL)flag
{
    compressEvents = flag;
}

//----------------------------------------------------------------------

- (EMMapLocation) getLocationForPoint:(NSPoint)point
{
    EMMapSize mapSize;
    EMMapLocation target;

    NSAssert (map != nil, @"Map was nil.");

    mapSize = [map mapSize];

    target.row = mapSize.height - (int)(point.y / GRIDSIZE) - 1;
    target.column = point.x / GRIDSIZE;

    target.row = (target.row >= mapSize.height) ? mapSize.height - 1 : target.row;
    target.row = (target.row <= 0) ? 0 : target.row;

    target.column = (target.column >= mapSize.width) ? mapSize.width - 1 : target.column;
    target.column = (target.column < 0) ? 0 : target.column;

    return target;
}

//----------------------------------------------------------------------

- (NSRect) getRectForLocation:(EMMapLocation)target
{
    EMMapSize mapSize;

    NSAssert (map != nil, @"Map was nil.");

    mapSize = [map mapSize];
    
    return NSMakeRect (GRIDSIZE * (float)target.column, (mapSize.height - 1 - target.row) * GRIDSIZE, GRIDSIZE, GRIDSIZE);
}

//----------------------------------------------------------------------

- (NSRect) getRectAround3x3Location:(EMMapLocation)target
{
    EMMapLocation location1, location2;
    EMMapSize mapSize;

    NSAssert (map != nil, @"Map was nil.");
    mapSize = [map mapSize];

    location1.row = target.row - 1;
    location1.row = (location1.row < 0) ? 0 : location1.row;
    location1.row = (location1.row >= mapSize.height) ? mapSize.height - 1 : location1.row;
    location2.row = target.row + 1;
    location2.row = (location2.row < 0) ? 0 : location2.row;
    location2.row = (location2.row >= mapSize.height) ? mapSize.height - 1 : location2.row;

    location1.column = target.column - 1;
    location1.column = (location1.column < 0) ? 0 : location1.column;
    location1.column = (location1.column >= mapSize.width) ? mapSize.width - 1 : location1.column;
    location2.column = target.column + 1;
    location2.column = (location2.column < 0) ? 0 : location2.column;
    location2.column = (location2.column >= mapSize.width) ? mapSize.width - 1 : location2.column;

    return NSMakeRect ((float)location1.column * GRIDSIZE, (mapSize.height - 1 - location2.row) * GRIDSIZE,
                       (float)(location2.column - location1.column + 1) * GRIDSIZE,
                       (float)(location2.row - location1.row + 1) * GRIDSIZE);
}

//----------------------------------------------------------------------

- (void) scrollLocationToVisible:(EMMapLocation)target
{
    NSRect aRect = [self getRectAround3x3Location:target];

    [self scrollRectToVisible:aRect];
}

//----------------------------------------------------------------------

- (void) centerLocation:(EMMapLocation)target ifNotVisible:(BOOL)notVisibleFlag
{
    NSRect aRect;
    NSRect visibleRect;
  
    aRect = [self getRectAround3x3Location:target];
    visibleRect = [self visibleRect];

    aRect.origin.x = NSMinX (aRect) + (NSWidth (aRect) / 2) - (NSWidth (visibleRect) / 2);
    aRect.origin.y = NSMinY (aRect) + (NSHeight (aRect) / 2) - (NSHeight (visibleRect) / 2);
    aRect.size.width = NSWidth (visibleRect);
    aRect.size.height = NSHeight (visibleRect);

    aRect = NSIntersectionRect ([self bounds] , aRect);

    [self scrollRectToVisible:aRect];
}

//----------------------------------------------------------------------

- (void) displayLocation:(EMMapLocation)target
{
    EMMapSize mapSize;
    NSRect aRect;

    NSAssert (map != nil, @"Map was nil.");
    mapSize = [map mapSize];

    NSAssert (target.row >= 0 && target.column >= 0
              && target.row < mapSize.height && target.column < mapSize.width, @"Cell out of range.");
    
    aRect = [self getRectForLocation:target];
    [self scrollRectToVisible:aRect];

    [self setNeedsDisplayInRect:aRect];
    [self displayIfNeeded];
}

//----------------------------------------------------------------------

- (void) displayAround3x3Location:(EMMapLocation)target
{
    EMMapSize mapSize;
    NSRect aRect;

    NSAssert (map != nil, @"Map was nil.");
    mapSize = [map mapSize];

    NSAssert (target.row >= 0 && target.column >= 0
              && target.row < mapSize.height && target.column < mapSize.width, @"Location out of range.");
    
    aRect = [self getRectAround3x3Location:target];
    [self scrollRectToVisible:aRect];

    [self setNeedsDisplayInRect:aRect];
    [self displayIfNeeded];
}

//----------------------------------------------------------------------

- (void) vendorImagesUpdated:(BOOL)player1:(BOOL)player2:(BOOL)player3:(BOOL)other
{
    EmpireImageVendor *vendor = [EmpireImageVendor instance];
    Player p;

    for (p = p_neutral; p <= p_player3; p++)
        playerColors[p] = [vendor colorForPlayer:p];

    terrainColors[3] = playerColors[0];

    [self setNeedsDisplay:YES];
}

//======================================================================
// Map Observer
//======================================================================

- (void) refreshMap
{
    [self setNeedsDisplay:YES];
}

//----------------------------------------------------------------------

- (void) refreshLocation:(EMMapLocation)target
{
    [self displayLocation:target];
    //[self displayIfNeeded];
}

//----------------------------------------------------------------------

- (void) refresh3x3Location:(EMMapLocation)target
{
    [self displayAround3x3Location:target];
}

//----------------------------------------------------------------------

- (void) mouseDown:(NSEvent *)theEvent 
{
    NSPoint aPoint;
    EMMapLocation mapLocation;
    NSRect visibleRect;
    BOOL scrolled = NO;

    NSPoint mouseLocation;

    EMMapLocation lastLocation;

    BOOL periodicOn = NO;
    unsigned int originalModifierFlags = [theEvent modifierFlags];
    unsigned int modifierFlags;

    lastLocation.row = -1;
    lastLocation.column = -1;

    if (delegate == nil)
    {
        [super mouseDown:theEvent];
        return;
    }

    mouseLocation = [theEvent locationInWindow];

    while ([theEvent type] != NSLeftMouseUp)
    {
        visibleRect = [self visibleRect];
        switch ([theEvent type])
        {
          case NSLeftMouseUp:
              break;

          case NSPeriodic:
          case NSLeftMouseDown:
          case NSLeftMouseDragged:
              if ([theEvent type] == NSPeriodic)
              {
                  mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
                  modifierFlags = originalModifierFlags;
              }
              else
              {
                  mouseLocation = [theEvent locationInWindow];
                  modifierFlags = [theEvent modifierFlags];
              }

              aPoint = [self convertPoint:mouseLocation fromView:nil];

              mapLocation = [self getLocationForPoint:aPoint];

              if (compressEvents == NO
                  || mapLocation.row != lastLocation.row || mapLocation.column != lastLocation.column
                  || [theEvent type] == NSPeriodic
                  || (originalModifierFlags & NSControlKeyMask != 0))
              {
                  lastLocation = mapLocation;

                  [delegate mouseDown:modifierFlags atLocation:mapLocation];
              }

              //if (compressEvents == NO)
              {
                  if (periodicOn == NO)
                  {
                      [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05];
                      periodicOn = YES;
                  }
              }

              if (NSPointInRect(mouseLocation, visibleRect) == NO)
              {
                  scrolled = YES;
                  if (periodicOn == NO)
                  {
                      [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05];
                      periodicOn = YES;
                  }
              }
              else if (compressEvents == YES)
              {
                  [NSEvent stopPeriodicEvents];
                  periodicOn = NO;
              }

              break;

          default:
              break;
        }

        if (scrolled)
            scrolled = NO;

        theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask)];
    }

    [NSEvent stopPeriodicEvents];

    // Mouse up event has been consumed by this point.
    [self mouseUp:theEvent];
}

//----------------------------------------------------------------------

- (void) mouseUp:(NSEvent *)theEvent
{
    NSPoint point;
    EMMapLocation mapLocation;
  
    if ([theEvent type] == NSLeftMouseUp && delegate != nil)
    {
        //NSLog (@"here.");

        point = [self convertPoint:[theEvent locationInWindow] fromView:nil];

        mapLocation = [self getLocationForPoint:point];

        //NSLog (@"delegate: %@", delegate);
        [delegate mouseUp:[theEvent modifierFlags] atLocation:mapLocation];
    }
    else
    {
        [super mouseUp:theEvent];
    }
}

//----------------------------------------------------------------------

- (void) keyDown:(NSEvent *)theEvent 
{
    if (delegate != nil)
    {
        [delegate keyDown:theEvent];
    }
    else
    {
        [super keyDown:theEvent];
    }
}

@end
