#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai

from __future__ import (unicode_literals, division, absolute_import, print_function)

import re
import json
import time

from calibre.utils.date import parse_only_date

from calibre_plugins.overdrive_link.numbers import value_unit
from calibre_plugins.overdrive_link.formats import (
    FORMAT_ADOBE_EPUB, FORMAT_ADOBE_PDF, FORMAT_BLIO, FORMAT_ACOUSTIK,
    FORMAT_AXIS360_READER, FORMAT_AXIS360_AUDIO)
from calibre_plugins.overdrive_link.book import (LibraryBook, InfoBook)
from calibre_plugins.overdrive_link.library import SearchableLibrary
from calibre_plugins.overdrive_link.author_prep import normalize_author
from calibre_plugins.overdrive_link.title_prep import normalize_title
from calibre_plugins.overdrive_link.net import (open_url, hostname_from_url)
from calibre_plugins.overdrive_link.parseweb import (LibraryError, class_contains, must_find, text_only, beautiful_soup)
from calibre_plugins.overdrive_link.language import LANGUAGE_NAME_LOWER

from .python_transition import (IS_PYTHON2)
if IS_PYTHON2:
    from .python_transition import (http, repr, urllib)
else:
    import http.cookiejar
    import urllib.parse


__license__ = 'GPL v3'
__copyright__ = '2012-2025, John Howell <jhowell@acm.org>'


FORMAT_OF_NAME = {
    'Axis 360 eAudio': FORMAT_AXIS360_AUDIO,
    'Axis 360 eBook': FORMAT_AXIS360_READER,
    'ePUB': FORMAT_ADOBE_EPUB,
    'In-Browser Reader': FORMAT_AXIS360_READER,
    'PDF': FORMAT_ADOBE_PDF,
    }


# Bucks County still has EPUBs as of 9/2015: http://buckslib.axis360.baker-taylor.com/


def library_host(library_id):
    if '.' in library_id:
        return library_id

    return '%s.axis360.baker-taylor.com' % library_id


'''
http://ocls.axis360.baker-taylor.com/Search/GetContent?availabilityFilter=ALL&formatType=NONE&firstload=1&layout=ListView&term=scott+adams&searchBy=keyword&sort=-score&author=&subject=&genre=&series=&sourcePage=&includeRecommendable=False&pubDate=&dateAdded=&audience=&format=&take=10&skip=0&page=1&pageSize=10

/Search/GetListContent?sortby=Score&sortorder=-1&term=stephen%20king&searchby=&availability=All&format=&releasedate=&addeddate=&language=&
agelevel=&pageSize=24&page=1&_=1545920834709 HTTP/1.1

http://ocls.axis360.baker-taylor.com/Title?itemId=0012420466
Overview: title, author, publisher, date
Details: language, isbn
Select a format (blio, epub, pdf, acoustik)

'''


class Axis360(SearchableLibrary):
    id = 'ax'
    name = 'Axis 360'
    formats_supported = {FORMAT_ACOUSTIK, FORMAT_ADOBE_EPUB, FORMAT_ADOBE_PDF, FORMAT_BLIO,
                         FORMAT_AXIS360_READER, FORMAT_AXIS360_AUDIO}

    @staticmethod
    def validate_library_id(library_id, migrate=True, config=None):
        if (':' in library_id) or ('/' in library_id):
            library_id = hostname_from_url(library_id)

        if library_id.lower().endswith('.axis360.baker-taylor.com'):
            library_id = library_id[:-len('.axis360.baker-taylor.com')]    # strip suffix

        if not re.match(r'^([0-9a-zA-Z]+)$', library_id):
            raise ValueError('Axis 360 library id must be alphanumeric: "%s"' % library_id)

        return library_id.lower()

    @staticmethod
    def validate_book_id(book_id, library_id):
        if not re.match(r'^([0-9a-z]+)$', book_id):
            raise ValueError('Axis 360 book id must be alphanumeric: "%s"' % book_id)

        return book_id

    @staticmethod
    def book_url(library_id, book_id):
        return 'http://%s/Title?itemId=%s' % (library_host(library_id), book_id)

    def __init__(self):
        self.cookiejar = http.cookiejar.CookieJar()
        self.is_boundless = False

    def find_books(self, books, search_author, search_title, keyword_search):
        '''
        Search Axis 360 for books that match an author/title (or subsets thereof).
        '''

        if self.is_boundless:
            return False

        page_num = 1
        total_pages = 1
        total_results = 0
        results_processed = 0
        RESULTS_PER_PAGE = 24

        MAX_RESULTS_ALLOWED = 500

        '''
        data = {}
        data['layout'] = 'ListView'
        data['pageSize'] = '%d' % RESULTS_PER_PAGE
        data['firstload'] = '1'

        # establish page size
        open_url(self.log, 'http://%s/Search/GetLayoutViewIndex' % library_host(self.library_id), urllib.parse.urlencode(data),
                    cookiejar=self.cookiejar)
        '''

        while (page_num <= total_pages):
            data = {}
            data['sortby'] = 'Score'
            data['sortorder'] = '-1'
            data['term'] = ' '.join([search_author, search_title]).strip()
            data['searchBy'] = ''
            data['availability'] = 'All'
            data['format'] = ''
            data['releasedate'] = ''
            data['addeddate'] = ''
            data['language'] = ''
            data['agelevel'] = ''
            data['pageSize'] = '%d' % RESULTS_PER_PAGE
            data['page'] = '%d' % page_num
            data['_'] = '%d' % int(time.time() * 1000)

            response = open_url(self.log, 'http://%s/Search/GetListContent?%s' % (library_host(self.library_id), urllib.parse.urlencode(data)),
                                cookiejar=self.cookiejar)

            response_url = response.geturl()
            if "boundless.baker-taylor.com" in response_url:
                self.log.error(
                    "Library %s has transitioned from Axis 360 to Boundless. Change the provider for this library in the plugin configuration "
                    "to Boundless in order to allow search." % self.name)
                self.is_boundless = True
                return False

            results = json.loads(response.data_string)      # Parse the json results

            new_total_results = results["TotalItems"] or 0
            if total_results and (new_total_results != total_results):
                raise LibraryError('Total results changed from %d to %d' % (total_results, new_total_results))

            total_results = new_total_results
            total_pages = ((total_results - 1) // RESULTS_PER_PAGE) + 1  # floor division
            self.log.info('Response: page %d of %d. %d total results' % (page_num, total_pages, total_results))

            if total_results > MAX_RESULTS_ALLOWED:
                return True

            for book in (results["Items"] or []):
                '''
                "ISBN": "9780786030224",
                "ItemId": "0014426526",
                "LibraryId": "F762C8A1-00D2-E211-9CFC-3C4A92F959D3",
                "ListId": null,
                "Title": " End Game",
                "Author": "Gilstrap, John ",
                "FormatType": "EBT",
                "IsAvailable": true,
                "IsRTV": true,
                "IsRecommendable": false
                '''

                book_id = book["ItemId"]
                title = normalize_title(book["Title"].replace('<b>', '').replace('</b>', ''))
                authors = [normalize_author(a, unreverse=True) for a in re.split(r'[;#]', book["Author"])]

                isbn = book["ISBN"]

                '''
                pubdate = None
                book_pubdate = book["PublicationDate"]
                if book_pubdate:
                    pubdate = parse_only_date(book_pubdate, assume_utc=True)
                '''

                available = not book["IsRecommendable"]

                lbook = LibraryBook(
                    authors=authors, title=title, isbn=isbn,
                    available=available, lib=self, book_id=book_id, search_author=search_author)

                if not available:
                    self.log.info('Ignoring unavailable: %s' % repr(lbook))
                else:
                    self.log.info('Found: %s' % repr(lbook))
                    books.add(lbook)

                results_processed += 1

            if results_processed >= total_results:
                break

            page_num += 1

        if results_processed != total_results:
            raise LibraryError('Expected %s but found %d' % (value_unit(total_results, 'result'), results_processed))

        return False

    def get_book_info(self, book_id, cache):
        response = open_url(self.log, self.book_url(self.library_id, book_id))

        if ("The requested page is not valid" in response.data_string or
                "The requested title is no longer available" in response.data_string or
                "Error 404 Title Not Found" in response.data_string):
            self.log.info('Book not valid or no longer available')
            return InfoBook(lib=self, book_id=book_id)

        if "An error occurred while processing your request" in response.data_string:
            self.log.error('Axis 360: An error occurred while processing your request')
            return InfoBook(lib=self, book_id=book_id)

        publisher = ''
        pubdate = None
        isbn = ''
        formats = set()
        language = ''

        soup = beautiful_soup(response.data_string)

        book_overview = must_find(soup, 'main', attrs={'id': 'content'})
        book_title = must_find(book_overview, 'h1', attrs={'id': 'titleText'})
        title = normalize_title(text_only(book_title))

        book_author = must_find(book_overview, 'div', attrs=class_contains('product-author'))
        authors = [normalize_author(a, unreverse=True) for a in re.split(r'[;#]', text_only(book_author))]

        for row in soup.findAll('div', attrs={'class': 'row'}, recursive=True):
            header = row.find('h3', attrs=class_contains('bookinfo-header'))
            if header:
                parameter = row.find('span', attrs=class_contains('bookinfo-parameter'))
                if parameter:
                    header_txt = text_only(header)
                    parameter_txt = text_only(parameter)
                    if header_txt == 'Publisher':
                        publisher = parameter_txt
                    elif header_txt == 'Released':
                        pubdate = parse_only_date(parameter_txt, assume_utc=True)
                    elif header_txt == 'Language':
                        language = parameter_txt
                        language = LANGUAGE_NAME_LOWER.get(language, language)
                    elif header_txt == 'ISBN':
                        isbn = parameter_txt

        response2 = open_url(self.log, 'http://%s/Title/GetButtonView?itemId=%s' % (library_host(self.library_id), book_id))
        soup2 = beautiful_soup(response2.data_string)

        for row in soup2.findAll('div', attrs=class_contains('top-buffer'), recursive=True):
            header = row.find('h3', attrs=class_contains('bookinfo-header'))
            if header:
                parameter = row.find('div', attrs=class_contains('bookinfo-parameter'))
                if parameter:
                    header_txt = text_only(header)
                    if header_txt == 'Available Formats':
                        for fmt_name in text_only(parameter).split(','):
                            fmt_name = fmt_name.strip()
                            if fmt_name in FORMAT_OF_NAME:
                                formats.add(FORMAT_OF_NAME[fmt_name])
                            else:
                                self.log.warn('Unknown Axis360 format %s' % fmt_name)

        return InfoBook(
            authors=authors, title=title, isbn=isbn, publisher=publisher, pubdate=pubdate,
            language=language, formats=formats, lib=self, book_id=book_id)

    def get_current_book_availability(self, book_id):
        response = open_url(self.log, self.book_url(self.library_id, book_id))

        library_copies = 0
        available_copies = 0
        number_waiting_overall = 0
        estimated_wait_days = None

        if ("The requested page is not valid" in response.data_string or
                "The requested title is no longer available" in response.data_string or
                "Error 404 Title Not Found" in response.data_string):
            self.log.info('Book not valid or no longer available')
            return False

        response2 = open_url(self.log, 'http://%s/Title/GetButtonView?itemId=%s' % (library_host(self.library_id), book_id))
        soup2 = beautiful_soup(response2.data_string)

        for row in soup2.findAll('div', attrs=class_contains('top-buffer'), recursive=True):
            header = row.find('h3', attrs=class_contains('bookinfo-header'))
            if header:
                parameter = row.find('div', attrs=class_contains('bookinfo-parameter'))
                if parameter:
                    header_txt = text_only(header)
                    parameter_txt = text_only(parameter)
                    if header_txt == 'Available Copies':
                        m = re.match(r'([0-9]+) of ([0-9]+)', parameter_txt, flags=re.IGNORECASE)
                        if m:
                            available_copies = int(m.group(1))
                            library_copies = int(m.group(2))
                        else:
                            raise LibraryError('Unexpected Available Copies: %s' % parameter_txt)
                    elif header_txt == 'Patrons on Hold':
                        number_waiting_overall = int(parameter_txt)

        if not library_copies:
            self.log.info('Book no longer available')
            return False

        # not signed in so cannot determine if book is checked out or already on hold by this user

        # estimate availability
        return self.calculate_wait_weeks(
                library_copies=library_copies, available_copies=available_copies,
                number_waiting_overall=number_waiting_overall, estimated_wait_days=estimated_wait_days)
