#!/usr/bin/python
#
# $Id: mappergui.py,v 1.19 2005/06/22 16:29:45 cyhiggin Exp $

"""DAOC MapperGUI - an graphical version of Oliver Jowett's original mapper.py
DAOC map rendering program. """

__SVNtags__ = """
$URL$
$Author$
$LastChangedDate$
$Revision$
"""

import sys, os,os.path
import re

# Hey, someone MIGHT try to run this on Python 1.5...
if sys.hexversion < 0x020300F0:
    print >> sys.stderr, "Requires Python 2.3 or higher"
    sys.exit(2)

import logging

import wx
from wx._misc import *
import wx.html

import globals
from modules.mainframe import MainFrame
from utils.general import opj
from utils.configmgr import MyPrefs
from modules.Zones import Zones
from utils.captionmgr import CaptionDB

#-------------------------------------------------
class MyApp(wx.App):
    """ Main App class for MapperGUI
    """
    # initialize ids
    wx.RegisterId(wx.ID_HIGHEST)
    
    oneclick = True
    ID_RENDER = wx.NewId()
    ID_ZONE_0 = wx.NewId()+1000
    
    cwd = os.getcwd()
    mgopts = globals.mappergui_options
    localopts = globals.local_options
    
    mappergui_cfgfile = os.path.abspath('settings.ini')

    # fix platform-specific path issues.
    if "__WXMSW__" in wx.PlatformInfo:
        globals.mappergui_options['savepath'] = os.path.normpath('C:/windows/temp')
        globals.mappergui_options['mapper_dir'] = os.path.abspath('./mapper')
        globals.local_options['gamepath'] = os.path.normpath('C:/Mythic/Labyrinth')

    # renderlog
    renderlog = logging.getLogger('render')
    hdlr = logging.FileHandler('render.log','w')
    formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
    hdlr.setFormatter(formatter)
    renderlog.addHandler(hdlr)
    renderlog.setLevel(logging.DEBUG)
    renderlog.propagate = 0
    

    #-------------------------------------------------
    def OnInit(self):
        """Create the frame and set it as top window. Also initialize
        help controller and config files.
        """
        app_name = "DAoC MapperGUI v2.99."
        m = re.search("\$Revision:\s*(\d+)\s*\$", __SVNtags__)
        app_name = app_name + m.group(1)
        
        self.SetAppName(app_name)
        self.crit_global_changed = False
        
        # set up help controller
        self.hlpController = wx.html.HtmlHelpController()
        self.hlpController.AddBook(opj("help/mappergui.hhp"),False)
        if "__WXMSW__" in wx.PlatformInfo and \
               wx.VERSION_STRING.find("2.5",0,3) != -1:
            self.hlpController.GetFrame().Hide()

        # set up help tip thingy
        provider = wx.SimpleHelpProvider()
        wx.HelpProvider_Set(provider)

        # load config files
        self.LoadConfig()

        # load caption database
        self.captions = CaptionDB()
        
        # initialize some map constants
        self.COORDMAX = self.mgopts['coord_max']
        self.GRIDFACTOR = (float(self.mgopts['coord_max']+1)) / 1000.0
        self.imageSize = self.mgopts['imagesize']
        
        self.zoneSelected = None
        self.renderRegion = False
        self.fullSizeRender = True
        
        # load zonelist
        self.zone = Zones(self.localopts['gamepath'])
        
        # start things rolling
        frame = MainFrame(None, self.GetAppName())
        frame.CenterOnParent()
        frame.Show()
        self.SetTopWindow(frame)

        # and remind user to set gamepath if need be..
        if self.zone.locations==None:
            msgstr = """Please enter your gamepath under Options | Set Preferences then exit and restart to populate the 'Zone' menu"""
            self.ConfigErrorBox(msgstr, "Game Not Found")
            
        return True

    #-------------------------------------------------
    def UpdateColorDefaults(self):
        """ Fix up those color options that may be either an RGB
        string or 'default' or 'none'
        """
        # river color
        if self.mgopts['use_river_default']:
            globals.ini_river_options['color'] = 'default'
        elif self.mgopts['use_lava']:
            globals.ini_river_options['color'] = self.mgopts['lava_color']
        else:
            globals.ini_river_options['color'] = self.mgopts['river_color']

        # tree/grove color
        self._defaults_by_draw_type(globals.draw_tree_options['type'],
                                    self.mgopts['tree_color'],
                                    self.mgopts['tree_fill_color'],
                                    self.mgopts['tree_outline_color'],
                                    self.mgopts['use_tree_default'],
                                    globals.draw_tree_options)
                                   
        # structures color
        self._defaults_by_draw_type(globals.draw_shaded_options['type'],
                                    self.mgopts['structure_color'],
                                    self.mgopts['structure_fill_color'],
                                    self.mgopts['structure_outline_color'],
                                    self.mgopts['use_structure_default'],
                                    globals.draw_shaded_options)
        
                                    
        # decor color
        self._defaults_by_draw_type(globals.draw_decor_options['type'],
                                    self.mgopts['decor_color'],
                                    self.mgopts['decor_fill_color'],
                                    self.mgopts['decor_outline_color'],
                                    self.mgopts['use_decor_default'],
                                    globals.draw_decor_options)
        
    #-------------------------------------------------
    def _defaults_by_draw_type(self, type, color, fill_color,
                               outline_color, use_default, drawopts ):
        if type=='shaded' or type=='wirefill':
            drawopts['fill'] = 'none'
            drawopts['outline'] = 'none'
            if use_default:
                drawopts['color'] = 'default'
            else:
                drawopts['color'] = color
        elif type=='solid':
            drawopts['color'] = 'none'
            if use_default:
                drawopts['fill'] = 'default'
                drawopts['outline' ]= 'default'
            else:
                drawopts['fill'] = fill_color
                drawopts['outline'] = outline_color
        else:   # type == 'none'
            pass
        
    #-------------------------------------------------
    def WriteConfigsForRender(self):
        """ Write the files mapper.py expects: mappergui.ini, local.ini,
        captions.ini in mapper_dir...
        """
        
        self.UpdateRenderersString()
        self.UpdateColorDefaults()
        
        # write local.ini
        self.local_prefs.SetComment(["; DO NOT MODIFY THIS FILE!\n",
                                     "; Dynamically generated from MapperGUI.py\n",])
        self.local_prefs.WriteFromDict('maps',self.localopts)
        self.local_prefs.WriteConfigFile()
        
        # write captions.ini anew
        try:
            self.caption_prefs = MyPrefs(self.captions_cfgfile, writeOnly=True)
        except IOError:
            msgstr = msgstr % (self.captions_cfgfile,)
            self.ConfigErrorBox(msgstr, "Mapper path not writable")
            return

        self.caption_prefs.SetComment(["; DO NOT MODIFY THIS FILE!\n",
                                       "; Dynamically generated from MapperGUI.py\n",])
        self.captions.writeMapperCaptionIni(self.zoneSelected, self.caption_prefs)
        self.caption_prefs.WriteConfigFile()
        
        # write mapper.ini
        self.mapper_prefs.SetComment(["; DO NOT MODIFY THIS FILE!\n",
                                      "; Dynamically generated from MapperGUI.py\n",])
        self.mapper_prefs.WriteFromDict('maps', globals.ini_map_options)
        self.mapper_prefs.WriteFromDict('solidgrey', globals.ini_solidgrey_options)
        self.mapper_prefs.WriteFromDict('background', globals.ini_background_options)
        self.mapper_prefs.WriteFromDict('bounds', globals.ini_bounds_options)
        self.mapper_prefs.WriteFromDict('grid', globals.ini_grid_options)
        self.mapper_prefs.WriteFromDict('grid2', globals.ini_grid2_options)
        self.mapper_prefs.WriteFromDict('captions', globals.ini_caption_options)
        self.mapper_prefs.WriteFromDict('contours', globals.ini_contour_options)
        self.mapper_prefs.WriteFromDict('river', globals.ini_river_options)
        self.mapper_prefs.WriteFromDict('bumpmap', globals.ini_bumpmap_options)
        self.mapper_prefs.WriteFromDict('trees', globals.ini_tree_options)
        self.mapper_prefs.WriteFromDict('huglydecor', globals.ini_decor_options)
        self.mapper_prefs.WriteFromDict('grove', globals.ini_grove_options)
        self.mapper_prefs.WriteFromDict('structures', globals.ini_structures_options)
        self.mapper_prefs.WriteFromDict('draw.tree', globals.draw_tree_options)
        self.mapper_prefs.WriteFromDict('draw.shaded', globals.draw_shaded_options)
        self.mapper_prefs.WriteFromDict('draw.decor', globals.draw_decor_options)
        self.mapper_prefs.WriteFromDict('draw.none', globals.draw_none_options)
        self.mapper_prefs.WriteFromDict('fixture-classes',
                                        globals.fixture_classes_option)
        self.mapper_prefs.WriteConfigFile()
        
        
    #-------------------------------------------------
    def LoadConfig(self):
        """ Load all those pesky configuration items from settings.ini
        and setup other files for output.
        """
        # set up config files
        self.mapper_prefs = None
        self.caption_prefs = None
        self.local_prefs = None
        
        self.prefs = MyPrefs(MyApp.mappergui_cfgfile)
        # update mgopts from settings.ini
        self.prefs.ReadToDict('mappergui', MyApp.mgopts)
        self.crit_global_changed = True
        
        # update localopts from settings.ini
        self.prefs.ReadToDict('local', MyApp.localopts)

        # update all those rendering options from settings.ini
        self.prefs.ReadToDict('maps', globals.ini_map_options)
        self.prefs.ReadToDict('solidgrey', globals.ini_solidgrey_options)
        self.prefs.ReadToDict('background', globals.ini_background_options)
        self.prefs.ReadToDict('bounds', globals.ini_bounds_options)
        self.prefs.ReadToDict('grid', globals.ini_grid_options)
        self.prefs.ReadToDict('grid2', globals.ini_grid2_options)
        self.prefs.ReadToDict('captions', globals.ini_caption_options)
        self.prefs.ReadToDict('contours', globals.ini_contour_options)
        self.prefs.ReadToDict('river', globals.ini_river_options)
        self.prefs.ReadToDict('bumpmap', globals.ini_bumpmap_options)
        self.prefs.ReadToDict('trees', globals.ini_tree_options)
        self.prefs.ReadToDict('huglydecor', globals.ini_decor_options)
        self.prefs.ReadToDict('grove', globals.ini_grove_options)
        self.prefs.ReadToDict('structures', globals.ini_structures_options)
        self.prefs.ReadToDict('draw.tree', globals.draw_tree_options)
        self.prefs.ReadToDict('draw.shaded', globals.draw_shaded_options)
        self.prefs.ReadToDict('draw.decor', globals.draw_decor_options)
        self.prefs.ReadToDict('draw.none', globals.draw_none_options)
        self.prefs.ReadToDict('fixture-classes',
                              globals.fixture_classes_option)
        self.UpdateRenderersString()
        
        self.OnCritGlobalsChanged()
            
    #-------------------------------------------------
    def OnCritGlobalsChanged(self):
        if not self.crit_global_changed:
            return
        
        # toss old prefs objects
        if self.mapper_prefs is not None:
            del self.mapper_prefs
        if self.caption_prefs is not None:
            del self.captions_prefs
        if self.local_prefs is not None:
            del self.local_prefs
        
        # get names of other config files from mgopts
        self.mapper_cfgfile = os.path.join(self.mgopts['mapper_dir'],
                                           self.mgopts['mapper_ini'])
        self.captions_cfgfile = os.path.join(self.mgopts['mapper_dir'],
                                             'newcaptions.ini')
        self.local_cfgfile = os.path.join(self.mgopts['mapper_dir'],
                                             'local.ini')
        
        # setup mappergui.ini, local.ini and captions.ini config
        # objects for later output
        msgstr = """%s cannot be created.
        Please correct your mapper.py or mapper config file settings in Options | Change Preferences then exit and restart this program."""
        # mappergui.ini
        try:
            self.mapper_prefs = MyPrefs(self.mapper_cfgfile, writeOnly=True)
        except IOError:
            msgstr = msgstr % (self.mapper_cfgfile,)
            self.ConfigErrorBox(msgstr, "Mapper path not writable")
            self.crit_global_changed = False
            return
        
        # captions.ini is created new each time renderer called
        # look for it in WriteConfigsForRender

        # local.ini
        try:
            self.local_prefs = MyPrefs(self.local_cfgfile, writeOnly=True)
        except IOError:
            msgstr = msgstr % (self.local_cfgfile,)
            self.ConfigErrorBox(msgstr, "Mapper path not writable")

        self.crit_global_changed = False

        
    #-------------------------------------------------
    def ConfigErrorBox(self, msgstr, caption):
        NoGamePathBox = wx.MessageDialog(self.GetTopWindow(),
                                         msgstr,
                                         caption=caption,
                                         style=wx.OK|wx.CENTER|wx.ICON_ERROR)
        NoGamePathBox.ShowModal()
        
    #-------------------------------------------------
    def CleanUpAndSave(self):
        """ Save all those configuration items back to settings.ini
        """
        # save captions database
        self.captions.saveAsXML()
        # save rest of config
        self.UpdateRenderersString()
        self.prefs.WriteFromDict('mappergui', self.mgopts)
        self.prefs.WriteFromDict('local', self.localopts)
        self.prefs.WriteFromDict('maps', globals.ini_map_options)
        self.prefs.WriteFromDict('solidgrey', globals.ini_solidgrey_options)
        self.prefs.WriteFromDict('background', globals.ini_background_options)
        self.prefs.WriteFromDict('bounds', globals.ini_bounds_options)
        self.prefs.WriteFromDict('grid', globals.ini_grid_options)
        self.prefs.WriteFromDict('grid2', globals.ini_grid2_options)
        self.prefs.WriteFromDict('captions', globals.ini_caption_options)
        self.prefs.WriteFromDict('contours', globals.ini_contour_options)
        self.prefs.WriteFromDict('river', globals.ini_river_options)
        self.prefs.WriteFromDict('bumpmap', globals.ini_bumpmap_options)
        self.prefs.WriteFromDict('trees', globals.ini_tree_options)
        self.prefs.WriteFromDict('huglydecor', globals.ini_decor_options)
        self.prefs.WriteFromDict('grove', globals.ini_grove_options)
        self.prefs.WriteFromDict('structures', globals.ini_structures_options)
        self.prefs.WriteFromDict('draw.tree', globals.draw_tree_options)
        self.prefs.WriteFromDict('draw.shaded', globals.draw_shaded_options)
        self.prefs.WriteFromDict('draw.decor', globals.draw_decor_options)
        self.prefs.WriteFromDict('draw.none', globals.draw_none_options)
        self.prefs.WriteFromDict('fixture-classes',
                                 globals.fixture_classes_option)
        
        
        self.prefs.WriteConfigFile()

    #-------------------------------------------------
    def getGridSize(self):
        return float(self.imageSize)/self.GRIDFACTOR

    #-------------------------------------------------
    def getRegion(self):
        """ Get selected region coordinates from frame
        and convert to map coords for use by mapper.py

        Returns: revised image size and map coords tuple
        
        Assumes that a selection exists.
        """
        imageSize = float(self.imageSize)
        r = self.GetTopWindow().getSelection()
        if r is None:
            return imageSize, (0, 0, self.COORDMAX, self.COORDMAX)

        coords = map(float, self._fixrectcoords(r))
        SCALE1 = self.COORDMAX / imageSize
        region = ( int (SCALE1*coords[0]),
                   int (SCALE1*coords[1]),
                   int (SCALE1*coords[2]),
                   int (SCALE1*coords[3]))
        boxWidth = float(region[2]) - float(region[0])
        boxHeight =  float(region[3]) - float(region[1])
        if boxWidth > boxHeight:
            scale2 =(float(self.COORDMAX)/boxWidth)*100.0
        else:
            scale2 =(float(self.COORDMAX)/boxHeight)*100.0
        imageSize = int((imageSize/100.0)*scale2)
        return imageSize, region

    #-------------------------------------------------
    def getCoords(self, pt):
        """ given select point in window coords,
        return (x,y) tuple in game coords
        """
        imageSize = float(self.imageSize)
        SCALE1 = self.COORDMAX / imageSize
        x = int(pt.x * SCALE1)
        y = int(pt.y * SCALE1)
        return x,y
    
    #-------------------------------------------------
    def _fixrectcoords(self, r):
        """ Given a wx.Rect, return a tuple in the form (x1,y1,x2,y2)
        """
        x1, y1, width, height = r.Get()
        region= (x1, y1, x1+width, y1+width)
        return region
    
    #-------------------------------------------------
    option_table = {
        'enable_background' : 'background',
        'enable_river' : 'river',
        'enable_bumpmap' : 'bumpmap',
        'enable_contours' : 'contours',
        'enable_decor' : 'huglydecor',
        'enable_structures' : 'structures',
        'enable_groves' : 'grove',
        'enable_trees' : 'trees',
        'enable_bounds' : 'bounds',
        'enable_grid' : 'grid',
        'enable_minor_grid' : 'grid2',
        'enable_captions' : 'captions',
        }

    option_order = [
        'enable_background',
        'enable_bumpmap',
        'enable_contours',
        'enable_decor',
        'enable_structures',
        'enable_groves',
        'enable_trees',
        'enable_river',
        'enable_structures',
        'enable_bounds',
        'enable_grid',
        'enable_minor_grid',
        'enable_captions',
        ]
    
    def UpdateRenderersString(self):
        """ make sure [maps] option 'renderers' (string) agrees
        with options enabled in mappergui_options
        """
        renderstr = ''
        for k in self.option_order:
            if self.mgopts[k]:
                if not len(renderstr):
                    renderstr = self.option_table[k]
                else:
                    renderstr = renderstr + ", %s" % self.option_table[k]
        globals.ini_map_options['renderers'] = renderstr

    
#-------------------------------------------------
if __name__ == '__main__':

    # re-direct sys.stderr to a log file.
    try:
        errfp = file('mg_err.log','w')
    except:
        print >> sys.stderr, "Could not re-direct sys.stderr to mg_err.log"
        raise
    else:
        sys.stderr = errfp
        
    # setup logger to stderr (i.e., the above log file)
    # also setup logger for render output.
    if sys.version_info[1] >= 4:
        # root (stderr) logger
        logging.basicConfig(level=logging.DEBUG,
                            format='%(asctime)s %(levelname)-8s %(message)s',
                            datefmt='%b %d %H:%M:%S')
        
    elif sys.version_info[1] == 3:
        # root (stderr) logger
        rootlog = logging.getLogger()
        errHdlr = logging.StreamHandler(sys.stderr)
        errFormatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
        errHdlr.setFormatter(errFormatter)
        rootlog.addHandler(errHdlr)
        rootlog.setLevel(logging.DEBUG)

    # init app & run it
    app = MyApp()
    app.MainLoop()

    # clean-up re-direct
    logging.shutdown()
    sys.stderr = sys.__stderr__
    errfp.close()
    
# ChangeLog:
# $Log: mappergui.py,v $
# Revision 1.19  2005/06/22 16:29:45  cyhiggin
# Updated to version 2.99.4
#
# Revision 1.18  2005/06/21 02:54:20  cyhiggin
# updated version to 2.99.3
# Created additional logger for render op.
#
# Revision 1.17  2005/06/17 18:28:08  cyhiggin
# correctly fixed hide bug.
# Updated version to 2.99.2
#
# Revision 1.16  2005/06/17 18:15:39  cyhiggin
# Fix attempt to hide helpcontroller frame before it exists; version-specific issue with wxPython 2.6.1.0. Hide required in wxPython 2.5.
#
# Revision 1.15  2005/06/16 15:53:57  cyhiggin
# Fixed to handle either Python 2.3 or 2.4-specific logging code
# (basicConfig doesn't take args in 2.3, does in 2.4)
#
# Revision 1.14  2005/06/15 23:28:47  cyhiggin
# Added logging capability. Re-direct all sys.stderr to a log file.
#
# Revision 1.13  2005/05/20 12:16:53  cyhiggin
# Got right-click caption edit working.
#
# Revision 1.12  2005/04/11 15:22:11  cyhiggin
# Implemented writing of captions.ini file from XML database.
#
# Revision 1.11  2005/03/31 20:53:45  cyhiggin
# Added load/save of captions.xml
# Fixup under Windows for global path options.
#
# Revision 1.10  2005/03/21 01:49:24  cyhiggin
# Many, many changes to implement Drawing Options dialogs and make all
# that work. However, we can now have wireframe trees if we like.
#
# Revision 1.9  2005/03/13 23:03:19  cyhiggin
# Much stuff added to support tree drawing options, including
# more generalized routines in commonPage.py
#
# Revision 1.8  2005/03/13 00:05:22  cyhiggin
# Finished up river options. Added 'use_river_default'.
#
# Revision 1.7  2005/03/11 18:16:15  cyhiggin
# Added 'enable' options to all option pages, and implemented code to
# assemble renderer string from the 'enable' options.
#
# Revision 1.6  2005/03/10 22:53:57  cyhiggin
# Added new globals, (hopefully) fixed MS Help window display showing
# when it shouldn't.
#
# Revision 1.5  2005/03/09 23:27:18  cyhiggin
# Supports changes to user-configurable, critical globals such as
# path to mapper.py
#
# Revision 1.4  2005/03/09 14:54:37  cyhiggin
# Changes to get basic(main) page of options notebook up and running.
#
# Revision 1.3  2005/03/07 20:16:11  cyhiggin
# Correctly implemented config file reading/writing (i.e., now write
# input configuration for mapper.py, and read settings.ini okay)
# Added support for rendering regions as defined by bounding box in
# map window.
#
# Revision 1.2  2005/03/04 19:19:35  cyhiggin
# Got preferences dialog fully operational.
#
# Revision 1.1.1.1  2005/02/20 18:10:55  cyhiggin
# Re-write of MapperGUI using wxPython
#
