from Cheetah import Template
from recaptcha.client import captcha
from paste.request import parse_formvars, resolve_relative_url
from cgi import escape

import blog
import conf
import security
from util import makeUniqueKey, runTemplate
import viewlets

viewlets_enabled = viewlets.viewlets_available[:]

class ViewBase(object):
    ''' Abstract class for page views. '''
    template = None  # String path to location of template
    searchList = {}
    traverseOverrideView = None
        
    def __init__(self, environ, session, extrapath, storage=None):
        if not storage: storage = StorageFactory()
        self.root = storage
        self.environ = environ
        self.session = session
        self.extrapath = extrapath
        self.bloguri = self.environ.get('SCRIPT_NAME','').rstrip('/')
        # ^^ Removed the ending / to make templates make more sense in Cheetah:
        # "$blog_base_uri/$stuff_here" instead of "$blog_base_uri$stuff_here"
        self.fields = parse_formvars(self.environ)
        self.user = security.currentUser(session)
        
        self.searchList = {}
        if (self.user is not None and
            self.user.clearance == security.ADMIN and
            'show_ads' in self.fields):
            if self.fields['show_ads'] == 'yes':
                self.root.showAds(True)
            else:
                self.root.showAds(False)

        self.searchList.update({
                'blog_title': conf.BLOG_TITLE,
                'blog_subtitle': conf.BLOG_SUBTITLE,
                'blog_author': conf.BLOG_AUTHOR,
                'blog_base_uri': self.bloguri,
                'blog_domain': conf.BLOG_DOMAIN,
                'title': '',
                'description': '',
                'warning': '',
                'recaptcha': captcha,
                'recaptcha_key': conf.RECAPTCHA_PUBLIC_KEY,
                'user': self.user,
                'escape_func': lambda x: escape(x,True),
                'show_ads': self.root.showAds(),
                'is_admin': (bool(self.user) and
                             (self.user.clearance==security.ADMIN))
                })
        self.response = blog.Response()
        self.response.setHeader('Content-Type', 'text/html')

    def __call__(self):
        ''' Default __call__ '''
        if self.traverseOverrideView:
            return self.traverseOverrideView()
        content = runTemplate(self.template, self.searchList)
        self.response.write(content)
        self.response.http_status = "200 OK"
        return self.response

class EntryView(ViewBase):
    template = "templates/entry.tmpl"
    viewlets = []

    def __init__(self, environ, session, extrapath, storage):
        super(EntryView,self).__init__(environ, session, extrapath, storage)
        self.entrykey = extrapath[0]
        if len(extrapath)>1:
            self.pathargs = extrapath[1:]
        else:
            self.pathargs = []
        self.entry = self.root.getEntry(self.entrykey)
        self.noentries = False
        if not self.entrykey and not self.entry:
            self.noentries = True

    def prepareViewlets(self):
        global viewlets_enabled
        # instantiate and execute
        self.viewlets = [v(self)() for v in viewlets_enabled] 
        self.searchList.update({
                'viewlets': self.viewlets})
    
    def display(self):
        self.prepareViewlets()
        root = self.root
        sortedEntryKeys = sorted(root['entries'].keys(),
                                 key = lambda x: root['entries'][x].date)
        first = sortedEntryKeys[0]
        last = sortedEntryKeys[-1]
        prev, next = None, None
        mypos = sortedEntryKeys.index(self.entry.key)
        if mypos > 0:
            prev = sortedEntryKeys[mypos-1]
        if mypos < len(sortedEntryKeys) - 1:
            next = sortedEntryKeys[mypos+1]

        self.comments = sorted(self.entry.comments,
                               key= lambda x: x.date)
        
        self.searchList.update(
            {'entry': self.entry,
             'title': self.entry.title,
             'description': escape(self.entry.summary, True), 
             'first': first,
             'last': last,
             'prev': prev,
             'next': next,
             'comments': self.comments,
             'isUserObject': (lambda x: isinstance(x, blog.User)),
             'canCompose': (self.user and self.user.clearance >= security.BLOGGER),
             'canEdit': (self.user and
                         (self.user==self.entry.author or
                          self.user.clearance==security.ADMIN))
             })

        content = runTemplate(self.template, self.searchList)
        self.response.setHeader('Content-Type', "text/html")
        self.response.write(content)
        return self.response

    def showSource(self):
        if len(self.pathargs)>2 and self.pathargs[1]=='comment':
            commentnum = self.pathargs[2]
            if (commentnum.isdigit() and
                int(commentnum)>-1 and
                int(commentnum)<len(self.entry.comments)
                ):
                commentnum = int(commentnum)
                self.response.setHeader('Content-Type', 'text/plain')
                self.response.write("Content-Type: %s\n\n" %
                                    self.entry.comments[commentnum].datatype)
                self.response.write(self.entry.comments[commentnum].data)
                return self.response
            self.response.http_status = '404 Not Found'
            return self.response
            
        self.response.setHeader('Content-Type', "text/plain")
        self.response.write("Content-Type: %s\n\n" % self.entry.datatype)
        self.response.write(self.entry.data)
        return self.response 

    def warnNicknameTaken(self):
        self.template = "templates/nickwarning.tmpl"
        return super(EntryView,self).__call__()

    def newComment(self):
        author = str(self.fields.get('author','')).strip()
        u = self.root.getUser(author, nick=True)
        nicktaken = False
        if u:
            nicktaken = True
        if self.user and (self.user.username == u.username):
            nicktaken = False
        if self.user and (self.user.clearance == security.ADMIN):
            nicktaken = False
        if nicktaken:
            return self.warnNicknameTaken()
        
        self.response.setHeader('Content-Type', 'text/plain')
        recaptcha_challenge = self.fields.get('recaptcha_challenge_field', 'wrong')
        recaptcha_response = self.fields.get('recaptcha_response_field', 'wrong')
        if not self.user:
            response = captcha.submit(recaptcha_challenge,
                                      recaptcha_response,
                                      conf.RECAPTCHA_PRIVATE_KEY,
                                      '')
            if not response.is_valid:
                self.response.write('You failed the captcha!')
                return self.response
        content_type = str(self.fields.get('content-type','wrong'))
        if content_type not in ['RST', 'BBCode']:
            self.response.write('Invalid content type!')
            return self.response
        data = str(self.fields.get('data',''))

        self.root.CommentFactory(data, author, self.entry, datatype=content_type)
        
        # Use a temporary redirect to make sure future new comments get added right
        url = resolve_relative_url(self.bloguri+'/'+self.entry.key, self.environ)
        self.response.redirect(url, True)
        return self.response
        

    def showEdit(self):
        if (not self.user or (
                (self.user != self.entry.author) and
                (self.user.clearance < security.ADMIN))):
            url = resolve_relative_url(self.bloguri+
                                       '/login?nexturl='+
                                       self.bloguri+'/'+
                                       self.entry.key+
                                       '/edit', 
                                       self.environ)
            self.response.redirect(url, True)
            return self.response
        
        if 'FORM_SUBMITTED' in self.fields:
            return self.processEdit()
        self.template = 'templates/entryedit.tmpl'
        self.searchList.update(
            {'entry': self.entry,
             'escaped_title': escape(self.entry.title, True),
             'escaped_body': escape(self.entry.data, True),
             'escaped_summary': escape(self.entry.summary, True),
             })
        return super(EntryView,self).__call__()
        
    def processEdit(self):
        root = self.entry.storage.root

        key = self.fields.get('key', self.entry.key)
        if key != self.entry.key:
            import app
            keys = root['entries'].keys() + app.reserved_locations.keys()
            key = makeUniqueKey(keys, key)
            
        title = self.fields.get('title', self.entry.title)
        summary = self.fields.get('summary', self.entry.summary)
        datatype = self.fields.get('format', self.entry.datatype)
        data = self.fields.get('data', self.entry.data)
        
        author = self.fields.get('author', self.entry.author.username)
        author = root['users'].get(author, self.entry.author)

        del root['entries'][self.entry.key]
        
        self.entry.key = key
        self.entry.title = title
        self.entry.summary = summary
        self.entry.datatype = datatype
        self.entry.data = data
        self.entry.author = author

        root['entries'][key] = self.entry._obj
        
        url = resolve_relative_url(self.bloguri+'/'+self.entry.key, self.environ)
        self.response.redirect(url, True)
        return self.response
    
    def __call__(self):
        if not self.entrykey and self.entry:
            newkey = self.entry.key
            url = resolve_relative_url(self.bloguri+'/'+newkey, self.environ)
            self.response.redirect(url, True)
            return self.response
        elif self.entry:
            self.searchList['entry'] = self.entry
            if self.pathargs and self.pathargs[0]=='source':
                return self.showSource()
            if self.pathargs and self.pathargs[0]=='newComment':
                return self.newComment()
            if self.pathargs and self.pathargs[0]=='edit':
                return self.showEdit()
            return self.display()
        elif self.noentries:
            # Redirect to add the first entry
            url = resolve_relative_url(self.bloguri+'/newEntry', self.environ)
            self.response.redirect(url, True)
            return self.response
        else:
            # Redirect to the main blog page
            url = resolve_relative_url(self.bloguri+'/', self.environ)
            self.response.redirect(url)
            return self.response
        
class BrowseView(ViewBase):
    template = "templates/browse.tmpl"
    
    def __init__(self, environ, session, extrapath, storage):
        super(BrowseView,self).__init__(environ,session,extrapath, storage)
        root = self.root
        entries = root['entries']
        sorted_keys = reversed(sorted(entries.keys(),
                             key = lambda x: entries[x].date))
        
        self.searchList.update(
            {'title': 'Browse',
             'description': 'No description',
             'entries': entries,
             'sorted_keys': sorted_keys
             })
            
class RSSEntryView(BrowseView):
    template = "templates/rss_entry.tmpl"

    def __init__(self, environ, session, extrapath, storage):
        super(RSSEntryView, self).__init__(environ,session,extrapath, storage)
        self.response.setHeader('Content-Type', "application/rss+xml")

class RSSCommentView(BrowseView):
    template = "templates/rss_comment.tmpl"

    def __init__(self, environ, session, extrapath, storage):
        super(RSSCommentView, self).__init__(environ,session,extrapath, storage)
        self.response.setHeader('Content-Type', "application/rss+xml")
        

class NewEntryView(ViewBase):
    template="templates/newentry.tmpl"
    
    def __init__(self, environ, session, extrapath, storage):
        super(NewEntryView, self).__init__(environ, session, extrapath, storage)
        self.searchList.update(
            {'title': 'New Entry',
             'description': 'No description',
             'warning': '',         
             'nexturl': self.fields.get('nexturl', self.bloguri),
             })

    def processNewEntry(self):
        title = self.fields.get('title','No title given')
        description = self.fields.get('description', 'No description given')
        data = self.fields.get('data','')
        datatype = self.fields.get('content-type','RST')
        authorname = self.user.username
        entry = self.root.EntryFactory(title, data, description, authorname, datatype)
        
        url = resolve_relative_url(self.bloguri+'/'+entry.key, self.environ)
        self.response.redirect(url, True)
        return self.response

    def __call__(self):
        if not self.user or self.user.clearance < security.BLOGGER:
            url = resolve_relative_url(self.bloguri+'/login?nexturl='+self.bloguri+'/newEntry',
                                       self.environ)
            self.response.redirect(url, True)
            return self.response
        if 'FORM_SUBMITTED' not in self.fields:
            return super(NewEntryView, self).__call__()
        else:
            return self.processNewEntry()

class PollView(ViewBase):
    template = 'templates/poll_view.tmpl'

    def __init__(self, environ, session, extrapath, storage):
        super(PollView, self).__init__(environ, session, extrapath, storage)
        if len(extrapath)>1:
            self.pollid = extrapath[1]
            self.poll = self.root.getPoll(self.pollid)
        else:
            self.poll = None
        self.can_vote = (self.user) and (self.user.clearance >= security.REGULAR)
        self.has_voted = False
        self.my_option = None
        if self.can_vote and self.poll:
            assert self.user
            if self.user.username in self.poll.votes:
                self.has_voted = True
                self.my_option = self.poll.votes[self.user.username]
        self.see_results = self.has_voted or not self.can_vote
        self.searchList.update({
                'poll': self.poll,
                'can_vote': self.can_vote,
                'has_voted': self.has_voted,
                'show_results': self.has_voted,
                'my_option': self.my_option
                })

    def performVote(self):
        option = self.fields.get('vote',None)
        if (self.can_vote and
            not self.has_voted and
            option in self.poll.options): # if the request is good

            self.poll.vote(self.user, option)
            
        url = resolve_relative_url(self.bloguri + '/polls/' + self.poll.id, self.environ)
        self.response.redirect(url,True)
        return self.response

    def performUnVote(self):
        if (self.can_vote and self.has_voted):
            self.poll.unvote(self.user)

        url = resolve_relative_url(self.bloguri + '/polls/' + self.poll.id, self.environ)
        self.response.redirect(url,True)
        return self.response

    def __call__(self):
        if not self.poll:
            url = resolve_relative_url(self.bloguri + '/polls', self.environ)
            self.response.redirect(url,True)
            return self.response
        if 'vote' in self.fields:
            return self.performVote()
        elif 'unvote' in self.fields:
            return self.performUnVote()

        if 'results' in self.fields or self.see_results:
            self.see_results = True
            tally = self.poll.allTally()
            self.searchList.update({
                'tally': tally,
                'show_results': True
                })
        return super(PollView,self).__call__()

class PollBrowseView(ViewBase):
    template = 'templates/poll_browse.tmpl'

    def __init__(self, environ, session, extrapath, storage):
        super(PollBrowseView, self).__init__(environ, session, extrapath, storage)
        root = self.root
        polls = root['polls'].values()
        if not polls:
            polls = []
        polls.sort(key = lambda x: x.date)
        polls.reverse()
        self.searchList.update({
                'polls': polls})

    def __call__(self):
        if len(self.extrapath)>1:  # If a specific poll is specified
            return PollView(self.environ, self.session, self.extrapath, self.root)()
        return super(PollBrowseView,self).__call__()

class PollManageView(ViewBase):
    template = "templates/poll_manage.tmpl"
    
    def __init__(self, environ, session, extrapath, storage):
        super(PollManageView, self).__init__(environ, session, extrapath, storage)
        if len(extrapath)>1:
            pollname = extrapath[1]
            poll = self.root.getPoll(pollname)
            self.poll = poll
        else:
            self.poll = None

    def manageOnePoll(self):
        self.searchList.update({
                'onepoll': True,
                'poll': self.poll,
                'escaped_question': escape(self.poll.question),
                })

    def newPoll(self):
        if 'FORM_SUBMITTED' not in self.fields:
            self.template = 'templates/poll_new.tmpl'
            return super(PollManageView,self).__call__()
        question = self.fields.get('question', '...?')
        data = self.fields.get('data', '...')
        options = data.split('<!--~-->')
        author = self.user
        root = self.root
        id = makeUniqueKey(root['polls'].keys(), title=question)
        
        self.root.PollFactory(id, author, question, options)

        url = resolve_relative_url(self.bloguri+'/polls', self.environ)
        self.response.redirect(url, True)
        return self.response

    def __call__(self):
        if not self.user or self.user.clearance < security.ADMIN:
            url = resolve_relative_url(self.bloguri+
                                       '/login?nexturl='+
                                       self.bloguri+'/pollManage',
                                       self.environ)
            self.response.redirect(url, True)
            return self.response

        if self.poll:
            return self.manageOnePoll()
        elif 'new' in self.fields:
            return self.newPoll()
        else:
            url = resolve_relative_url(self.bloguri+'/polls', self.environ)
            self.response.redirect(url)
            return self.response

        
### Security Views ###

class LogInView(ViewBase):
    template = "templates/login.tmpl"
    
    def __init__(self, environ, session, extrapath, storage):
        super(LogInView, self).__init__(environ, session, extrapath, storage)
        self.searchList.update(
            {'title': 'Log In',
             'description': 'No description',
             'warning': '',         
             'nexturl': self.fields.get('nexturl', self.bloguri),
             })
        
    def displayLogInForm(self):
        return super(LogInView, self).__call__()

    def performLogIn(self):
        username = self.fields.get('username', '')
        password = self.fields.get('password', '')
        good = security.logIn(username, password, self.session)
        if good:
            self.searchList['warning'] = "you're logged in!"
        else:
            self.searchList['warning'] = "Wrong Username or Password."
        url = resolve_relative_url(self.bloguri+'/'+self.extrapath[0], self.environ)
        if 'nexturl' in self.fields:
            url = self.fields['nexturl']
        self.response.redirect(url, True)
        return self.response

    def performLogOut(self):
        security.logIn('','',self.session, logout=True)
        url = resolve_relative_url(self.bloguri+'/'+self.extrapath[0], self.environ)
        if 'nexturl' in self.fields:
            url = self.fields['nexturl']
        self.response.redirect(url, True)
        return self.response
        
    def __call__(self):
        if 'logout' in self.fields:
            return self.performLogOut()
        elif self.fields.get('FORM_SUBMITTED','no') == 'no':
            return self.displayLogInForm()
        else:
            return self.performLogIn()

class RegisterView(ViewBase):
    template = 'templates/register.tmpl'
    
    def __init__(self, environ, session, extrapath, storage):
        super(RegisterView, self).__init__(environ, session, extrapath, storage)
        self.searchList.update(
            {'title': 'Register',
             'description': 'No description',
             'warning': '',
             'username': '',
             'nickname': '',
             'homepage': ''
             })
        if 'username' in self.fields:
            self.searchList['username'] = self.fields['username']
        if 'nickname' in self.fields:
            self.searchList['nickname'] = self.fields['nickname']
        if 'homepage' in self.fields:
            self.searchList['homepage'] = self.fields['homepage']

    def showWarning(self, warning):
        self.searchList['warning'] = warning
        return super(RegisterView,self).__call__()
        
    def performRegistration(self):
        recaptcha_challenge = self.fields.get('recaptcha_challenge_field', 'wrong')
        recaptcha_response = self.fields.get('recaptcha_response_field', 'wrong')
        response = captcha.submit(recaptcha_challenge,
                                  recaptcha_response,
                                  conf.RECAPTCHA_PRIVATE_KEY,
                                  '')
        if not response.is_valid:
            return self.showWarning('You failed the captcha!')
            
        username = self.fields.get('username','')
        if not username:
            return self.showWarning('You need a username!')
        root = self.root
        esc_username = escape(
            makeUniqueKey(root['users'].keys(),
                          title=username,
                          allow_uppercase=True),
            True)
        if esc_username!=username:
            return self.showWarning('Invalid username, or username taken.')
        nickname = escape(self.fields.get('nickname',''), True).strip()
        if not nickname:
            nickname = esc_username
        homepage = self.fields.get('homepage','')
        esc_homepage = escape(homepage, True).strip()

        if ('javascript:' in esc_homepage.lower()):
            self.showWarning('Please don\'t try to hack me. \
                              No "javascript:" in your homepage.')
        if (homepage[:7].lower()!='http://' and
            homepage[:8].lower()!='https://'):
            homepage = 'http://'+homepage
        
        password = self.fields.get('password','')
        password2= self.fields.get('password2','')

        if not password:
            return self.showWarning('You need a password!')
        if password != password2:
            return self.showWarning('Passwords don\'t match!')

        # Finally, a valid new user.
        self.root.UserFactory(esc_username, nickname, password,
                    security.REGULAR, homepage = esc_homepage)

        url = self.bloguri+'/login?nexturl=/'
        self.response.redirect(url, True)
        return self.response
        
    def __call__(self):
        if self.fields.get('FORM_SUBMITTED','no') == 'no':
            return super(RegisterView,self).__call__()
        else:
            return self.performRegistration()

### Widgets ###

            

### Utilitary Views ###

class CSSView(ViewBase):
    template = "templates/styling.tmpl"

    def __init__(self, environ, session, extrapath, storage):
        super(CSSView, self).__init__(environ,session,extrapath, storage)
        self.response.setHeader('Content-Type', "text/css")
    
class AllSourceView(ViewBase):

    def __call__(self):
        self.response.setHeader('Content-Type', 'application/x-tar')
        import os
        #import pdb; pdb.set_trace()
        mypath = os.path.dirname(os.path.realpath(__file__))
        uppath, blogdir = os.path.split(mypath)
        os.chdir(uppath)
        data = os.popen('tar c --exclude={*.durus*.~*,*.pyc} %s/' % blogdir).read()
        os.chdir(mypath)
        self.response.write(data)
        return self.response

class UploadView(ViewBase):
    template = "templates/upload.tmpl"

    def __init__(self, environ, session, extrapath, storage):
        super(UploadView, self).__init__(environ, session, extrapath, storage)
        self.searchList.update({
                'uploaded_file' : None,
                'isimage': False })

    def processUpload(self):
        if not self.user:
            self.response.http_status = '403 Forbidden'
            #self.response.write('Gotta be a user!')
            return self.response
        title = self.fields['title']
        content = self.fields['file'].value
        content_type = self.fields['file'].type
        root = self.root
        id = makeUniqueKey(root['uploads'].keys(), title)
        self.root.UploadFactory(id, content, content_type, self.user)

        fileurl = resolve_relative_url(self.bloguri+'/upload/'+id,
                                       self.environ)
        self.searchList['uploaded_file'] = fileurl
        if 'image' in content_type:
            self.searchList['isimage'] = True
        
        return super(UploadView, self).__call__()

    def viewUpload(self):
        id = self.extrapath[1]
        upload = self.root.getUpload(id)
        if not upload:
            self.response.http_status = '404 Not Found'
            return self.response
        self.response.setHeader('Content-Type', upload.datatype)
        self.response.write(upload.data)
        return self.response
        
    def __call__(self):
        if "FORM_SUBMITTED" not in self.fields:
            if len(self.extrapath)>1:
                return self.viewUpload()
            return super(UploadView, self).__call__()
        return self.processUpload()

