"""Simple wxpython frame for smart card application.

__author__ = "gemalto http://www.gemalto.com"
__date__ = "November 2006"
__version__ = "1.4.0"

Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com

This file is part of pyscard.

pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

pyscard 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
"""

import os.path
import wx

import smartcard.wx
from smartcard.wx import APDUTracerPanel
from smartcard.wx import CardAndReaderTreePanel
from smartcard.wx import ReaderToolbar

import smartcard
from smartcard.wx.SimpleSCardAppEventObserver import \
    SimpleSCardAppEventObserver

[
wxID_SIMPLESCARDAPP_FRAME,
] = [wx.NewId() for x in range(1)]


class BlankPanel(wx.Panel, SimpleSCardAppEventObserver):
    '''A blank panel in case no panel is provided to SimpleSCardApp.'''

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        sizer = wx.GridSizer(1, 1)
        self.SetSizer(sizer)
        self.SetAutoLayout(True)


class TreeAndUserPanelPanel(wx.Panel):
    '''The panel that contains the Card/Reader TreeCtrl
    and the user provided Panel.'''

    def __init__(self, parent, apppanelclass, appstyle):
        """
        Constructor. Creates the panel with two panels:
          - the left-hand panel is holding the smartcard and/or reader tree
          - the right-hand panel is holding the application dialog

        @param apppanelclass: the class of the panel to instantiate in the
                    L{SimpleSCardAppFrame}
        @param appstyle: a combination of the following styles (bitwise or |)
           - TR_SMARTCARD: display a smartcard tree panel
           - TR_READER: display a reader tree panel
           - TB_SMARTCARD: display a smartcard toolbar
           - TB_SMARTCARD: display a reader toolbar
           - default is TR_DEFAULT = TR_SMARTCARD
        """
        wx.Panel.__init__(self, parent, -1)

        self.parent = parent
        self.selectedcard = None

        boxsizer = wx.BoxSizer(wx.HORIZONTAL)

        # create user dialog
        if None != apppanelclass:
            self.dialogpanel = apppanelclass(self)
        else:
            self.dialogpanel = BlankPanel(self)

        # create card/reader tree control
        if appstyle & smartcard.wx.SimpleSCardApp.TR_SMARTCARD or \
           appstyle & smartcard.wx.SimpleSCardApp.TR_READER:
            self.readertreepanel = \
                CardAndReaderTreePanel.CardAndReaderTreePanel(
                    self, appstyle, self.dialogpanel)
            boxsizer.Add(self.readertreepanel, 1, wx.EXPAND | wx.ALL, 5)

        boxsizer.Add(self.dialogpanel, 2, wx.EXPAND | wx.ALL)

        if appstyle & smartcard.wx.SimpleSCardApp.TR_READER:
            self.Bind(
                wx.EVT_TREE_ITEM_ACTIVATED,
                self.OnActivateReader,
                self.readertreepanel.readertreectrl)
            self.Bind(
                wx.EVT_TREE_SEL_CHANGED,
                self.OnSelectReader,
                self.readertreepanel.readertreectrl)
            self.Bind(
                wx.EVT_TREE_ITEM_RIGHT_CLICK,
                self.OnReaderRightClick,
                self.readertreepanel.readertreectrl)
            self.Bind(
                wx.EVT_TREE_ITEM_COLLAPSED,
                self.OnItemCollapsed,
                self.readertreepanel.readertreectrl)

        if appstyle & smartcard.wx.SimpleSCardApp.TR_SMARTCARD:
            self.Bind(
                wx.EVT_TREE_ITEM_ACTIVATED,
                self.OnActivateCard,
                self.readertreepanel.cardtreectrl)
            self.Bind(
                wx.EVT_TREE_SEL_CHANGED,
                self.OnSelectCard,
                self.readertreepanel.cardtreectrl)
            self.Bind(
                wx.EVT_TREE_ITEM_RIGHT_CLICK,
                self.OnCardRightClick,
                self.readertreepanel.cardtreectrl)

        self.SetSizer(boxsizer)
        self.SetAutoLayout(True)

    def ActivateCard(self, card):
        """Activate a card."""
        if not hasattr(card, 'connection'):
            card.connection = card.createConnection()
            if None != self.parent.apdutracerpanel:
                card.connection.addObserver(self.parent.apdutracerpanel)
            card.connection.connect()
        self.dialogpanel.OnActivateCard(card)

    def DeactivateCard(self, card):
        """Deactivate a card."""
        if hasattr(card, 'connection'):
            card.connection.disconnect()
            if None != self.parent.apdutracerpanel:
                card.connection.deleteObserver(self.parent.apdutracerpanel)
            delattr(card, 'connection')
        self.dialogpanel.OnDeactivateCard(card)

    def OnActivateCard(self, event):
        """Called when the user activates a card in the tree."""
        item = event.GetItem()
        if item:
            itemdata = self.readertreepanel.cardtreectrl.GetItemPyData(item)
            if isinstance(itemdata, smartcard.Card.Card):
                self.ActivateCard(itemdata)
            else:
                self.dialogpanel.OnDeselectCard(itemdata)

    def OnActivateReader(self, event):
        """Called when the user activates a reader in the tree."""
        item = event.GetItem()
        if item:
            itemdata = self.readertreepanel.readertreectrl.GetItemPyData(item)
            if isinstance(itemdata, smartcard.Card.Card):
                self.ActivateCard(itemdata)
            elif isinstance(itemdata, smartcard.reader.Reader.Reader):
                self.dialogpanel.OnActivateReader(itemdata)
        event.Skip()

    def OnItemCollapsed(self, event):
        item = event.GetItem()
        self.readertreepanel.readertreectrl.Expand(item)

    def OnCardRightClick(self, event):
        """Called when user right-clicks a node in the card tree control."""
        item = event.GetItem()
        if item:
            itemdata = self.readertreepanel.cardtreectrl.GetItemPyData(item)
            if isinstance(itemdata, smartcard.Card.Card):
                self.selectedcard = itemdata
                if not hasattr(self, "connectID"):
                    self.connectID = wx.NewId()
                    self.disconnectID = wx.NewId()

                    self.Bind(wx.EVT_MENU, self.OnConnect, id=self.connectID)
                    self.Bind(
                        wx.EVT_MENU, self.OnDisconnect, id=self.disconnectID)

                menu = wx.Menu()
                if not hasattr(self.selectedcard, 'connection'):
                    menu.Append(self.connectID, "Connect")
                else:
                    menu.Append(self.disconnectID, "Disconnect")
                self.PopupMenu(menu)
                menu.Destroy()

    def OnReaderRightClick(self, event):
        """Called when user right-clicks a node in the reader tree control."""
        item = event.GetItem()
        if item:
            itemdata = self.readertreepanel.readertreectrl.GetItemPyData(item)
            if isinstance(itemdata, smartcard.Card.Card):
                self.selectedcard = itemdata
                if not hasattr(self, "connectID"):
                    self.connectID = wx.NewId()
                    self.disconnectID = wx.NewId()

                    self.Bind(wx.EVT_MENU, self.OnConnect, id=self.connectID)
                    self.Bind(
                        wx.EVT_MENU, self.OnDisconnect, id=self.disconnectID)

                menu = wx.Menu()
                if not hasattr(self.selectedcard, 'connection'):
                    menu.Append(self.connectID, "Connect")
                else:
                    menu.Append(self.disconnectID, "Disconnect")
                self.PopupMenu(menu)
                menu.Destroy()

    def OnConnect(self, event):
        if isinstance(self.selectedcard, smartcard.Card.Card):
            self.ActivateCard(self.selectedcard)

    def OnDisconnect(self, event):
        if isinstance(self.selectedcard, smartcard.Card.Card):
            self.DeactivateCard(self.selectedcard)

    def OnSelectCard(self, event):
        """Called when the user selects a card in the tree."""
        item = event.GetItem()
        if item:
            itemdata = self.readertreepanel.cardtreectrl.GetItemPyData(item)
            if isinstance(itemdata, smartcard.Card.Card):
                self.dialogpanel.OnSelectCard(itemdata)
            else:
                self.dialogpanel.OnDeselectCard(itemdata)

    def OnSelectReader(self, event):
        """Called when the user selects a reader in the tree."""
        item = event.GetItem()
        if item:
            itemdata = self.readertreepanel.readertreectrl.GetItemPyData(item)
            if isinstance(itemdata, smartcard.Card.Card):
                self.dialogpanel.OnSelectCard(itemdata)
            elif isinstance(itemdata, smartcard.reader.Reader.Reader):
                self.dialogpanel.OnSelectReader(itemdata)
            else:
                self.dialogpanel.OnDeselectCard(itemdata)


class SimpleSCardAppFrame(wx.Frame):
    """The main frame of the simple smartcard application."""

    def __init__(self,
                  appname,
                  apppanelclass,
                  appstyle,
                  appicon,
                  pos=(-1, -1),
                  size=(-1, -1),
               ):
        """
        Constructor. Creates the frame with two panels:
          - the left-hand panel is holding the smartcard and/or reader tree
          - the right-hand panel is holding the application dialog

        @param appname: name of the application
        @param apppanelclass: the class of the panel to instantiate in the
                       L{SimpleSCardAppFrame}
        @param appstyle: a combination of the following styles (bitwise or |)
          - TR_SMARTCARD: display a smartcard tree panel
          - TR_READER: display a reader tree panel
          - TB_SMARTCARD: display a smartcard toolbar
          - TB_SMARTCARD: display a reader toolbar
          - PANEL_APDUTRACER: display an APDU tracer panel
          - default is TR_DEFAULT = TR_SMARTCARD
        @param pos: the application position as a (x,y) tupple; default is (-1,-1)
        @param size: the application window size as a (x,y) tuple; default is (-1,-1)
        """
        wx.Frame.__init__(self,
                           None,
                          wxID_SIMPLESCARDAPP_FRAME,
                          appname,
                          pos=pos,
                          size=size,
                          style=wx.DEFAULT_FRAME_STYLE)

        if appicon:
            _icon = wx.Icon(appicon, wx.BITMAP_TYPE_ICO)
            self.SetIcon(_icon)
        elif os.path.exists(smartcard.wx.ICO_SMARTCARD):
            _icon = wx.Icon(smartcard.wx.ICO_SMARTCARD, wx.BITMAP_TYPE_ICO)
            self.SetIcon(_icon)

        boxsizer = wx.BoxSizer(wx.VERTICAL)
        self.treeuserpanel = TreeAndUserPanelPanel(
            self, apppanelclass, appstyle)
        boxsizer.Add(self.treeuserpanel, 3, wx.EXPAND | wx.ALL)

        # create a toolbar if required
        if appstyle & smartcard.wx.SimpleSCardApp.TB_SMARTCARD:
            self.toolbar = ReaderToolbar.ReaderToolbar(self)
            self.SetToolBar(self.toolbar)
        else:
            self.toolbar = None

        # create an apdu tracer console if required
        if appstyle & smartcard.wx.SimpleSCardApp.PANEL_APDUTRACER:
            self.apdutracerpanel = APDUTracerPanel.APDUTracerPanel(self)
            boxsizer.Add(self.apdutracerpanel, 1, wx.EXPAND | wx.ALL)
        else:
            self.apdutracerpanel = None

        self.SetSizer(boxsizer)
        self.SetAutoLayout(True)

        self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
        if appstyle & smartcard.wx.SimpleSCardApp.TB_SMARTCARD:
            self.Bind(
                wx.EVT_COMBOBOX,
                self.OnReaderComboBox,
                self.toolbar.readercombobox)

    def OnCloseFrame(self, evt):
        """Called when frame is closed, i.e. on wx.EVT_CLOSE"""
        evt.Skip()

    def OnExit(self, evt):
        """Called when frame application exits."""
        self.Close(True)
        evt.Skip()

    def OnReaderComboBox(self, event):
        """Called when the user activates a reader in the toolbar combo box."""
        cb = event.GetEventObject()
        reader = cb.GetClientData(cb.GetSelection())
        if isinstance(reader, smartcard.reader.Reader.Reader):
            self.treeuserpanel.dialogpanel.OnActivateReader(reader)