from lettuce import world
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from courseware.courses import get_course_by_id
from xmodule import seq_module, vertical_module

from logging import getLogger
logger = getLogger(__name__)

## support functions

def get_courses():
    '''
    Returns dict of lists of courses available, keyed by course.org (ie university).
    Courses are sorted by course.number.
    '''
    courses = [c for c in modulestore().get_courses()
               if isinstance(c, CourseDescriptor)]
    courses = sorted(courses, key=lambda course: course.number)
    return courses


def get_courseware_with_tabs(course_id):
    """
    Given a course_id (string), return a courseware array of dictionaries for the
    top three levels of navigation. Same as get_courseware() except include
    the tabs on the right hand main navigation page.

    This hides the appropriate courseware as defined by the XML flag test:
    chapter.metadata.get('hide_from_toc','false').lower() == 'true'

    Example:

    [{
        'chapter_name': 'Overview',
        'sections': [{
            'clickable_tab_count': 0,
            'section_name': 'Welcome',
            'tab_classes': []
        }, {
            'clickable_tab_count': 1,
            'section_name': 'System Usage Sequence',
            'tab_classes': ['VerticalDescriptor']
        }, {
            'clickable_tab_count': 0,
            'section_name': 'Lab0: Using the tools',
            'tab_classes': ['HtmlDescriptor', 'HtmlDescriptor', 'CapaDescriptor']
        }, {
            'clickable_tab_count': 0,
            'section_name': 'Circuit Sandbox',
            'tab_classes': []
        }]
    }, {
        'chapter_name': 'Week 1',
        'sections': [{
            'clickable_tab_count': 4,
            'section_name': 'Administrivia and Circuit Elements',
            'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor']
        }, {
            'clickable_tab_count': 0,
            'section_name': 'Basic Circuit Analysis',
            'tab_classes': ['CapaDescriptor', 'CapaDescriptor', 'CapaDescriptor']
        }, {
            'clickable_tab_count': 0,
            'section_name': 'Resistor Divider',
            'tab_classes': []
        }, {
            'clickable_tab_count': 0,
            'section_name': 'Week 1 Tutorials',
            'tab_classes': []
        }]
    }, {
        'chapter_name': 'Midterm Exam',
        'sections': [{
            'clickable_tab_count': 2,
            'section_name': 'Midterm Exam',
            'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor']
        }]
    }]
    """

    course = get_course_by_id(course_id)
    chapters = [chapter for chapter in course.get_children() if chapter.metadata.get('hide_from_toc', 'false').lower() != 'true']
    courseware = [{'chapter_name': c.display_name,
                    'sections': [{'section_name': s.display_name,
                                'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0,
                                'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0,
                                        'class': t.__class__.__name__}
                                        for t in s.get_children()]}
                                for s in c.get_children() if s.metadata.get('hide_from_toc', 'false').lower() != 'true']}
                    for c in chapters]

    return courseware


def process_section(element, num_tabs=0):
    '''
    Process section reads through whatever is in 'course-content' and classifies it according to sequence module type.

    This function is recursive

    There are 6 types, with 6 actions.

    Sequence Module
    -contains one child module

    Vertical Module
    -contains other modules
    -process it and get its children, then process them

    Capa Module
    -problem type, contains only one problem
    -for this, the most complex type, we created a separate method, process_problem

    Video Module
    -video type, contains only one video
    -we only check to ensure that a section with class of video exists

    HTML Module
    -html text
    -we do not check anything about it

    Custom Tag Module
    -a custom 'hack' module type
    -there is a large variety of content that could go in a custom tag module, so we just pass if it is of this unusual type

    can be used like this:
    e = world.browser.find_by_css('section.course-content section')
    process_section(e)

    '''
    if element.has_class('xmodule_display xmodule_SequenceModule'):
        logger.debug('####### Processing xmodule_SequenceModule')
        child_modules = element.find_by_css("div>div>section[class^='xmodule']")
        for mod in child_modules:
            process_section(mod)

    elif element.has_class('xmodule_display xmodule_VerticalModule'):
        logger.debug('####### Processing xmodule_VerticalModule')
        vert_list = element.find_by_css("li section[class^='xmodule']")
        for item in vert_list:
            process_section(item)

    elif element.has_class('xmodule_display xmodule_CapaModule'):
        logger.debug('####### Processing xmodule_CapaModule')
        assert element.find_by_css("section[id^='problem']"), "No problems found in Capa Module"
        p = element.find_by_css("section[id^='problem']").first
        p_id = p['id']
        logger.debug('####################')
        logger.debug('id is "%s"' % p_id)
        logger.debug('####################')
        process_problem(p, p_id)

    elif element.has_class('xmodule_display xmodule_VideoModule'):
        logger.debug('####### Processing xmodule_VideoModule')
        assert element.find_by_css("section[class^='video']"), "No video found in Video Module"

    elif element.has_class('xmodule_display xmodule_HtmlModule'):
        logger.debug('####### Processing xmodule_HtmlModule')
        pass

    elif element.has_class('xmodule_display xmodule_CustomTagModule'):
        logger.debug('####### Processing xmodule_CustomTagModule')
        pass

    else:
        assert False, "Class for element not recognized!!"



def process_problem(element, problem_id):
    '''
    Process problem attempts to
    1) scan all the input fields and reset them
    2) click the 'check' button and look for an incorrect response (p.status text should be 'incorrect')
    3) click the 'show answer' button IF it exists and IF the answer is not already displayed
    4) enter the correct answer in each input box
    5) click the 'check' button and verify that answers are correct

    Because of all the ajax calls happening, sometimes the test fails because objects disconnect from the DOM.
    The basic functionality does exist, though, and I'm hoping that someone can take it over and make it super effective.
    '''

    prob_xmod = element.find_by_css("section.problem").first
    input_fields = prob_xmod.find_by_css("section[id^='input']")

    ## clear out all input to ensure an incorrect result
    for field in input_fields:
        field.find_by_css("input").first.fill('')

    ## because of cookies or the application, only click the 'check' button if the status is not already 'incorrect'
    # This would need to be reworked because multiple choice problems don't have this status
    # if prob_xmod.find_by_css("p.status").first.text.strip().lower() != 'incorrect':
    prob_xmod.find_by_css("section.action input.check").first.click()

    ## all elements become disconnected after the click
    ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
    # Wait for the ajax reload
    assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
    element = world.browser.find_by_css("section[id='%s']" % problem_id).first
    prob_xmod = element.find_by_css("section.problem").first
    input_fields = prob_xmod.find_by_css("section[id^='input']")
    for field in input_fields:
        assert field.find_by_css("div.incorrect"), "The 'check' button did not work for %s" % (problem_id)

    show_button = element.find_by_css("section.action input.show").first
    ## this logic is to ensure we do not accidentally hide the answers
    if show_button.value.lower() == 'show answer':
        show_button.click()
    else:
        pass

    ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
    assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
    element = world.browser.find_by_css("section[id='%s']" % problem_id).first
    prob_xmod = element.find_by_css("section.problem").first
    input_fields = prob_xmod.find_by_css("section[id^='input']")

    ## in each field, find the answer, and send it to the field.
    ## Note that this does not work if the answer type is a strange format, e.g. "either a or b"
    for field in input_fields:
        field.find_by_css("input").first.fill(field.find_by_css("p[id^='answer']").first.text)

    prob_xmod.find_by_css("section.action input.check").first.click()

    ## assert that we entered the correct answers
    ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
    assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
    element = world.browser.find_by_css("section[id='%s']" % problem_id).first
    prob_xmod = element.find_by_css("section.problem").first
    input_fields = prob_xmod.find_by_css("section[id^='input']")
    for field in input_fields:
        ## if you don't use 'starts with ^=' the test will fail because the actual class is 'correct ' (with a space)
        assert field.find_by_css("div[class^='correct']"), "The check answer values were not correct for %s" % problem_id
