#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
import requests

from lxml import html
from urllib2 import urlopen, URLError, urlparse
from publisher.models import PublisherModel

# Optional packages
try:
    import cssutils
    cssutils.log.setLevel(logging.FATAL)
except (ImportError, ):
    cssutils = None
try:
    from html5print import CSSBeautifier
except (ImportError, ):
    CSSBeautifier = None

from bs4 import BeautifulSoup

from django.db import models
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.core.files.base import ContentFile
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey

try:
    from django.template import Template, Context
except ImportError:  # Django < 1.8
    from django.template.loader import Template, Context
from django.utils.safestring import mark_safe

from .settings import ARCHIVE_PAGE_ASYNCHRONOUSLY, METADATA_FORM, STORAGE, RELATIONS, FILE_TYPE_CHOICES
from .compatible import ModelFormField
from .archiveurl import relative_to_full_url
from .tasks import archive_page_components

logger = logging.getLogger(__name__)

class ArchivePageXpathForm(forms.Form):
    title = forms.CharField(
        required=True,
        initial='/html/head/title')
    content = forms.CharField(
        required=True,
        initial='/html/body')

class ArchivedPage(PublisherModel):
    """
    Part of an HTML page
    """
    url = models.CharField(
        _('URL'),
        max_length=255,
        db_index=True)
    original_url = models.CharField(
        _('original URL'),
        max_length=255)
    title = models.CharField(
        _('title'),
        blank=True,
        max_length=200)
    content = models.TextField(
        _('content'),
        blank=True)
    head = models.TextField(
        _('head'),
        blank=True)
    template_name = models.CharField(
        _('template name'),
        max_length=70,
        blank=True,
        help_text=_("""Example: 'vintage/contact_page.html'. If this
            isn't provided, the system will use 'vintage/wpf.html'."""))
    metadata = ModelFormField(form=METADATA_FORM)
    xpaths = ModelFormField(form=ArchivePageXpathForm)
    refetch = models.BooleanField(default=False)

    class Meta:
        verbose_name = _('archived page')
        verbose_name_plural = _('archived pages')
        ordering = ('url',)
        unique_together = (('url', 'publisher_is_draft'),
                           ('original_url', 'publisher_is_draft'))

    def __unicode__(self):
        return "%s -- %s" % (self.url, self.title)

    @models.permalink
    def get_absolute_url(self):
        return ('vintage_detail', (), {'url': self.url.lstrip('/')})

    def clone_relations(self, src_obj, dst_obj):
        '''
        Overrides PublisherModel.clone_relations() which is called at end of .publish().
            * publishes src_obj associated files.
            * clones ArchivedPageRelation for src_obj to dst_obj.
        '''
        # Clear all Files for dst_obj
        dst_obj.files.clear()
        # Publish each of src_obj's files, if not already published,
        # and link each published-file to dst_obj.
        for src_file in src_obj.files.all():
            if src_file.publisher_linked:
                dst_file = src_file.publisher_linked
            else:
                try:
                    dst_file = ArchivedFile.objects.get(original_url=src_file.original_url,
                                                        publisher_is_draft=False)
                except ArchivedFile.DoesNotExist:
                    src_file.publish()
                    dst_file = src_file.publisher_linked
            dst_file.archivedpages.add(dst_obj)
        # Clone all ArchivedPageRelations
        dst_obj.relations.all().delete()
        for src_relation in src_obj.relations.all():
            dst_relation = ArchivedPageRelation(
                archivedpage=dst_obj,
                content_type=src_relation.content_type,
                object_id=src_relation.object_id,
                alias=src_relation.alias,
            )
            dst_relation.save()

    def get_files(self, file_type=None):
        """Get associated files, optional file type"""
        if file_type:
            return self.files.filter(file_type=file_type)
        return self.files.all()

    def get_page(self):
        page = requests.get(self.original_url)
        tree = html.fromstring(page.content)
        xcontent = tree.xpath(self.xpaths['content'])[0]

        try:
            # Get the title
            self.title = html.tostring(tree.xpath(self.xpaths['title'])[0])
        except (Exception, ) as exc:
            logger.exception('Error parsing title: %s', unicode(exc))

        try:
            # Get the head
            self.head = html.tostring(tree.xpath('/html/head')[0])
        except (Exception, ):
            logger.exception('Error parsing head: %s', unicode(exc))

        # Get main content
        self.content = self.get_content(xcontent)

    def get_content(self, xhtml):
        return html.tostring(xhtml)

    @property
    def published_state(self):
        if not self.publisher_linked:
            return 'draft'
        if self.is_dirty:
            return 'unpublished changes'
        return 'published'

    def update_links(self, save=True):
        """
        Parse through the saved document and make sure the links to the existing
        site are archived.
        """
        soup = BeautifulSoup(self.content, "html.parser")
        links = soup.find_all('a')
        for tag in links:
            if not tag.has_key('href'):  # NOQA
                continue

            href = tag['href'].strip()
            if href.startswith('{'):
                continue
            if href.startswith('javascript'):
                continue
            if '.mp3' in href:
                # It's a Link pointing to an audio file.
                if not href.startswith('{'):
                    url = self.get_original_file(href, file_type='audio')
                    tag['href'] = url
            elif ('.png' in href or
                  '.jpg' in href or
                  '.jpeg' in href or
                  '.gif' in href):
                # It's a Link pointing to an image.
                if not href.startswith('{'):
                    url = self.get_original_file(href, file_type='image')
                    tag['href'] = url
            elif ('.doc' in href or
                  '.docx' in href or
                  '.pdf' in href or
                  '.xls' in href):
                # It's a link pointing to a document.
                if not href.startswith('{'):
                    url = self.get_original_file(href, file_type='document')
                    tag['href'] = url
            else:
                # It's a regular Link pointing to url for a page.
                url = relative_to_full_url(self.original_url, href)
                try:
                    ap = ArchivedPage.objects.get(original_url=url, publisher_is_draft=True)
                    url = "{%% url 'vintage_detail' url='%s' %%}" % ap.url.lstrip('/')
                except (ArchivedPage.DoesNotExist, ):
                    logger.warning('Unable to get archived page: %s', url)
                tag['href'] = url

        self.content = unicode(soup.prettify())
        if save:
            self.save()

    def strip_js(self, save=True):
        soup = BeautifulSoup(self.content, "html.parser")
        scripts = soup.find_all('script')
        [s.extract() for s in scripts]
        self.content = unicode(soup.prettify())
        if save:
            self.save()

    def update_audio_tags(self, save=True):
        """
        Parse through the saved document and make sure the images are archived.
        """
        soup = BeautifulSoup(self.content, "html.parser")
        audio_tags = soup.find_all('audio')
        for tag in audio_tags:
            src = tag['src'].strip()
            if not src.startswith('{'):
                url = self.get_original_file(src, file_type='audio')
                tag['src'] = url
        self.content = unicode(soup.prettify())
        if save:
            self.save()

    def update_images(self, save=True):
        """
        Parse through the saved document and make sure the images are archived.
        """
        soup = BeautifulSoup(self.content, "html.parser")
        images = soup.find_all('img')
        for tag in images:
            src = tag['src'].strip()
            if not src.startswith('{'):
                url = self.get_original_file(src, file_type='image')
                tag['src'] = url
        self.content = unicode(soup.prettify())
        if save:
            self.save()

    def update_css(self, save=True):
        """
        Parse the head of the document for css files.
        """
        soup = BeautifulSoup(self.head, "html.parser")
        # All the link head tags
        css = soup.find_all('link')
        for i, link in enumerate(css):
            # Make sure we are dealing with stylesheets
            if 'stylesheet' in link.get('rel', []):
                # The url of the stylesheet
                src = link['href'].strip()
                # if this has special markup, skip
                if not src.startswith('{'):
                    # Generate the local file as editable
                    url = self.get_original_file(
                        src, editable=True,
                        update_func=self.update_internal_css,
                        pretty=CSSBeautifier.beautify,
                        pretty_args=[4],  # Indent
                        order=i,
                        file_type='css')
                    link['href'] = url
        styles = soup.find_all('style')
        for style in styles:
            style.string = self.update_internal_css(self.original_url, style.string)
        self.head = unicode(soup.prettify())
        if save:
            self.save()

    def update_internal_css(self, original_path, content):
        """Given the contents of the css file, parse out any urls so we
        have them locally. Note: that if cssutils is not installed it
        will return the content as is.

        """
        def replace_url(url):
            """This is called with the cssutils.replaceUrls function"""
            parsed = urlparse.urlparse(original_path)
            # In the case where the path contains .., we make the url proper
            if url.startswith('..'):
                url = os.path.abspath(
                    os.path.join(
                        os.path.dirname(parsed.path), url))
                # Build the full url with domain so we can download it
                url = '{}://{}{}'.format(parsed.scheme, parsed.netloc, url)
            # Generate the local file for the url
            return self.get_original_file(url)

        # We can parse the css urls if cssutils is available
        if cssutils:
            sheet = cssutils.parseString(content)
            cssutils.replaceUrls(sheet, replace_url)
            return sheet.cssText
        return content

    def get_original_file(self, path, **kwargs):
        """
        Given a full or partial path, download and create the archivedfile.
        Return the url path instance
        """
        editable = kwargs.get('editable', False)
        update_func = kwargs.get('update_func', None)
        pretty = kwargs.get('pretty', None)
        pretty_args = kwargs.get('pretty_args', [])
        order = kwargs.get('order', 0)
        file_type = kwargs.get('file_type', '')

        if path.startswith('//'):
            path = 'http:%s' % path
        parsed = urlparse.urlparse(path.strip())
        path = relative_to_full_url(self.original_url, path.strip())

        # Get or create the archived file
        af, _ = ArchivedFile.objects.get_or_create(original_url=path,
                                                   publisher_is_draft=self.publisher_is_draft)
        af.order = order
        af.file_type = file_type
        af.save()

        try:
            try:
                file_content = urlopen(path).read()
            except (Exception, ) as exc:
                logger.exception('Error opening file: %s', unicode(exc))
                file_content = ''

            try:
                # Save the file contents
                af.content.save(
                    os.path.basename(parsed.path), ContentFile(file_content))
            except (Exception,) as exc:
                logger.exception('Error saving file: %s', unicode(exc))
                return ''

            # If we want the content of the file to be editable, we create an
            # addtional model record
            if editable:
                try:
                    source, _ = ArchivedSourceFile.objects.get_or_create(
                        archivedfile=af)

                    # Optinal function to handle the file contents
                    if update_func:
                        file_content = update_func(path, file_content)

                    # Optional function to prettyify the file contents
                    if pretty:
                        file_content = pretty(file_content, *pretty_args)

                    source.source = file_content
                    source.save()
                except (Exception, ) as exc:
                    logger.exception(
                        'Error creating editable srouce: %s', unicode(exc))
        except URLError:
            return path

        self.files.add(af)
        return '{{ STATIC_URL }}%s' % af.content.url

    def save(self, *args, **kwargs):
        if (not self.content) or self.refetch:
            if not self.id:
                super(ArchivedPage, self).save(*args, **kwargs)
            if ARCHIVE_PAGE_ASYNCHRONOUSLY:
                archive_page_components.delay(self)
            else:
                archive_page_components(self)

        super(ArchivedPage, self).save(*args, **kwargs)

    if RELATIONS:
        def get_related_content_type(self, content_type):
            """
            Get all related items of the specified content type
            """
            return self.relations.filter(content_type__name=content_type)

        def get_relation_type(self, relation_type):
            """
            Get all relations of the specified relation type
            """
            return self.relations.filter(alias__iexact=relation_type)

        def get_content_object(self, field):
            app_label, model = field[1].split('.')
            ctype = ContentType.objects.get(app_label=app_label, model=model)
            ar = self.get_related_content_type(ctype.name)
            if len(ar) > 0:
                return ar[0].content_object
            else:
                return None

def get_upload_path(instance, filename):
    """
    Return the path based on the primary_key of the related page
    """
    from urlparse import urlparse
    parsed = urlparse(instance.original_url)
    directory_name = os.path.normpath(
        os.path.join(
            'vintage',
            parsed.netloc,
            os.path.dirname(parsed.path).strip('/'))
    )
    new_filename = os.path.normpath(
        instance.content.storage.get_valid_name(
            os.path.basename(filename)))
    return os.path.join(directory_name, new_filename)

class ArchivedFile(PublisherModel):
    """
    A non-html file used in an Archived Page, such as a file
    """
    archivedpages = models.ManyToManyField(ArchivedPage, related_name='files')
    original_url = models.CharField(
        _('original URL'),
        max_length=255)
    content = models.FileField(
        max_length=255,
        upload_to=get_upload_path,
        storage=STORAGE())
    file_type = models.CharField(
        _('type'), choices=FILE_TYPE_CHOICES,
        blank=True,
        max_length=25)
    order = models.IntegerField(default=0)

    class Meta:
        ordering = ('order', )
        unique_together = (('original_url', 'publisher_is_draft'), )

    def __unicode__(self):
        return self.original_url

    def clone_relations(self, src_obj, dst_obj):
        '''
        When published(), runs this at the end to keep relations nsync:
            * Leaving this here as a note, be careful adding logic in here,
              initially tried cloning file-page relationships from here with strange
              results.  That work is being done in ArchivedPage.clone_relations().
        '''
        pass

    @property
    def published_state(self):
        if not self.publisher_linked:
            return 'draft'
        if self.is_dirty:
            return 'unpublished changes'
        return 'published'

class ArchivedSourceFile(models.Model):
    archivedfile = models.OneToOneField(ArchivedFile)
    source = models.TextField(_('source'))

    def save(self, *args, **kwargs):
        # Only update the source file if the `source` field has data
        from django.core.files.base import ContentFile

        if self.source:
            path = self.archivedfile.original_url
            if path.startswith('//'):
                path = 'http:%s' % path

            # Render the source content
            source = mark_safe(Template(self.source).render(Context({})))

            # Fetch the storage and name of the content field
            storage = self.archivedfile.content.storage
            file_name = self.archivedfile.content.file.name
            try:
                # generate the archived filed using the source
                storage.save(file_name, ContentFile(source))
            except (Exception, ) as exc:
                logger.exception('Error writing source file: %s', unicode(exc))

        super(ArchivedSourceFile, self).save(*args, **kwargs)

if RELATIONS:
    ARCHIVEDPAGE_RELATION_LIMITS = reduce(lambda x, y: x | y, RELATIONS)
else:
    ARCHIVEDPAGE_RELATION_LIMITS = []

class ArchivedPageRelationManager(models.Manager):
    def get_content_type(self, content_type):
        """
        Get all the items of the given content type related to this item.
        """
        qs = self.get_query_set()
        return qs.filter(content_type__name=content_type)

    def get_relation_type(self, relation_type):
        """
        Get all the items of the given relationship type related to this item.
        """
        qs = self.get_query_set()
        return qs.filter(relation_type=relation_type)

class ArchivedPageRelation(models.Model):
    """Related item"""
    archivedpage = models.ForeignKey(
        ArchivedPage, related_name='relations')
    content_type = models.ForeignKey(
        ContentType, limit_choices_to=ARCHIVEDPAGE_RELATION_LIMITS)
    object_id = models.PositiveIntegerField(
        blank=False, null=False)
    content_object = GenericForeignKey(
        ct_field='content_type', fk_field='object_id')
    alias = models.CharField(
        verbose_name=_("Alias"), max_length=200, blank=True, null=True,
        help_text=_("A generic text field to tag a relation, "
                    "like 'leadphoto'."))

    objects = ArchivedPageRelationManager()

    def __unicode__(self):
        return u"ArchivedPageRelation"


from itertools import islice, chain
from collections import Counter

class WordleSolver:

    points = {
        's': 61, 'e': 61, 'a': 52, 'r': 39, 'o': 38, 'i': 34,
        'l': 32, 't': 30, 'n': 26, 'd': 23, 'u': 22, 'c': 19,
        'p': 18, 'y': 18, 'm': 17, 'h': 16, 'g': 14, 'b': 14,
        'k': 11, 'f': 10, 'w': 9, 'v': 6, 'z': 2, 'x': 2,
        'j': 2, 'q': 1
    }

    def __init__(
        self,
        infile:iter,
        wordlength:int = 5,
        resetpoints:bool = False,
        run:bool=True,
    ):
        '''
        Initiates WordleSolver instance attributes and runs run method.

        :param infile: Iterable of words to be searched through.
                       All words should be the length specified
                       in wordlength.

        :param wordlength: Length of words that to be put in Wordle.
                           Defaults to 5.

        :param resetpoints: Whether or not to reinitialize how words are
                            scored based on words in infile.
        '''
        self.infile = infile
        if resetpoints:
            self.setpoints()

        self.length = wordlength

        self.green = []
        self.yellow = {position: set() for position in range(self.length)}
        self.cum_yel = set()
        self.black = set()

        self.results = {}
        self.result = ""
        self.topscore = 0

        self.action = -1

        self.attempts = 1
        if run:
            self.run()

    def __str__(self):
        string = (f'green: {self.green}\n'
                  + f'yellow: {self.yellow}\n'
                  + f'{self.cum_yel}\n'
                  + f'black: {self.black}\n')
        return string

    def findqualifiers(self):
        '''Fills results with words that meet Wordle requiremets.'''
        self.results.clear()

        for word in self.infile:
            score = 0
            for i, (greenlet, wordlet) in (
                enumerate(zip(self.green, word))
            ):
                if (
                    (greenlet != '' and greenlet != wordlet)
                    or (wordlet in self.yellow[i])
                    or (wordlet in self.black)
                ):
                    score = -1

            for yellowlet in self.cum_yel:
                if yellowlet not in word:
                    score = -1

            if score != -1:
                self.results[word] = 0

        print(f'\n{len(self.results)} results found')

    def assignscores(self):
        '''Gives each word in results a score based on its letters.'''
        for word in self.results:
            self.results[word] = 0
            lets_present = set()
            for let in word:
                if let not in lets_present:
                    self.results[word] += self.points[let]
                    lets_present.add(let)
                else:
                    self.results[word] -= self.points[let]

        self.results = dict(sorted(
            self.results.items(),
            key=lambda item: item[1],
            reverse=True,
        ))

    def gettophit(self) -> str:
        '''Sets, prints, and returns result with highest value.'''
        self.result = max(self.results, key=self.results.get)
        self.topscore = max(self.results.values())
        print(self.result, self.topscore, '\n')
        return self.result

    def evaluate(self, colors: str, word: str):
        '''Sets instance attributes according to given colors.'''
        self.green.clear()
        for i, (c, w) in enumerate(zip(colors, word)):
            if c == 'g':
                self.green.append(w)
                if c in self.black:
                    self.black.remove(c)
            else:
                self.green.append('')

            if c == 'y':
                self.yellow[i].add(w)

            if c == 'b':
                self.black.add(w)

        self.cum_yel = set.union(*self.yellow.values())

        self.black.difference_update(self.green)
        self.black.difference_update(self.cum_yel)

        for i, let in enumerate(self.green):
            if let:
                self.yellow[i].difference_update([let])

    def askaction(self, prevact3:bool=False) -> str:
        '''
        Determines available actions, displays them, and asks user
        what they want to do.

        :param prevact3: Whether or not the user previously selected
                         action 3.
        '''
        options = {"1": "use word",
                   "2": "reroll",
                   "3": "display more results",
                   "4": "manually chose word",
                   "5": "end program",
                   }

        if len(self.results) <= 1:
            del options['2']
            del options['3']

        if prevact3:
            del options['1']
            try:
                del options['2']
            except KeyError:
                pass

        for option, instruction in options.items():
            print(f'({option}) - {instruction}')

        while True:
            answer = input('Enter number: ')
            if answer in options:
                self.action = answer
                return self.action

            print('Error: Invalid input')

    def askeval(self) -> str:
        '''Asks user for and returns colors given by Wordle'''
        while True:
            evl = input(f'Enter colors for word {self.result}:  ')
            evl = evl.lower()

            if len(evl) == self.length:
                for let in evl:
                    if let not in {'g','y','b',}:
                        break
                else:
                    return evl
            print('Error: Invalid input')

    def askword(self) -> str:
        '''Asks for and returns word entered by user'''
        while True:
            word = input('Enter word:  ')
            word = word.lower()

            if len(word) == self.length:
                return word

            print(f'Error: Word must be {self.length} letters')

    def checkcont(self) -> bool:
        '''Checks if program should be ended'''
        if (self.green and '' not in self.green) or self.action == '5':
            print(f'Program ended on {self.attempts} attempts')
            return False
        return True

    def gettopfive(self) -> dict:
        '''Prints and removes top five results'''
        result_slice = dict(islice(self.results.items(), 5))
        print(f'\n{result_slice}\n')
        [self.results.pop(key) for key in result_slice.keys()]
        return result_slice

    def run(self):
        while True:
            self.findqualifiers()
            self.assignscores()
            self.gettophit()
            while True:
                self.action = self.askaction(prevact3=self.action=='3')

                if self.action == '1':
                    self.evaluate(self.askeval(), self.result)
                    break

                elif self.action == '2':
                    del self.results[self.result]
                    print()
                    self.gettophit()

                elif self.action == '3':
                    self.gettopfive()

                elif self.action == '4':
                    self.result = self.askword()
                    self.evaluate(self.askeval(), self.result)
                    break

                elif self.action == '5':
                    break

            if not self.checkcont():
                break

            self.attempts += 1

            # d = self.__dict__.copy()
            # d.pop('infile')
            # print(d)

    def setpoints(self):
        '''Resets the points attribute according to words in infile.'''
        all_lets = list(chain(*self.infile))
        let_count = dict(Counter(all_lets))
        minimum = min(let_count.values())
        for let in let_count:
            let_count[let] //= minimum
        # Sort dictionary into descending order by value
        # just to look nice
        let_count = dict(sorted(
            let_count.items(),
            key=lambda item: item[1],
            reverse=True,
        ))
        self.points = let_count
        return self.points

# Make a set of five letter words from file
with open('words.txt','r') as f:
    wordsfile = f.read().split('\n')

fiveLet = [word for word in wordsfile if len(word) == 5]

app = WordleSolver(fiveLet)


# -*- coding: utf-8 -*-
from google.appengine.datastore.datastore_query import Cursor
from google.appengine.ext import ndb
from collections import OrderedDict
from bp_includes.lib.basehandler import BaseHandler
from bp_includes.models import LogEmail
from google.appengine.api import users as g_users #https://cloud.google.com/appengine/docs/python/refdocs/modules/google/appengine/api/users#get_current_user

class AdminLogsEmailsHandler(BaseHandler):
    def get(self):
        p = self.request.get('p')
        q = self.request.get('q')
        c = self.request.get('c')
        forward = True if p not in ['prev'] else False
        cursor = Cursor(urlsafe=c)

        if q:
            qry = LogEmail.query(LogEmail.to == q.lower())
        else:
            qry = LogEmail.query()

        PAGE_SIZE = 50
        if forward:
            emails, next_cursor, more = qry.order(LogEmail.key).fetch_page(PAGE_SIZE, start_cursor=cursor)
            if next_cursor and more:
                self.view.next_cursor = next_cursor
            if c:
                self.view.prev_cursor = cursor.reversed()
        else:
            emails, next_cursor, more = qry.order(-LogEmail.key).fetch_page(PAGE_SIZE, start_cursor=cursor)
            emails = list(reversed(emails))
            if next_cursor and more:
                self.view.prev_cursor = next_cursor
            self.view.next_cursor = cursor.reversed()

        def pager_url(p, cursor):
            params = OrderedDict()
            if q:
                params['q'] = q
            if p in ['prev']:
                params['p'] = p
            if cursor:
                params['c'] = cursor.urlsafe()
            return self.uri_for('admin-logs-emails', **params)

        self.view.pager_url = pager_url
        self.view.q = q

        params = {
            "list_columns": [('when', 'Fecha'),
                             ('to', 'Destinatario'),
                             ('subject', 'Asunto'),
            #                 ('sender', 'Sender'),
            #                 ('body', 'Body')
            ],
            "emails": emails,
            "count": qry.count()
        }
        params['nickname'] = g_users.get_current_user().email().lower()
        return self.render_template('usage/admin_logs_emails.html', **params)

class AdminLogsEmailViewHandler(BaseHandler):
    def get(self, email_id):
        try:
            emaildata = LogEmail.get_by_id(long(email_id))
            if emaildata:
                params = {
                    'emailinfo': emaildata
                }
                params['nickname'] = g_users.get_current_user().email().lower()
                return self.render_template('usage/admin_logs_email_view.html', **params)
        except ValueError:
            pass
        self.abort(404)


#!/usr/bin/python
"""
#################################################################
Script Header

$Id: $ US27698

Copyright (c) 2016-2017 Cisco Systems, Inc.

Name:
   cmCC27698_3pcc_BS_IOT_Interop_IPv6_275_BroadWorksHangsUpBeforeAnswer.py

Author:
    Vishnu Prasad B(vishnpra@cisco.com)

Purpose:

    This test case verifies a BroadWorks user originated call registered with
    IPv6 that is hung up by the BroadWorks user before it is answered.

Description:

    1: Originate a call from BroadWorks User to the DUT.
    DUT Do not answer the call.
    BroadWorks User Disconnect the call before the DUT answers.

Test bed requirement:
    1: 2 3pcc phones
    2: Both  phone should register successfully before running script

Test Steps:
    1. Phone B calls to Phone A(DUT)
    2. Phone B disconnects the call
    Verify:
    1. Phone A(DUT) will get ring from Phone B
    2. Call is disconnected
    3. Verify the SIP signaling to and from the DUT.
    4.Make sure the DUT sends all messages to the primary Application Server.
    5.Verify that 180 ringing sent
    6.Verify that 487 is sent
    7.Verify ACK

# Known Bugs:
"""

import tng
import logging
from tng.api import concurrent
from tng_sl.device.endpoint.synergylite.synergylite_3pcc_extended\
    import wait_for_ccapi_call_states
from tng_sl.contrib.mpp.phone_line_reg_helper import PhoneLineRegHelper
from tng_sl.contrib.mpp.phone_line_reg_helper import PhoneConfigHelper
from tng_sl.contrib.mpp.tshark_helper import TsharkHelper
from tng_sl.contrib.setup_helper import SetupHelpersTestCase

log = logging.getLogger("IPv6_BW_Hangsup_Before_Answer")

class BWHangsupBeforeAnswer(SetupHelpersTestCase, tng.api.TestCase):

    helpers = (PhoneConfigHelper, PhoneLineRegHelper, TsharkHelper)
    helper_num_devices = 2

    def setUp(self):
        log.info("Start of setUp")
        concurrent([
            self.oPhone1.ui.set_param_value,
            self.oPhone2.ui.set_param_value],
            {'IP Mode': 'IPv6 Only', 'SIP IP Preference': 'IPv6'})
        self.serverproxy = self.toolkit.get_test_env_info(
            section='bsoft', parameter_name="as_ip_addr6")
        log.info("End of setUp")

    def test_ipv6_bw_hangsup_before_answer(self):
        log.info("Start of IPv6_BW_Hangsup_Before_Answer")
        log.info('Start tshark on linux')

        dut_ip = self.oPhone1.ui.get_param_value("Current IP_IPV6")
        filter_cmd = ('port sip and host {}'.format(dut_ip))
        capture_file = self.tshark.tshark_start(filter_cmd)

        log.info("Phone2 dial Phone1's number: {}".format(self.user_id1))
        self.oPhone2.ccapi.dial('null', self.user_id1, '', 1, 0, 1)
        # check ophone1 ringout status and oPhone2 ringing status
        wait_for_ccapi_call_states(
            self.devices, ("RINGING", "PROCEEDING"))

        log.info("Phone2 disconnects the call")
        self.oPhone2.ccapi.hangUp('0000')
        # check two phones are in idle status
        wait_for_ccapi_call_states(self.devices, ("IDLE", "IDLE"), timeout=20)

        log.info('Stop tshark on Linux')
        self.tshark.tshark_stop()

        # tshark analysis
        log.info("Start tshark analysis")
        received_msgs = self.tshark.tshark_read(
            file=capture_file, protocol='sip')

        expected_msgs = dict()
        expected_msgs['frame_src'] = [dut_ip, dut_ip, self.serverproxy]
        expected_msgs['frame_dst'] = [
            self.serverproxy, self.serverproxy, dut_ip]
        expected_msgs['frame_proto'] = ['SIP', 'SIP', 'SIP']
        expected_msgs['frame_data'] = [
            'Status: 180 Ringing',
            'Status: 487 Request Terminated',
            'Request: ACK']

        result_src = self.tshark.tshark_call_flow(
            expected=expected_msgs, received=received_msgs)

        self.assertTrue(result_src, "Messages from DUT doesnt match")
        log.info(
            "Successfully verified traces for BroadWorksHangsUpBeforeAnswer")
        log.info("Tshark analysis stopped")
        log.info("Stop of IPv6_BW_Hangsup_Before_Answer")

def main():
    tng.api.runner()

if __name__ == '__main__':
    tng.run(main)