#!/usr/bin/env python

"""
BNA-Editor: a simple app for editing polygons in BNA files

BNA is a simple text format for storing polygons in lat-long coordinates.

"""
import os, sys
import sets

import numpy as np

#### import local version:
#sys.path.append("..")
#from floatcanvas import NavCanvas, FloatCanvas

## import the installed version
from wx.lib.floatcanvas import NavCanvas, FloatCanvas

import wx
import sys

if len(sys.argv) > 1:
    StartFileName = sys.argv[1]
else:
    StartFileName = None

### These utilities are required to load and save BNA data.
class BNAData:
    """
    Class to store the full set of data in a BNA file

    """
    def __init__(self, Filename = None):
        self.Filename = Filename
        self.PointsData = None
        self.Filename = None
        self.Names = None
        self.Types = None
        if Filename is not None:
            self.Load(Filename)

    def __getitem__(self,index):
        return (self.PointsData[index], self.Names[index])

    def __len__(self):
        return len(self.PointsData)

    def Save(self, filename = None):
        if not filename:
            filename = self.filename
        with open(filename, 'w') as file:
            for i, points in enumerate(self.PointsData):
                file.write('"%s","%s", %i\n'%(self.Names[i],self.Types[i],len(points) ) )
                for p in points:
                    file.write("%.12f,%.12f\n"%(tuple(p)))

    def Load(self, filename):
        #print("Loading:", filename)

        self.Filename = filename
        self.PointsData = []
        self.Names = []
        self.Types = []

        with open(filename,'rU') as file_:
            for line in file_:
                if not line:
                    break
                line = line.strip()
                Name, line = line.split('","')
                Name = Name[1:]
                Type,line = line.split('",')
                num_points = int(line)
                self.Types.append(Type)
                self.Names.append(Name)
                polygon = np.zeros((num_points,2),float)
                for i in range(num_points):
                    polygon[i,:] = map(float, file_.readline().split(','))
                self.PointsData.append(polygon)

        return None

class DrawFrame(wx.Frame):
    """
    A frame used for the BNA Editor

    """

    def __init__(self,parent, id,title,position,size):
        wx.Frame.__init__(self,parent, id,title,position, size)

        ## Set up the MenuBar
        MenuBar = wx.MenuBar()

        FileMenu = wx.Menu()

        OpenMenu = FileMenu.Append(wx.ID_ANY, "&Open", "Open BNA")
        self.Bind(wx.EVT_MENU, self.OpenBNA, OpenMenu)

        SaveMenu = FileMenu.Append(wx.ID_ANY, "&Save", "Save BNA")
        self.Bind(wx.EVT_MENU, self.SaveBNA, SaveMenu)

        CloseMenu = FileMenu.Append(wx.ID_EXIT, "", "Close Application")
        self.Bind(wx.EVT_MENU, self.OnQuit, CloseMenu)

        MenuBar.Append(FileMenu, "&File")

        view_menu = wx.Menu()
        ZoomMenu = view_menu.Append(wx.ID_ANY, "Zoom to &Fit","Zoom to fit the window")
        self.Bind(wx.EVT_MENU, self.ZoomToFit, ZoomMenu)
        MenuBar.Append(view_menu, "&View")

        help_menu = wx.Menu()
        AboutMenu = help_menu.Append(wx.ID_ABOUT, "",
                                "More information About this program")
        self.Bind(wx.EVT_MENU, self.OnAbout, AboutMenu)
        MenuBar.Append(help_menu, "&Help")

        self.SetMenuBar(MenuBar)

        self.CreateStatusBar()
        # Add the Canvas
        self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500),
                                          Debug = 0,
                                          BackgroundColor = "DARK SLATE BLUE"
                                          ).Canvas

        wx.EVT_CLOSE(self, self.OnCloseWindow)

        FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove )
        FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp )
        FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeftDown)

        try:
            self.FileDialog = wx.FileDialog(self, "Pick a BNA file",".","","*", wx.FD_OPEN)
        except wx._core.PyAssertionError:
            self.FileDialog = None

        self.ResetSelections()
        return None

    def ResetSelections(self):
        self.SelectedPoly = None
        self.SelectedPolyOrig = None
        self.SelectedPoints = None
        self.PointSelected = False
        self.SelectedPointNeighbors = None

    def OnLeftDown(self,event):
        if self.SelectedPoly:
            self.DeSelectPoly()
            self.Canvas.Draw()

    def OnAbout(self, event):
        dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n"
                                                  "the use of the FloatCanvas\n",
                                                  "About Me", wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def ZoomToFit(self,event):
        self.Canvas.ZoomToBB()

    def OpenBNA(self, event):
        if self.FileDialog is None:
            self.FileDialog = wx.FileDialog(self, "Pick a BNA file",style= wx.OPEN)
        dlg = self.FileDialog
        dlg.SetMessage("Pick a BNA file")
        if dlg.ShowModal() == wx.ID_OK:
            filename = dlg.GetPath()
            self.LoadBNA(filename)

    def SaveBNA(self, event):
        for i in self.ChangedPolys:
            self.BNAFile.PointsData[i] = self.AllPolys[i].Points
        dlg = wx.FileDialog(self,
                               message="Pick a BNA file",
                               style=wx.FD_SAVE)
        if dlg.ShowModal() == wx.ID_OK:
            filename = dlg.GetPath()
            self.BNAFile.Save(filename)

    def Clear(self,event = None):
        self.Canvas.ClearAll()
        self.Canvas.Draw(True)

    def OnQuit(self,event):
        self.Close(True)

    def OnCloseWindow(self, event):
        self.Destroy()

    def OnMove(self, event):
        """
        Updates the status bar with the world coordinates
        And moves a point if there is one

        """
        self.SetStatusText("%.4f, %.4f"%tuple(event.Coords))

        if self.PointSelected:
            PolyPoints = self.SelectedPoly.Points
            Index = self.SelectedPoints.Index
            dc = wx.ClientDC(self.Canvas)
            PixelCoords = event.GetPosition()
            dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
            dc.SetLogicalFunction(wx.XOR)
            if self.SelectedPointNeighbors is None:
                self.SelectedPointNeighbors = np.zeros((3,2), float)
                #fixme: This feels very inelegant!
                if Index == 0:
                    self.SelectedPointNeighbors[0] = self.SelectedPoly.Points[-1]
                    self.SelectedPointNeighbors[1:3] = self.SelectedPoly.Points[:2]
                elif Index == len(self.SelectedPoly.Points)-1:
                    self.SelectedPointNeighbors[0:2] = self.SelectedPoly.Points[-2:]
                    self.SelectedPointNeighbors[2] = self.SelectedPoly.Points[0]
                else:
                    self.SelectedPointNeighbors = self.SelectedPoly.Points[Index-1:Index+2]
                self.SelectedPointNeighbors = self.Canvas.WorldToPixel(self.SelectedPointNeighbors)
            else:
                    dc.DrawLines(self.SelectedPointNeighbors)
            self.SelectedPointNeighbors[1] = PixelCoords
            dc.DrawLines(self.SelectedPointNeighbors)

    def OnLeftUp(self, event):
        if self.PointSelected:
            self.SelectedPoly.Points[self.SelectedPoints.Index] = event.GetCoords()
            self.SelectedPoly.SetPoints(self.SelectedPoly.Points, copy = False)
            self.SelectedPoints.SetPoints(self.SelectedPoly.Points, copy = False)
            self.PointSelected = False
            self.SelectedPointNeighbors = None
            self.SelectedPoly.HasChanged = True
            self.Canvas.Draw()

    def DeSelectPoly(self):
        Canvas = self.Canvas
        if self.SelectedPoly.HasChanged:
            self.ChangedPolys.add(self.SelectedPolyOrig.BNAIndex)
            self.SelectedPolyOrig.SetPoints(self.SelectedPoly.Points, copy = False)
            self.Canvas.Draw(Force = True)
        Canvas.RemoveObject(self.SelectedPoly)
        Canvas.RemoveObject(self.SelectedPoints)
        self.ResetSelections()

    def SelectPoly(self, Object):
        Canvas = self.Canvas
        if Object is self.SelectedPolyOrig:
            pass
        else:
            if self.SelectedPoly is not None:
                self.DeSelectPoly()
            self.SelectedPolyOrig = Object
            self.SelectedPoly = Canvas.AddPolygon(Object.Points,
                                                  LineWidth = 1,
                                                  LineColor = "Red",
                                                  FillColor = None,
                                                  InForeground = True)
            self.SelectedPoly.HasChanged = False
            # Draw points on the Vertices of the Selected Poly:
            self.SelectedPoints = Canvas.AddPointSet(Object.Points,
                                                     Diameter = 4,
                                                     Color = "Red",
                                                     InForeground = True)
            self.SelectedPoints.HitLineWidth = 8 # make it a bit easier to hit
            self.SelectedPoints.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPointHit)
            Canvas.Draw()

    def SelectPointHit(self, PointSet):
        PointSet.Index = PointSet.FindClosestPoint(PointSet.HitCoords)
        self.PointSelected = True

    def LoadBNA(self, filename):
        self.ResetSelections()
        self.Canvas.ClearAll()
        self.Canvas.SetProjectionFun('FlatEarth')
        try:
            AllPolys = []
            self.BNAFile  = BNAData(filename)
            print("loaded BNAFile:", self.BNAFile.Filename)
            for i, shoreline in enumerate(self.BNAFile.PointsData):
                Poly = self.Canvas.AddPolygon(shoreline,
                                       LineWidth = 1,
                                       LineColor = "Black",
                                       FillColor = "Brown",
                                       FillStyle = 'Solid')
                Poly.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPoly)
                Poly.BNAIndex = i
                AllPolys.append(Poly)
            self.Canvas.ZoomToBB()
            self.ChangedPolys = sets.Set()
            self.AllPolys = AllPolys
        except:
            #raise
            dlg = wx.MessageDialog(None,
                                   'There was something wrong with the selected bna file',
                                   'File Loading Error',
                                   wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()


class BNAEditor(wx.App):
    """
    Once you have a picture drawn, you can zoom in and out and move about
    the picture. There is a tool bar with three tools that can be
    selected.
    """

    def __init__(self, *args, **kwargs):
        wx.App.__init__(self, *args, **kwargs)

    def OnInit(self):
        frame = DrawFrame(None, -1, "BNA Editor",wx.DefaultPosition,(700,700))

        self.SetTopWindow(frame)
        frame.Show()

        if StartFileName:
            frame.LoadBNA(StartFileName)
        else:
            ##frame.LoadBNA("Tests/Small.bna")
            frame.LoadBNA("Tiny.bna")
        return True


app = BNAEditor(False)# put in True if you want output to go to it's own window.
app.MainLoop()























