# -*- coding: utf-8 -*-
#
# Quis leget haec?
"""
Stumblr

Stumblr is a Sublime Text 3 interface to tumblr

:copyright: (c) 2013 marcos.a.ojeda <marcos at generic dot cx>
:license: MIT, see LICENSE for details
"""

import json
import logging
import os
import os.path
import re

# this happens to let tumblor correctly load requests and requests_oauth
# without incident, but it's not 100% ideal :(
import sys
sys.path.append(os.path.dirname(__file__))

try:
    import Stumblr.tumblor as tumblor
except ImportError:
    import tumblor
from lib.thread_progress import ThreadProgress
import sublime
import sublime_plugin
import tempfile
import threading
try:
    from urlparse import parse_qs, urlparse
except ImportError:
    from urllib.parse import parse_qs, urlparse
import webbrowser

logging.basicConfig(level=logging.DEBUG,
    format='%(levelname)s\t%(asctime)s [%(filename)s:%(lineno)d] %(message)s')

PREFSFILE = 'Stumblr.sublime-settings'

class TumblrUtility(object):
    def __init__(self):
        self.prefsfile = 'Stumblr.sublime-settings'
        self.prefs = sublime.load_settings(self.prefsfile)
        self.t = self.get_tumblr()

    @property
    def application_tokens(self):
        return (self.prefs.get('consumer_key', '') and
            self.prefs.get('secret_key', ''))

    @property
    def oauth_tokens(self):
        """returns the oauth token and oauth token secret"""
        prefs = self.prefs
        if (not prefs.get('oauth_token') or
            not prefs.get('oauth_token_secret')):
            return None
        return {
            'oauth_token': prefs.get('oauth_token'),
            'oauth_token_secret': prefs.get('oauth_token_secret')
        }

    def get_tumblr(self):
        """returns an authenticated tumblor instance ready to call"""
        creds = self.oauth_tokens
        prefs = self.prefs
        if not creds:
            logging.debug('no creds!')
            return None
        logging.debug('returning Tumblor')
        return tumblor.Tumblor(prefs.get('consumer_key'),
            prefs.get('secret_key'), credentials=creds,
            base_hostname=prefs.get('base_hostname'))

    def get_new_tokens(self):
        prefs = self.prefs
        sublime.status_message('[STUMBLR] Starting OAuth Dance')
        t = tumblor.Tumblor(prefs.get('consumer_key'), prefs.get('secret_key'),
            credentials=None, base_hostname=prefs.get('base_hostname'))
        tokens = t.serialize_credentials()
        prefs.set('oauth_token', tokens['oauth_token'])
        prefs.set('oauth_token_secret', tokens['oauth_token_secret'])
        sublime.save_settings(PREFSFILE)
        return tokens


class CheckCredentialsCommand(sublime_plugin.WindowCommand):
    """Checks to see if user has gathered oauth tokens yet"""
    def run(self):
        tum = TumblrUtility()
        if not tum.application_tokens:
            sublime.status_message('[STUMBLR] Set application keys in %s' % PREFSFILE)
            return

        if tum.oauth_tokens:
            sublime.status_message('[STUMBLR] Already logged in')
            return

        sublime.status_message('[STUMBLR] Run tumblr login')
        return


class TumblrListDraftsCommand(sublime_plugin.WindowCommand):
    """Lists your tumblr drafts in the quick panel"""
    def run(self):
        draft_thread = ListDraftsThreaded(self.window)
        draft_thread.start()
        ThreadProgress(draft_thread, 'Fetching Drafts', '')


class ListDraftsThreaded(threading.Thread, TumblrUtility):
    """Fetches tumblr drafts for display in quick panel"""
    def __init__(self, window):
        self.window = window
        self.drafts = None
        self.strip_tags_re = re.compile(r'<[^>]*>?')
        threading.Thread.__init__(self)
        TumblrUtility.__init__(self)

    def run(self):
        posts = self.get_draft_list()
        logging.info("loaded %s posts" % len(posts))
        def show_panel():
            self.window.show_quick_panel(self.draft_list, self.on_done)

        if len(posts):
            sublime.set_timeout(show_panel, 10)
        sublime.status_message('[STUMBLR]: No drafts. Get writing!')

    def get_draft_list(self):
        drafts = self.t.call_api('/posts/draft', params={'filter':'raw'})
        if drafts['meta']['status'] == 200:
            self.drafts = drafts['response']['posts']
            if len(self.drafts):
                self.draft_list = [self.snippet(p) for p in self.drafts]
                return self.drafts
            logging.info('no drafts found')
            return []
        logging.error('[STUMBLR]: Request for drafts failed.')
        logging.error(drafts)
        return []

    def snippet(self, post):
        """summarizes various kinds of posts for display in quick menu
        """
        if post['type'] == 'text':
            return [post['title'][:60] or post['slug'][:60],
                post['date'],
                self.strip_tags(post['body'])[:80]
            ]
        if post['type'] == 'quote':
            source = self.strip_tags(post.get('source', ''))
            if(post.get('source_url')):
                source = urlparse(post.get('source_url','')).path
            source_title = post.get('source_title', '')
            return [source[-60:],
                "%s: %s" % (post['date'], source_title),
                self.strip_tags(post['text'])[:80]
            ]
        if post['type'] == 'link':
            source = urlparse(post['url']).netloc
            return [post['title'][:60],
                "%s: %s" % (post['date'], source),
                self.strip_tags(post['description'])[:80]
            ]
        if post['type'] == 'photo':
            return [post['slug'][:60],
                post['date'],
                post['caption'][:80]
            ]
        else:
            return [post['slug'], post['date'], ""]

    def strip_tags(self, markup):
        if markup:
            return self.strip_tags_re.sub('', markup)
        return ""

    def on_done(self, index):
        if index == -1:
            logging.info("canceled out of fetch draft quick list")
        else:
            logging.info("picked draft:\n\n %s " % self.drafts[index])
            self.new_buffer_from_post(self.drafts[index])
        return

    def new_buffer_from_post(self, post):
        mapping = {
            'text': ['title', 'body'],
            'photo': ['caption'],
            'quote': ['source', 'text'],
            'link': ['title', 'description'],
            'chat': ['title', 'conversation']
        }
        fields = mapping[post['type']]
        for field in fields:
            self.new_view_with_text(post[field], {
                'id': str(post['id']),
                'field': field
            })

    def new_view_with_text(self, text, settings={}):
        """creates a new view and fills it with text"""
        temp, tempname = tempfile.mkstemp(prefix='stumblr_%s_'%settings['field'])
        logging.debug('have fd: %s, path: %s' % (temp, tempname))
        temp = os.fdopen(temp, 'wb')
        temp.write(text.encode('utf-8'))
        temp.close()

        new_view = self.window.open_file(tempname)
        new_view.settings().set('stumblr_post', True)
        for k in settings:
            logging.debug('setting %s:%s for view' % (k, settings[k]))
            new_view.settings().set('stumblr_%s' % k, settings[k])
            self.set_syntax(new_view)
        pass

    def set_syntax(self, view):
        """looks for a markdown syntax and applies it to a view"""
        possible_syntaxes = sublime.find_resources("Markdown.tmLanguage")
        if possible_syntaxes:
            md_syntax = possible_syntaxes[0]
            logging.debug('setting syntax file to: %s', md_syntax)
            view.set_syntax_file(md_syntax)
        else:
            logging.debug('could not finde a Markdown.tmLanguage')
        return None


class StumblrEvents(sublime_plugin.EventListener):
    """deletes stumblr posts when closed"""
    def on_close(self, view):
        view_prefs = view.settings()
        if view_prefs.get('stumblr_post'):
            original_name = view.file_name()
            if original_name and os.path.exists(original_name):
                logging.info("unlinking: %s" % original_name)
                os.remove(original_name)
            pass
        return

    def on_pre_save(self, view):
        """posts the draft to tumblr when saving a dirty buffer"""
        master_prefs = sublime.load_settings(PREFSFILE)
        if not master_prefs.get('update_on_save'):
            return

        view_prefs = view.settings()
        if view_prefs.get('stumblr_post') and view.is_dirty():
            logging.debug('Stumblr: trying to post draft')
            postup = UpdateDraftThreaded(view)
            postup.start()
            ThreadProgress(postup, 'Updating draft on tumblr',
                '[STUMBLR] Draft Posted successfully!',
                '[STUMBLR] Draft Post failed')
            return
        return


class TumblrUpdateDraftCommand(sublime_plugin.TextCommand):
    """Manually update a draft if update-on-save thing is not for you"""
    def run(self, edit):
        view_prefs = self.view.settings()
        if view_prefs.get('stumblr_post'):
            postup = UpdateDraftThreaded(self.view)
            postup.start()
            ThreadProgress(postup, 'Updating draft on tumblr',
                '[STUMBLR] Draft Updated successfully!',
                '[STUMBLR] Draft Update failed')
            return

    def is_enabled(self):
        if self.view.settings().get('stumblr_post'):
            return True
        return False


class UpdateDraftThreaded(threading.Thread, TumblrUtility):
    """A threaded """
    def __init__(self, view):
        self.view = view
        view_prefs = view.settings()
        self.post_id = int(view_prefs.get('stumblr_id'))
        self.post_field = view_prefs.get('stumblr_field')
        threading.Thread.__init__(self)
        TumblrUtility.__init__(self)

    def run(self):
        sublime.set_timeout(self.post_view, 10)

    def post_view(self):
        body = self.view.substr(sublime.Region(0, self.view.size()))
        logging.info("saving post: %s@%s" % (self.post_id, self.post_field))
        params = {
            'id': self.post_id,
            self.post_field: body
        }
        logging.info(params)
        result = self.t.call_api('/post/edit', verb='post', params=params)
        logging.info(result)
        if result['meta']['status'] == 200:
            sublime.status_message('[STUMBLR] draft updated successfully')
        else:
            logging.error("Update Post failed with result: %s" % result)
            sublime.status_message('[STUMBLR] there was an error updating the draft')
        return


class TumblrLoginCommand(sublime_plugin.WindowCommand):
    """
    Logs a user into tumblr, with nonblocking magic
    """

    def run(self):
        TumblrThreadedLogin(self.window).start()


class TumblrThreadedLogin(threading.Thread, TumblrUtility):

    def __init__(self, window):
        self.window = window
        threading.Thread.__init__(self)
        TumblrUtility.__init__(self)

    def run(self):
        self.get_new_tokens()

        def show_status():
            if self.oauth_tokens:
                sublime.status_message('[STUMBLR] Logged in successfully!')
            else:
                sublime.status_message('[STUMBLR] Login failed')
            return

        sublime.set_timeout(show_status, 10)


class TumblrPublishDraftCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        TumblrThreadedPublishDraft(self.view).start()

    def is_enabled(self):
        if self.view.settings().get('stumblr_post'):
            return True
        return False


class TumblrThreadedPublishDraft(threading.Thread, TumblrUtility):
    def __init__(self, view):
        self.view = view
        self.view_settings = view.settings()
        self.post_id = int(self.view_settings.get('stumblr_id'))
        threading.Thread.__init__(self)
        TumblrUtility.__init__(self)

    def run(self):
        def publish_post():
            info = self.t.call_api(method='/post/edit', params={
                'id': self.post_id,
                'state': 'published',
            }, verb='post')
            logging.debug(info)
            # TODO(marcos): what if the post fails? maybe set the status message?
            if info['meta']['status'] == 200:
                published_id = info['response']['id']
                more_info = self.t.call_api(method='/posts', params={
                    'api_key': self.prefs.get('consumer_key'),
                    'id': published_id
                })
                logging.debug(more_info)
                # TODO(marcos): what if this also fails?
                if more_info['meta']['status'] == 200:
                    post = more_info['response']['posts'][0]
                    # TODO(marcos): maybe check a pref for this
                    webbrowser.open(post['post_url'])

        sublime.set_timeout(publish_post, 10)


class TumblrPostDraftCommand(sublime_plugin.TextCommand):
    """
    Posts the current view to tumblr as a draft
    """
    def run(self, edit):
        TumblrThreadedPostDraft(self.view).start()


class TumblrThreadedPostDraft(threading.Thread, TumblrUtility):
    """
    Posts a view to tumblr
    """
    def __init__(self, view):
        self.view = view
        self.response = None
        threading.Thread.__init__(self)
        TumblrUtility.__init__(self)

    def run(self):

        def show_status():
            self.post_draft()
            logging.info(self.response)
            if self.response['meta']['status'] == 201:
                sublime.status_message('[STUMBLR] Draft Posted successfully!')
                self.view.set_name(str(self.response['response']['id']))
                prefs = self.view.settings()
                prefs.set('stumblr_post', True)
                prefs.set('stumblr_id', str(self.response['response']['id']))
                prefs.set('stumblr_field', 'body')
            else:
                sublime.status_message('[STUMBLR] Draft Post failed')
                # clearing this prevents the underlying file being killed when
                # the view gets closed
                prefs.erase('stumblr_post')
            return

        sublime.set_timeout(show_status, 10)

    def post_draft(self):
        if self.t:
            body = self.view.substr(sublime.Region(0, self.view.size()))
            payload = {
                'state':'draft', 'type':'text', 'format':'markdown',
                'body':body
            }
            self.response = self.t.call_api('/post', params=payload, verb='post')


class TumblrDeleteDraftCommand(sublime_plugin.TextCommand):
    """Deletes the current buffer's associated post on tumblr"""
    def run(self, edit):
        TumblrThreadedDeleteDraftCommand(self.view).start()

    def is_enabled(self):
        if self.view.settings().get('stumblr_post'):
            return True
        return False



class TumblrThreadedDeleteDraftCommand(threading.Thread, TumblrUtility):
    """
    Deletes a view on tumblr, but provides a dialog before actually doing it
    """
    def __init__(self, view):
        self.view = view
        self.response = None
        self.view_settings = view.settings()
        self.post_id = int(self.view_settings.get('stumblr_id'))
        threading.Thread.__init__(self)
        TumblrUtility.__init__(self)

    def run(self):
        def delete_draft():
            post = self.t.call_api(method='/posts', params={
                'api_key': self.prefs.get('consumer_key'),
                'id': self.post_id
            })
            logging.debug(post)
            # does the post exist?
            if post['meta']['status'] == 200:
                deleted_post = self.t.call_api(method='/post/delete', params={
                    'id': self.post_id
                }, verb='post')
                logging.debug(deleted_post)
                if deleted_post['meta']['status'] == 200:
                    sublime.status_message('[STUMBLR] Post #%s Deleted' % self.post_id)
                    return
            sublime.status_message('[STUMBLR] Failed on Deleting Post #%s' % self.post_id)
            return

        sublime.set_timeout(delete_draft, 10)