#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2010 British Broadcasting Corporation and Kamaelia Contributors(1)
#
# (1) Kamaelia Contributors are listed in the AUTHORS file and at
#     http://www.kamaelia.org/AUTHORS - please extend this file,
#     not this notice.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -------------------------------------------------------------------------
 
"""\
=========================================
GUI for Ryans Lothians Bittorrent Package
=========================================
 
A 3D GUI for bittorrent downloading using Ryan Lothians Kamaelia
Bittorrent package. Features torrent fetching from URLs,
starting/stopping of torrents and showing torrent information.
 
To work properly, an installation of Bittorrent 4.20.8 or higher is
required.
"""
 
import Axon
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
 
from Kamaelia.UI.OpenGL.Vector import Vector
from Kamaelia.UI.OpenGL.Button import Button
from Kamaelia.UI.OpenGL.ArrowButton import ArrowButton
from Kamaelia.UI.OpenGL.ProgressBar import ProgressBar
from Kamaelia.UI.OpenGL.Container import Container
from Kamaelia.UI.OpenGL.SkyGrassBackground import SkyGrassBackground
from Kamaelia.UI.OpenGL.Label import Label
from Kamaelia.UI.OpenGL.PygameWrapper import PygameWrapper
from Kamaelia.UI.OpenGL.Movement import WheelMover, PathMover, LinearPath
 
from Kamaelia.UI.Pygame.Ticker import Ticker
from Kamaelia.Protocol.Torrent.TorrentIPC import *
 
from BitTorrent.bencode import bdecode
 
import random
import os
import time
import sys
 
class TorrentOpenGLGUI(Axon.AdaptiveCommsComponent.AdaptiveCommsComponent):
    Inboxes = {
        "inbox":"Receive messages from TorrentPatron",
        "control":"receive shutdown messages",
 
        "nav":"To receive commands from navigation buttons",
 
        "start": "Start commands from buttons",
        "stop": "Stop commands from buttons",
 
        "show_info": "Info commands from buttons",
        "hide_info": "Hide torrent info",
 
        "torrent_url": "For reception of torrent file URLs",
        "torrent_file": "For reception of torrent files",
    }
 
    Outboxes = {
        "outbox":"Send Messages to TorrentPatron",
        "signal" : "",
 
        "fetcher" : "Send URLs to SimpleHttpClient",
 
        "mover_signal": "Send command to WheelMover",
        "mover_switch": "Control WheelMover",
 
        "infomover_commands": "Send commands to info container mover",
        "torrent_info": "For sending info about a torrent"
    }
 
 
    def __init__(self, **argd):
        super(TorrentOpenGLGUI, self).__init__()
 
        self.torrents = []
        self.torrent_from_id = {}
        self.torrent_to_id = {}
        self.torrent_progress_comms = {}
 
        self.requested_files = []
        self.started_torrents = []
 
 
    def main(self):
        self.initUIComponents()        
        self.loadLocalTorrentFiles()
 
        while 1:
            yield 1
 
            while self.dataReady("inbox"):
                msg = self.recv("inbox")
#                print msg
 
                if isinstance(msg, TIPCNewTorrentCreated):
                    torrent = self.started_torrents.pop(0)
                    self.torrent_from_id[msg.torrentid] = torrent
                    self.torrent_to_id[torrent] = msg.torrentid
 
                elif isinstance(msg, TIPCTorrentStartFail) or isinstance(msg, TIPCTorrentAlreadyDownloading):
                    self.started_torrents.pop(0)
 
                elif isinstance(msg, TIPCTorrentStatusUpdate):
                    self.send(msg.statsdictionary.get("fractionDone","0"), self.torrent_progress_comms[ self.torrent_from_id[msg.torrentid]] )
 
            while self.dataReady("torrent_url"):
                url = self.recv("torrent_url")
                filename = os.path.basename(url)
                self.requested_files.append(filename)
                self.send(url, "fetcher")
 
            while self.dataReady("torrent_file"):
                torrentfile = self.recv("torrent_file")
                # save received file
                filename = self.requested_files.pop()
                fobj = open(os.getcwd()+"/"+filename, 'w')
                fobj.write(torrentfile)
                fobj.close()
                # add torrent
                self.addTorrent(filename, torrentfile)                
 
            while self.dataReady("nav"):
                msg = self.recv("nav")
                if msg == "UP":
                    self.send("NEXT", "mover_switch")
                if msg == "DOWN":
                    self.send("PREVIOUS", "mover_switch")
 
            while self.dataReady("start"):
                torrent = self.recv("start")
                self.send(torrent)
                self.started_torrents.append(torrent)
 
            while self.dataReady("stop"):
                torrent = self.recv("stop")
                try:
                    self.send( TIPCCloseTorrent(torrentid=self.torrent_to_id[torrent]) )
                    self.send(0.0, self.torrent_progress_comms[torrent])
                except KeyError:
                    pass
 
            while self.dataReady("show_info"):
                torrent = self.recv("show_info")
                self.showInfo(torrent)
 
            while self.dataReady("hide_info"):
                msg = self.recv("hide_info")
                self.hideInfo()
 
            while self.dataReady("control"):
                cmsg = self.recv("control")
                if isinstance(cmsg, Axon.Ipc.shutdownMicroprocess):
                    # dirty way to terminate program
                    sys.exit(0)
 
 
    def initUIComponents(self):
        # listen to shutdown events
        ogl_display = OpenGLDisplay().getDisplayService()[0]
        self.link( (ogl_display, "signal"), (self, "control") )
 
        # init mover
        self.mover = WheelMover(radius=15, center=(0,0,-25), steps=500, slots=40).activate()
        self.link( (self, "mover_signal"), (self.mover, "notify") )
        self.link( (self, "mover_switch"), (self.mover, "switch") )
 
        self.background = SkyGrassBackground(size=(5000,5000,0), position=(0,0,-90)).activate()
 
        # create & link nav buttons
        self.up_button = ArrowButton(size=(1,1,0.3), position=(7,5,-15), msg="UP").activate()
        self.down_button = ArrowButton(size=(1,1,0.3), position=(7,-5,-15), rotation=(0,0,180), msg="DOWN").activate()
        self.link( (self.up_button, "outbox"), (self, "nav") )
        self.link( (self.down_button, "outbox"), (self, "nav") )
 
        # init info display
        self.infoticker = Ticker(text_height=21, render_right=250, render_bottom=500, background_colour=(250,250,200), text_colour=(0,0,0), outline_colour=(255,255,255)).activate()
        self.tickerwrapper = PygameWrapper(wrap=self.infoticker, size=(2.4,4.0,0.3)).activate()
        self.hideinfo_button = Button(caption="Hide", fontsize=30).activate()
 
        infocontents = {
            self.tickerwrapper : { "position":(0,0,0) },
            self.hideinfo_button : { "position":(0,-2.4,0) },
        }
 
        self.infocontainer = Container(contents=infocontents, position=(-10, 10, -16)).activate()
        infopath = LinearPath([(-10, 10, -16), (-3,0,-8)], 100)
 
        self.infomover = PathMover(infopath, False).activate()
 
        self.link( (self.infomover, "outbox"), (self.infocontainer, "position") )
        self.link( (self, "infomover_commands"), (self.infomover, "inbox") )
        self.link( (self, "torrent_info"), (self.infoticker, "inbox") )
        self.link( (self.hideinfo_button, "outbox"), (self, "hide_info") )
 
        self.send("Stop", "infomover_commands")
 
 
    def loadLocalTorrentFiles(self):
        print "Loading local torrent files..."
        cwd = os.getcwd()
        files = os.listdir(cwd)
        for f in files:
            if f.endswith(".torrent"):
                print "- ",f
                fobj = open(f)
                torrent = fobj.read() 
                fobj.close()
                self.addTorrent(f, torrent)
 
 
    def addTorrent(self, title, torrent):
        self.torrents.append(torrent)
 
        start = Button(size=(0.8,0.5,0.3), caption="Start", fontsize=35, msg=torrent).activate()
        info  = Button(size=(0.8,0.5,0.3), caption="Info", fontsize=35, msg=torrent).activate()
        stop = Button(size=(0.8,0.5,0.3), caption="Stop", fontsize=35, msg=torrent).activate()
        colour = [ int(random.randint(100,255)) for i in range(3) ]
        progress = ProgressBar(size=(3.2,0.5,0.3), barcolour=colour).activate()
        label = Label( size=(6.0, 0.3, 0.3), caption=title, fontsize=26, bgcolour=colour).activate()
 
        self.link( (start, "outbox"), (self, "start") )
        self.link( (stop, "outbox"), (self, "stop") )
        self.link( (info, "outbox"), (self, "show_info") )
 
        container_elements = {
            progress : { "position":(-0.4,-0.3,0) },
            start : { "position":(1.8,-0.3,0) },
            stop : { "position":(2.7,-0.3,0) },
            info : { "position":(3.6,-0.3,0) },
            label : { "position":(1, 0.3, 0) },
        }
 
        container = Container(contents=container_elements, position=(0,0,-10)).activate()
 
        req = { "APPEND_CONTROL":True,
                "objectid": id(container),
                "control": (container,"position")
        }
        self.send(req, "mover_signal")
 
        # create & link box for progress update
        progbox = self.addOutbox("progress")
        self.torrent_progress_comms[torrent] = progbox
        self.link( (self, progbox), (progress, "progress") )
 
 
    def showInfo(self, torrent):
        decoded = bdecode(torrent)
 
        info = decoded.get("info", None)
        if info is not None:
            name = info.get("name", "??")
            length = info.get("length", "??")
 
        date = decoded.get("creation date", "??")
        creator = decoded.get("created by", "??")
        announce = decoded.get("announce", "??")
        comment = decoded.get("comment", "??")
 
        infotuple = (name, comment, length, date, creator)
 
        infostring = "Name:%s\nComment:%s\nLength:%s\nCreation_Date:%s\nCreated_by:%s\n ----- \n" % infotuple
 
        # move info container in front of everthig
        self.send("Forward", "infomover_commands")
        self.send("Play", "infomover_commands")
        self.send(infostring, "torrent_info")
 
 
    def hideInfo(self):
        self.send("Backward", "infomover_commands")
        self.send("Play", "infomover_commands")
 
 
if __name__ == "__main__":
    from Kamaelia.Chassis.Graphline import Graphline
    from Kamaelia.Util.Console import ConsoleReader
    from Kamaelia.UI.PygameDisplay import PygameDisplay
    from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
    from Kamaelia.Protocol.HTTP.HTTPClient import SimpleHTTPClient
    from Kamaelia.Protocol.Torrent.TorrentPatron import TorrentPatron
 
    ogl_display = OpenGLDisplay(limit_fps=100).activate()  
    OpenGLDisplay.setDisplayService(ogl_display)
    # override pygame display service
    PygameDisplay.setDisplayService(ogl_display)
 
    Graphline(
        reader = ConsoleReader(prompt="Enter torrent location:", eol=""),
        httpclient = SimpleHTTPClient(),
        gui = TorrentOpenGLGUI(),
        backend = TorrentPatron(),
        linkages = {
            ("gui", "outbox") : ("backend", "inbox"),
            ("reader", "outbox") : ("gui", "torrent_url"),
            ("gui", "fetcher") : ("httpclient", "inbox"),
            ("httpclient", "outbox") : ("gui", "torrent_file"),
            ("backend", "outbox"): ("gui", "inbox")
        }
    ).run()
 
# Licensed to the BBC under a Contributor Agreement: THF