# -*- coding: utf-8 -*-

# Copyright 2011 Fanficdownloader team, 2018 FanFicFare team
#
# 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.
#

from __future__ import absolute_import
import contextlib
from datetime import datetime
import logging
import re
from .. import exceptions as exceptions
from ..dateutils import parse_relative_date_string
from ..htmlcleanup import stripHTML

from .base_adapter import BaseSiteAdapter

logger = logging.getLogger(__name__)


def getClass():
    return RoyalRoadAdapter

# Class name has to be unique.  Our convention is camel case the
# sitename with Adapter at the end.  www is skipped.
class RoyalRoadAdapter(BaseSiteAdapter):

    def __init__(self, config, url):
        BaseSiteAdapter.__init__(self, config, url)

        self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
        self.password = ""
        self.is_adult=False

        # get storyId from url--url validation guarantees query is only fiction/1234
        self.story.setMetadata('storyId',re.match(r'/fiction/(\d+)(/.*)?$',self.parsedUrl.path).groups()[0])


        # normalized story URL.
        self._setURL('https://' + self.getSiteDomain() + '/fiction/'+self.story.getMetadata('storyId'))

        # Each adapter needs to have a unique site abbreviation.
        self.story.setMetadata('siteabbrev','rylrdl')

        # The date format will vary from site to site.
        # http://docs.python.org/library/datetime.html#strftime-strptime-behavior
        self.dateformat = '%d/%m/%Y %H:%M:%S %p'

        # RR has globally unique ID for each chapter which can be used for fast lookup
        self.chapterURLIndex = {}

    def make_date(self, parenttag):
        # locale dates differ but the timestamp is easily converted
        timetag = parenttag.find('time')
        if timetag.has_attr('unixtime'):
            ts = timetag['unixtime']
            return datetime.fromtimestamp(float(ts))
        else:
            ## site has gone to crappy resolution "XX
            ## (min/day/month/year/etc) ago" dating
            return parse_relative_date_string(timetag.text)

    @staticmethod # must be @staticmethod, don't remove it.
    def getSiteDomain():
        # The site domain.  Does have www here, if it uses it.
        # changed from royalroadl.com
        return 'www.royalroad.com'

    @classmethod
    def getAcceptDomains(cls):
        return ['royalroad.com','royalroadl.com','www.royalroadl.com']

    @classmethod
    def getConfigSections(cls):
        "Only needs to be overriden if has additional ini sections."
        return ['royalroadl.com',cls.getSiteDomain()]

    @classmethod
    def getSiteExampleURLs(cls):
        return "https://www.royalroad.com/fiction/3056"

    @classmethod
    def get_section_url(cls,url):
        ## minimal URL used for section names in INI and reject list
        ## for comparison
        # logger.debug("pre--url:%s"%url)
        # https://www.royalroad.com/fiction/36051/memories-of-the-fall
        # https://www.royalroad.com/fiction/36051
        url = re.sub(r'^https?://(.*/fiction/\d+).*$',r'https://\1',url)
        # logger.debug("post-url:%s"%url)
        return url

    def getSiteURLPattern(self):
        return "https?"+re.escape("://")+r"(www\.|)royalroadl?\.com/fiction/\d+(/.*)?$"


    # rr won't send you future updates if you aren't 'caught up'
    # on the story.  Login isn't required but logging in will
    # mark stories you've downloaded as 'read' on rr.
    def performLogin(self):
        params = {}

        if self.password:
            params['Email'] = self.username
            params['password'] = self.password
        else:
            params['Email'] = self.getConfig("username")
            params['password'] = self.getConfig("password")

        if not params['password']:
            return

        loginUrl = 'https://' + self.getSiteDomain() + '/account/login'
        logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
                                                              params['Email']))

        ## need to pull empty login page first to get request token
        soup = self.make_soup(self.get_request(loginUrl))
        ## FYI, this will fail if cookiejar is shared, but
        ## use_basic_cache is false.
        params['__RequestVerificationToken']=soup.find('input', {'name':'__RequestVerificationToken'})['value']

        d = self.post_request(loginUrl, params)

        if "Sign in" in d : #Member Account
            logger.info("Failed to login to URL %s as %s" % (loginUrl,
                                                             params['Email']))
            raise exceptions.FailedToLogin(self.url,params['urealname'])
            return False
        else:
            return True

    ## RR chapter URL only requires the chapter ID number field to be correct, story ID and title values are ignored
    ## URL format after the domain /fiction/ is long form, storyID/storyTitle/chapter/chapterID/chapterTitle
    ##  short form has /fiction/chapter/chapterID    both forms have optional final /
    ## The regex matches both, and is valid if either there are both storyID/storyTitle and chapterTitle fields
    ##    or if there are neither of those two fields
    ## In addition, the chapterID must be found in chapterURLIndex table that is built when the ToC metadata is read.
    def normalize_chapterurl(self,url):
        chap_pattern = r"https?://(?:www\.)?royalroadl?\.com/fiction(/\d+/[^/]+)?/chapter/(\d+)(/[^/]+)?/?$"
        match = re.match(chap_pattern, url)
        if match and ((match.group(1) and match.group(3)) or (not match.group(1) and not match.group(3))):
            chapter_url_index = self.chapterURLIndex.get(match.group(2))
            if chapter_url_index is not None:
                return self.chapterUrls[chapter_url_index]['url']
        return url


		
    def make_soup(self, data):
        soup = super(RoyalRoadAdapter, self).make_soup(data)
    # Parse and store styles in a set
        self.styles_to_ignore = set()
        style_elements = soup.find_all('style')
        for style_element in style_elements:
            class_matches = re.findall(r'\.(\S+)\s*\{[^\}]*display\s*:\s*none\s*;[^\}]*\}', style_element.string, flags=re.IGNORECASE)
            if class_matches:
                self.styles_to_ignore.update(class_matches)
                del class_matches
        self.handle_spoilers(soup)
        return soup

    def handle_spoilers(self,topsoup):
        '''
        Modifies tag given as required to do spoiler changes.
        '''
        if self.getConfig('remove_spoilers'):
            for div in topsoup.find_all('div',class_='spoiler'):
                div.extract()
        elif self.getConfig('legend_spoilers'):
            for div in topsoup.find_all('div',class_='spoiler'):
                div.name='fieldset'
                legend = topsoup.new_tag('legend')
                smalltext = div.find('div',class_='smalltext')
                if smalltext:
                    legend.string = stripHTML(smalltext)
                    smalltext.extract()
                div.insert(0,legend)
                for inner in div.find_all('div',class_='spoiler-inner'):
                    del inner['style']
                #div.button.extract()

    ## Getting the chapter list and the meta data, plus 'is adult' checking.
    def extractChapterUrlsAndMetadata(self):

        url = self.url
        logger.debug("URL: "+url)

        # Log in so site will mark the chapers as read
        self.performLogin()

        data = self.get_request(url)

        soup = self.make_soup(data)
        # print data

        # site has taken to presenting a *page* that says 404 while
        # still returning an HTTP 200 code.
        div404 = soup.find('div',{'class':'number'})
        if div404 and stripHTML(div404) == '404':
            raise exceptions.StoryDoesNotExist(self.url)

        ## Title
        title = soup.select_one('.fic-header h1').text
        self.story.setMetadata('title',title)

        # Find authorid and URL from... author url.
        mt_card_social = soup.find(None,{'class':'mt-card-social'})
        author_link = mt_card_social('a')[-1]
        if author_link:
            authorId = author_link['href'].rsplit('/', 1)[1]
            self.story.setMetadata('authorId', authorId)
            self.story.setMetadata('authorUrl','https://'+self.host+'/user/profile/'+authorId)

        self.story.setMetadata('author',soup.find(attrs=dict(property="books:author"))['content'])


        chapters = soup.find('table',{'id':'chapters'}).find('tbody')
        tds = [tr.find_all('td') for tr in chapters.find_all('tr')]

        if not tds:
            raise exceptions.FailedToDownload(
                "Story has no chapters: %s" % url)

        # Links in the RR ToC page are in the normalized long form, so match is simpler than in normalize_chapterurl()
        chap_pattern_long = r"https?://(?:www\.)?royalroadl?\.com/fiction/\d+/[^/]+/chapter/(\d+)/[^/]+/?$"
        for chapter,date in tds:
            chapterUrl = 'https://' + self.getSiteDomain() + chapter.a['href']
            chapterDate = self.make_date(date)
            format = self.getConfig("datechapter_format", self.getConfig("datePublished_format", self.dateformat))
            if self.add_chapter(chapter.text, chapterUrl, {'date': chapterDate.strftime(format)}):
                match = re.match(chap_pattern_long, chapterUrl)
                if match:
                    chapter_id = match.group(1)
                    self.chapterURLIndex[chapter_id] = len(self.chapterUrls) - 1

        description = soup.select_one('div.description div.hidden-content')
        self.setDescription(url,description)

        self.story.setMetadata('dateUpdated', self.make_date(tds[-1][1]))
        self.story.setMetadata('datePublished', self.make_date(tds[0][1]))

        for a in soup.find_all('a',{'class':'fiction-tag'}): # not all stories have genre
            genre = stripHTML(a)
            if not "Unspecified" in genre:
                self.story.addToList('genre',genre)

        for label in [stripHTML(a) for a in soup.find_all('span', {'class':'label'})]:
            if 'COMPLETED' == label:
                self.story.setMetadata('status', 'Completed')
            elif 'ONGOING' == label:
                self.story.setMetadata('status', 'In-Progress')
            elif 'HIATUS' == label:
                self.story.setMetadata('status', 'Hiatus')
            elif 'STUB' == label:
                self.story.setMetadata('status', 'Stub')
            elif 'DROPPED' == label:
                self.story.setMetadata('status', 'Dropped')
            elif 'Fan Fiction' == label:
                self.story.addToList('category', 'FanFiction')
            elif 'Original' == label:
                self.story.addToList('category', 'Original')

        # 'rating' in FFF speak means G, PG, Teen, Restricted, etc.
        # 'stars' is used instead for RR's 1-5 stars rating.
        stars=soup.find(attrs=dict(property="books:rating:value"))['content']
        self.story.setMetadata('stars',stars)
        logger.debug("stars:(%s)"%self.story.getMetadata('stars'))

        warning = soup.find('strong',string='Warning')
        if warning != None:
            for li in warning.find_next('ul').find_all('li'):
                self.story.addToList('warnings',stripHTML(li))

        # get cover
        img = soup.find(None,{'class':'row fic-header'}).find('img')
        if img:
            cover_url = img['src']
            # usually URL is for thumbnail. Try expected URL for larger image, if fails fall back to the original URL
            if self.setCoverImage(url,cover_url.replace('/covers-full/', '/covers-large/'))[0] == "failedtoload":
                self.setCoverImage(url,cover_url)
                    # some content is show as tables, this will preserve them

        itag = soup.find('i',title='Story Length')
        if itag and itag.has_attr('data-content'):
            # "calculated from 139,112 words"
            m = re.search(r"calculated from (?P<words>[0-9,]+) words",itag['data-content'])
            if m:
                self.story.setMetadata('numWords',m.group('words'))

    # grab the text for an individual chapter.
    def getChapterText(self, url):

        logger.debug('Getting chapter text from: %s' % url)

        ## httplib max headers removed Jan 2021--not seeing it
        ## anymore, they probably fixed their site.  See
        ## https://github.com/JimmXinu/FanFicFare/pull/174 for
        ## original details.
        soup = self.make_soup(self.get_request(url))

        div = soup.find('div',{'class':"chapter-inner chapter-content"})

        # TODO: these stories often have tables in, but these wont render correctly
        # defaults.ini output CSS now outlines/pads the tables, at least.

        if None == div:
            raise exceptions.FailedToDownload("Error downloading Chapter: %s!  Missing required element!" % url)

        if self.getConfig("include_author_notes",True):
            # collect both first, changing div for frontnote first
            # causes confusion in the tree.
            frontnote = div.find_previous('div', {'class':'author-note-portlet'})
            endnote = div.find_next('div', {'class':'author-note-portlet'})
            if frontnote:
                # move frontnote into chapter text div.
                div.insert(0,frontnote.extract())
            if endnote:
                # move endnote into chapter text div.
                div.append(endnote.extract())
        def has_display_none_style(tag):
            tag_class = tag.get('class', '')
            return any(style in tag_class for style in self.styles_to_ignore)

        for element in div.find_all(has_display_none_style):
            element.extract()
        return self.utf8FromSoup(url,div)
